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:
openhands 2026-02-16 06:32:58 +00:00
parent 9afa3f7bbd
commit 6cdca6e7c9
11 changed files with 46 additions and 26 deletions

View file

@ -1 +0,0 @@
63676

3
.gitignore vendored
View file

@ -13,3 +13,6 @@ worktrees/
# local screenshot artifacts
artifacts/
# beadboard runtime artifacts
.beadboard/

View file

@ -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",

View file

@ -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({

View file

@ -16,7 +16,7 @@ export async function GET(request: Request): Promise<Response> {
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);

View file

@ -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

View file

@ -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<BeadStatus, string> = {
ready: 'border-teal-500/30 bg-teal-500/15 text-teal-200',
const STATUS_CLASSES: Partial<Record<BeadStatus, string>> = {
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<BadgeSize, string> = {
@ -21,24 +24,30 @@ const SIZE_CLASSES: Record<BadgeSize, string> = {
md: 'text-xs px-2.5 py-0.5',
};
const STATUS_LABELS: Record<BeadStatus, string> = {
ready: 'Ready',
const STATUS_LABELS: Partial<Record<BeadStatus, string>> = {
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 (
<Badge
variant="outline"
className={cn(
'rounded-md border font-semibold',
STATUS_CLASSES[status],
statusClass,
SIZE_CLASSES[size]
)}
>
{STATUS_LABELS[status]}
{statusLabel}
</Badge>
);
}

View file

@ -121,7 +121,7 @@ function trimOrEmpty(value: unknown): string {
async function callBdAgentShow(beadId: string, projectRoot: string): Promise<AgentRecord | null> {
const showResult = await runBdCommand({
projectRoot,
args: ['show', beadId, '--json'],
args: ['agent', 'show', beadId, '--json'],
});
if (!showResult.success) {

View file

@ -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;
}

View file

@ -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: {

View file

@ -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;
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(() => {});
}