From 6cdca6e7c9b4e02957999db937f8b7781f31686c Mon Sep 17 00:00:00 2001 From: openhands Date: Mon, 16 Feb 2026 06:32:58 +0000 Subject: [PATCH] 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 --- .beadboard/agent/runtime/existing-agent.pid | 1 - .gitignore | 3 +++ package.json | 2 +- scripts/bb-init.mjs | 26 +++++++++++++-------- src/app/api/sessions/route.ts | 2 +- src/app/globals.css | 1 - src/components/shared/status-badge.tsx | 23 ++++++++++++------ src/lib/agent-registry.ts | 2 +- src/lib/parser.ts | 2 +- tailwind.config.ts | 2 +- tools/bb.ts | 8 +++++-- 11 files changed, 46 insertions(+), 26 deletions(-) delete mode 100644 .beadboard/agent/runtime/existing-agent.pid diff --git a/.beadboard/agent/runtime/existing-agent.pid b/.beadboard/agent/runtime/existing-agent.pid deleted file mode 100644 index a5e4cc1..0000000 --- a/.beadboard/agent/runtime/existing-agent.pid +++ /dev/null @@ -1 +0,0 @@ -63676 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4ad2758..433df80 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ worktrees/ # local screenshot artifacts artifacts/ + +# beadboard runtime artifacts +.beadboard/ diff --git a/package.json b/package.json index 4f87728..91bb851 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "start": "next start", "lint": "eslint .", "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": { "@radix-ui/react-avatar": "^1.1.11", diff --git a/scripts/bb-init.mjs b/scripts/bb-init.mjs index c62f24d..2e399bd 100644 --- a/scripts/bb-init.mjs +++ b/scripts/bb-init.mjs @@ -18,7 +18,7 @@ import { parseArgs } from 'node:util'; import fs from 'node:fs/promises'; import path from 'node:path'; import os from 'node:os'; -import { execSync } from 'node:child_process'; +import { execSync, execFileSync } from 'node:child_process'; function log(obj) { process.stdout.write(`${JSON.stringify(obj, null, 2)} @@ -48,20 +48,20 @@ async function resolveBbPath() { const tsEntry = path.join(process.cwd(), 'tools', 'bb.ts'); try { await fs.access(tsEntry); - return `npx tsx ${tsEntry}`; + return { type: 'tsx', path: tsEntry }; } catch {} if (envRepo) { const p = path.join(envRepo, 'bb.ps1'); try { await fs.access(p); - return p; + return { type: 'powershell', path: p }; } catch {} const envTs = path.join(envRepo, 'tools', 'bb.ts'); try { await fs.access(envTs); - return `npx tsx ${envTs}`; + return { type: 'tsx', path: envTs }; } catch {} } @@ -82,9 +82,9 @@ async function main() { const isNonInteractive = values['non-interactive']; 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'); } @@ -103,8 +103,6 @@ async function main() { } try { - const bbExec = bbPath.includes('npx tsx') ? bbPath : `powershell.exe -NoProfile -Command "& '${bbPath}'"`; - // Compose environment fingerprint (Rig) const rigId = `${os.platform()}-${os.arch()}-${os.hostname()}`; @@ -112,10 +110,18 @@ async function main() { if (mode === 'register') { 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 { // 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({ diff --git a/src/app/api/sessions/route.ts b/src/app/api/sessions/route.ts index 1629034..646f81f 100644 --- a/src/app/api/sessions/route.ts +++ b/src/app/api/sessions/route.ts @@ -16,7 +16,7 @@ export async function GET(request: Request): Promise { const communication = await getCommunicationSummary(); const livenessMap = await getAgentLivenessMap(projectRoot, activity); const incursions = await calculateIncursions(); - const agentsResult = await listAgents({}); + const agentsResult = await listAgents({}, { projectRoot }); const feed = buildSessionTaskFeed(issues, activity, communication, livenessMap); diff --git a/src/app/globals.css b/src/app/globals.css index 4d2101b..a6f2d44 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -296,4 +296,3 @@ body::after { filter: drop-shadow(0 0 10px rgba(56, 189, 248, 0.6)); } -.workflow-graph-flow .workflow-edge-cycle .react-f diff --git a/src/components/shared/status-badge.tsx b/src/components/shared/status-badge.tsx index 3b76a12..7311a90 100644 --- a/src/components/shared/status-badge.tsx +++ b/src/components/shared/status-badge.tsx @@ -1,7 +1,7 @@ import { Badge } from '@/components/ui/badge'; import { cn } from '@/lib/utils'; +import type { BeadStatus } from '@/lib/types'; -type BeadStatus = 'ready' | 'in_progress' | 'blocked' | 'closed'; type BadgeSize = 'sm' | 'md'; interface StatusBadgeProps { @@ -9,11 +9,14 @@ interface StatusBadgeProps { size?: BadgeSize; } -const STATUS_CLASSES: Record = { - ready: 'border-teal-500/30 bg-teal-500/15 text-teal-200', +const STATUS_CLASSES: Partial> = { + 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', 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', + 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 = { @@ -21,24 +24,30 @@ const SIZE_CLASSES: Record = { md: 'text-xs px-2.5 py-0.5', }; -const STATUS_LABELS: Record = { - ready: 'Ready', +const STATUS_LABELS: Partial> = { + open: 'Open', in_progress: 'In Progress', blocked: 'Blocked', + deferred: 'Deferred', closed: 'Closed', + pinned: 'Pinned', + hooked: 'Hooked', }; 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 ( - {STATUS_LABELS[status]} + {statusLabel} ); } diff --git a/src/lib/agent-registry.ts b/src/lib/agent-registry.ts index 01c795c..4b4bd74 100644 --- a/src/lib/agent-registry.ts +++ b/src/lib/agent-registry.ts @@ -121,7 +121,7 @@ function trimOrEmpty(value: unknown): string { async function callBdAgentShow(beadId: string, projectRoot: string): Promise { const showResult = await runBdCommand({ projectRoot, - args: ['show', beadId, '--json'], + args: ['agent', 'show', beadId, '--json'], }); if (!showResult.success) { diff --git a/src/lib/parser.ts b/src/lib/parser.ts index 6cd7c11..94aff0d 100644 --- a/src/lib/parser.ts +++ b/src/lib/parser.ts @@ -83,7 +83,7 @@ export function parseIssuesJsonl(text: string, options: ParseIssuesOptions = {}) } // Exclude agent identities from standard mission lists - if (normalized.labels.includes('gt:agent')) { + if (!options.skipAgentFilter && normalized.labels.includes('gt:agent')) { continue; } diff --git a/tailwind.config.ts b/tailwind.config.ts index 4ac1367..e43271b 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -3,7 +3,7 @@ import tailwindcssAnimate from 'tailwindcss-animate'; const config: Config = { darkMode: ['class'], - content: ['./src/**/*.{ts,tsx}'], + content: ['./src/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'], theme: { extend: { fontFamily: { diff --git a/tools/bb.ts b/tools/bb.ts index 6e72d3e..8407694 100644 --- a/tools/bb.ts +++ b/tools/bb.ts @@ -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}`); } else if (response.command === 'agent activity-lease') { const d = response.data; - console.log(`✓ Activity lease extended: ${d.agent_id} (version: ${d.version})`); + if (d) { + console.log(`✓ Activity lease extended: ${d.agent_id} (version: ${d.version})`); + } else { + console.log(`✓ Activity lease extended.`); + } } else if (response.command === 'agent send') { const d = response.data; 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. // This provides observability WITHOUT background workers or popups. 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(() => {}); }