fix: address PR review comments and security issues
- Fix command injection in bb-init.mjs by using execFileSync with argument arrays - Fix parser.ts skipAgentFilter option not being respected - Fix src/app/globals.css truncated CSS rule causing parse errors - Fix status-badge.tsx BeadStatus type import from canonical source - Fix agent-registry.ts missing 'agent' prefix in callBdAgentShow - Fix tools/bb.ts null data access for activity-lease command - Fix src/app/api/sessions/route.ts projectRoot not passed to listAgents - Update package.json test script to include all test files - Fix tailwind.config.ts content glob missing UI components - Remove .beadboard/agent/runtime/existing-agent.pid and add .gitignore rule Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
9afa3f7bbd
commit
6cdca6e7c9
11 changed files with 46 additions and 26 deletions
|
|
@ -1 +0,0 @@
|
||||||
63676
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -13,3 +13,6 @@ worktrees/
|
||||||
|
|
||||||
# local screenshot artifacts
|
# local screenshot artifacts
|
||||||
artifacts/
|
artifacts/
|
||||||
|
|
||||||
|
# beadboard runtime artifacts
|
||||||
|
.beadboard/
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test": "node --test tests/bootstrap.test.mjs && node --import tsx --test tests/components/sessions/sessions-header.test.ts && node --import tsx --test tests/components/sessions/agent-station-logic.test.ts && node --import tsx --test tests/lib/parser.test.ts && node --import tsx --test tests/lib/pathing.test.ts"
|
"test": "node --test tests/bootstrap.test.mjs && node --import tsx --test tests/api/events-route.test.ts tests/api/mutations-routes.test.ts tests/api/projects-route.test.ts tests/api/sessions-route.test.ts tests/components/sessions/agent-station-logic.test.ts tests/components/sessions/sessions-header-logic.test.ts tests/components/sessions/sessions-header.test.ts tests/components/sessions/sessions-store.test.ts tests/components/shared/status-utils-visual.test.ts tests/guards/graph-responsive-contract.test.mjs tests/guards/kanban-responsive-contract.test.mjs tests/guards/no-direct-jsonl-write.test.mjs tests/guards/no-inline-style-in-kanban.test.mjs tests/guards/ui-foundation-contract.test.mjs tests/hooks/use-beads-subscription-shallow.test.ts tests/hooks/use-beads-subscription.test.ts tests/hooks/use-url-state.test.ts tests/lib/activity.test.ts tests/lib/agent-liveness.test.ts tests/lib/agent-mail.test.ts tests/lib/agent-protocol.test.ts tests/lib/agent-registry-bd.test.ts tests/lib/agent-registry.test.ts tests/lib/agent-reservations.test.ts tests/lib/agent-sessions-liveness.test.ts tests/lib/agent-sessions-state.test.ts tests/lib/agent-sessions.test.ts tests/lib/agent-takeover.test.ts tests/lib/aggregate-read.test.ts tests/lib/bd-path.test.ts tests/lib/bridge.test.ts tests/lib/coalescer.test.ts tests/lib/graph-view.test.ts tests/lib/graph.test.ts tests/lib/identity-isolation.test.ts tests/lib/issue-editor.test.ts tests/lib/kanban.test.ts tests/lib/mission-pathing.test.ts tests/lib/mutations.test.ts tests/lib/parser.test.ts tests/lib/path-overlap.test.ts tests/lib/pathing.test.ts tests/lib/project-context.test.ts tests/lib/project-scope.test.ts tests/lib/read-issues.test.ts tests/lib/read-text-retry.test.ts tests/lib/realtime-history.test.ts tests/lib/realtime.test.ts tests/lib/registry.test.ts tests/lib/scanner.test.ts tests/lib/snapshot-differ-stress.test.ts tests/lib/snapshot-differ.test.ts tests/lib/social-cards.test.ts tests/lib/swarm-cards.test.ts tests/lib/swarm-molecules-simple.test.ts tests/lib/swarm-molecules.test.ts tests/lib/watcher.test.ts tests/lib/writeback.test.ts tests/scripts/bb-init.test.ts tests/skills/beadboard-driver/generate-agent-name.test.ts tests/skills/beadboard-driver/readiness-report.test.ts tests/skills/beadboard-driver/resolve-bb.test.ts tests/skills/beadboard-driver/session-preflight.test.ts tests/skills/beadboard-driver/skill-local-runner.test.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-avatar": "^1.1.11",
|
"@radix-ui/react-avatar": "^1.1.11",
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import { parseArgs } from 'node:util';
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import { execSync } from 'node:child_process';
|
import { execSync, execFileSync } from 'node:child_process';
|
||||||
|
|
||||||
function log(obj) {
|
function log(obj) {
|
||||||
process.stdout.write(`${JSON.stringify(obj, null, 2)}
|
process.stdout.write(`${JSON.stringify(obj, null, 2)}
|
||||||
|
|
@ -48,20 +48,20 @@ async function resolveBbPath() {
|
||||||
const tsEntry = path.join(process.cwd(), 'tools', 'bb.ts');
|
const tsEntry = path.join(process.cwd(), 'tools', 'bb.ts');
|
||||||
try {
|
try {
|
||||||
await fs.access(tsEntry);
|
await fs.access(tsEntry);
|
||||||
return `npx tsx ${tsEntry}`;
|
return { type: 'tsx', path: tsEntry };
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
if (envRepo) {
|
if (envRepo) {
|
||||||
const p = path.join(envRepo, 'bb.ps1');
|
const p = path.join(envRepo, 'bb.ps1');
|
||||||
try {
|
try {
|
||||||
await fs.access(p);
|
await fs.access(p);
|
||||||
return p;
|
return { type: 'powershell', path: p };
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
const envTs = path.join(envRepo, 'tools', 'bb.ts');
|
const envTs = path.join(envRepo, 'tools', 'bb.ts');
|
||||||
try {
|
try {
|
||||||
await fs.access(envTs);
|
await fs.access(envTs);
|
||||||
return `npx tsx ${envTs}`;
|
return { type: 'tsx', path: envTs };
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,9 +82,9 @@ async function main() {
|
||||||
|
|
||||||
const isNonInteractive = values['non-interactive'];
|
const isNonInteractive = values['non-interactive'];
|
||||||
const projectRoot = values['project-root'] || process.cwd();
|
const projectRoot = values['project-root'] || process.cwd();
|
||||||
const bbPath = await resolveBbPath();
|
const bbResult = await resolveBbPath();
|
||||||
|
|
||||||
if (!bbPath) {
|
if (!bbResult) {
|
||||||
error('BB_NOT_FOUND', 'Could not resolve bb.ps1 or tools/bb.ts');
|
error('BB_NOT_FOUND', 'Could not resolve bb.ps1 or tools/bb.ts');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,8 +103,6 @@ async function main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const bbExec = bbPath.includes('npx tsx') ? bbPath : `powershell.exe -NoProfile -Command "& '${bbPath}'"`;
|
|
||||||
|
|
||||||
// Compose environment fingerprint (Rig)
|
// Compose environment fingerprint (Rig)
|
||||||
const rigId = `${os.platform()}-${os.arch()}-${os.hostname()}`;
|
const rigId = `${os.platform()}-${os.arch()}-${os.hostname()}`;
|
||||||
|
|
||||||
|
|
@ -112,10 +110,18 @@ async function main() {
|
||||||
|
|
||||||
if (mode === 'register') {
|
if (mode === 'register') {
|
||||||
const role = values.role || 'agent';
|
const role = values.role || 'agent';
|
||||||
execSync(`${bbExec} agent register --name ${agentId} --role ${role} --rig ${rigId} --json`, { stdio: 'ignore', cwd: projectRoot, env });
|
const registerArgs = bbResult.type === 'tsx'
|
||||||
|
? ['tsx', bbResult.path, 'agent', 'register', '--name', agentId, '--role', role, '--rig', rigId, '--json']
|
||||||
|
: ['agent', 'register', '--name', agentId, '--role', role, '--rig', rigId, '--json'];
|
||||||
|
const registerCmd = bbResult.type === 'tsx' ? 'npx' : bbResult.path;
|
||||||
|
execFileSync(registerCmd, registerArgs, { stdio: 'ignore', cwd: projectRoot, env });
|
||||||
} else {
|
} else {
|
||||||
// Start/Extend the lease to show we are now active
|
// Start/Extend the lease to show we are now active
|
||||||
execSync(`${bbExec} agent activity-lease --agent ${agentId} --json`, { stdio: 'ignore', cwd: projectRoot, env });
|
const leaseArgs = bbResult.type === 'tsx'
|
||||||
|
? ['tsx', bbResult.path, 'agent', 'activity-lease', '--agent', agentId, '--json']
|
||||||
|
: ['agent', 'activity-lease', '--agent', agentId, '--json'];
|
||||||
|
const leaseCmd = bbResult.type === 'tsx' ? 'npx' : bbResult.path;
|
||||||
|
execFileSync(leaseCmd, leaseArgs, { stdio: 'ignore', cwd: projectRoot, env });
|
||||||
}
|
}
|
||||||
|
|
||||||
log({
|
log({
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export async function GET(request: Request): Promise<Response> {
|
||||||
const communication = await getCommunicationSummary();
|
const communication = await getCommunicationSummary();
|
||||||
const livenessMap = await getAgentLivenessMap(projectRoot, activity);
|
const livenessMap = await getAgentLivenessMap(projectRoot, activity);
|
||||||
const incursions = await calculateIncursions();
|
const incursions = await calculateIncursions();
|
||||||
const agentsResult = await listAgents({});
|
const agentsResult = await listAgents({}, { projectRoot });
|
||||||
|
|
||||||
const feed = buildSessionTaskFeed(issues, activity, communication, livenessMap);
|
const feed = buildSessionTaskFeed(issues, activity, communication, livenessMap);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -296,4 +296,3 @@ body::after {
|
||||||
filter: drop-shadow(0 0 10px rgba(56, 189, 248, 0.6));
|
filter: drop-shadow(0 0 10px rgba(56, 189, 248, 0.6));
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-graph-flow .workflow-edge-cycle .react-f
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import type { BeadStatus } from '@/lib/types';
|
||||||
|
|
||||||
type BeadStatus = 'ready' | 'in_progress' | 'blocked' | 'closed';
|
|
||||||
type BadgeSize = 'sm' | 'md';
|
type BadgeSize = 'sm' | 'md';
|
||||||
|
|
||||||
interface StatusBadgeProps {
|
interface StatusBadgeProps {
|
||||||
|
|
@ -9,11 +9,14 @@ interface StatusBadgeProps {
|
||||||
size?: BadgeSize;
|
size?: BadgeSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
const STATUS_CLASSES: Record<BeadStatus, string> = {
|
const STATUS_CLASSES: Partial<Record<BeadStatus, string>> = {
|
||||||
ready: 'border-teal-500/30 bg-teal-500/15 text-teal-200',
|
open: 'border-teal-500/30 bg-teal-500/15 text-teal-200',
|
||||||
in_progress: 'border-green-500/30 bg-green-500/15 text-green-200',
|
in_progress: 'border-green-500/30 bg-green-500/15 text-green-200',
|
||||||
blocked: 'border-amber-500/30 bg-amber-500/15 text-amber-200',
|
blocked: 'border-amber-500/30 bg-amber-500/15 text-amber-200',
|
||||||
|
deferred: 'border-slate-500/30 bg-slate-500/15 text-slate-300',
|
||||||
closed: 'border-slate-500/30 bg-slate-500/15 text-slate-300',
|
closed: 'border-slate-500/30 bg-slate-500/15 text-slate-300',
|
||||||
|
pinned: 'border-purple-500/30 bg-purple-500/15 text-purple-200',
|
||||||
|
hooked: 'border-cyan-500/30 bg-cyan-500/15 text-cyan-200',
|
||||||
};
|
};
|
||||||
|
|
||||||
const SIZE_CLASSES: Record<BadgeSize, string> = {
|
const SIZE_CLASSES: Record<BadgeSize, string> = {
|
||||||
|
|
@ -21,24 +24,30 @@ const SIZE_CLASSES: Record<BadgeSize, string> = {
|
||||||
md: 'text-xs px-2.5 py-0.5',
|
md: 'text-xs px-2.5 py-0.5',
|
||||||
};
|
};
|
||||||
|
|
||||||
const STATUS_LABELS: Record<BeadStatus, string> = {
|
const STATUS_LABELS: Partial<Record<BeadStatus, string>> = {
|
||||||
ready: 'Ready',
|
open: 'Open',
|
||||||
in_progress: 'In Progress',
|
in_progress: 'In Progress',
|
||||||
blocked: 'Blocked',
|
blocked: 'Blocked',
|
||||||
|
deferred: 'Deferred',
|
||||||
closed: 'Closed',
|
closed: 'Closed',
|
||||||
|
pinned: 'Pinned',
|
||||||
|
hooked: 'Hooked',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function StatusBadge({ status, size = 'md' }: StatusBadgeProps) {
|
export function StatusBadge({ status, size = 'md' }: StatusBadgeProps) {
|
||||||
|
const statusClass = STATUS_CLASSES[status] || 'border-slate-500/30 bg-slate-500/15 text-slate-300';
|
||||||
|
const statusLabel = STATUS_LABELS[status] || status;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border font-semibold',
|
'rounded-md border font-semibold',
|
||||||
STATUS_CLASSES[status],
|
statusClass,
|
||||||
SIZE_CLASSES[size]
|
SIZE_CLASSES[size]
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{STATUS_LABELS[status]}
|
{statusLabel}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ function trimOrEmpty(value: unknown): string {
|
||||||
async function callBdAgentShow(beadId: string, projectRoot: string): Promise<AgentRecord | null> {
|
async function callBdAgentShow(beadId: string, projectRoot: string): Promise<AgentRecord | null> {
|
||||||
const showResult = await runBdCommand({
|
const showResult = await runBdCommand({
|
||||||
projectRoot,
|
projectRoot,
|
||||||
args: ['show', beadId, '--json'],
|
args: ['agent', 'show', beadId, '--json'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!showResult.success) {
|
if (!showResult.success) {
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ export function parseIssuesJsonl(text: string, options: ParseIssuesOptions = {})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exclude agent identities from standard mission lists
|
// Exclude agent identities from standard mission lists
|
||||||
if (normalized.labels.includes('gt:agent')) {
|
if (!options.skipAgentFilter && normalized.labels.includes('gt:agent')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import tailwindcssAnimate from 'tailwindcss-animate';
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
darkMode: ['class'],
|
darkMode: ['class'],
|
||||||
content: ['./src/**/*.{ts,tsx}'],
|
content: ['./src/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,11 @@ function printResponse(response: AnyCommandResponse, json: boolean) {
|
||||||
console.log(`Agent: ${d.agent_id}\nRole: ${d.role}\nStatus: ${d.status}\nLast Seen: ${d.last_seen_at}`);
|
console.log(`Agent: ${d.agent_id}\nRole: ${d.role}\nStatus: ${d.status}\nLast Seen: ${d.last_seen_at}`);
|
||||||
} else if (response.command === 'agent activity-lease') {
|
} else if (response.command === 'agent activity-lease') {
|
||||||
const d = response.data;
|
const d = response.data;
|
||||||
|
if (d) {
|
||||||
console.log(`✓ Activity lease extended: ${d.agent_id} (version: ${d.version})`);
|
console.log(`✓ Activity lease extended: ${d.agent_id} (version: ${d.version})`);
|
||||||
|
} else {
|
||||||
|
console.log(`✓ Activity lease extended.`);
|
||||||
|
}
|
||||||
} else if (response.command === 'agent send') {
|
} else if (response.command === 'agent send') {
|
||||||
const d = response.data;
|
const d = response.data;
|
||||||
console.log(`✓ Message sent: ${d.message_id} (state: ${d.state})`);
|
console.log(`✓ Message sent: ${d.message_id} (state: ${d.state})`);
|
||||||
|
|
@ -176,7 +180,7 @@ async function main() {
|
||||||
// we extend their lease as a side-effect of real work.
|
// we extend their lease as a side-effect of real work.
|
||||||
// This provides observability WITHOUT background workers or popups.
|
// This provides observability WITHOUT background workers or popups.
|
||||||
const targetAgent = stringArg(values.agent) || stringArg(values.from) || stringArg(values.name);
|
const targetAgent = stringArg(values.agent) || stringArg(values.from) || stringArg(values.name);
|
||||||
if (targetAgent && command !== 'register') {
|
if (targetAgent && command !== 'register' && command !== 'activity-lease') {
|
||||||
await extendActivityLease({ agent: targetAgent }, deps).catch(() => {});
|
await extendActivityLease({ agent: targetAgent }, deps).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue