- Move leftSidebarMode from URL state to local useState in unified-shell,
avoiding force-dynamic router round-trip that made the button appear broken - Replace fileURLToPath(new URL(..., import.meta.url)) with process.cwd()
in bb-pi-bootstrap.ts — import.meta.url is a webpack:// URL in Next.js,
causing cross-realm TypeError when passed to Node.js fileURLToPath()
Critical Security Fixes:
- Fix command injection vulnerability in Windows shims (beadboard.cmd, bb.cmd)
- Added path validation to block traversal (.. and root-relative paths)
- Added quotes around env var to prevent command injection
Reliability Fixes:
- Fix agent cache null safety bug
- Fixed callBdAgentShow() to check for cache misses (null check, expiration)
- Fixed getCachedAgent to properly return entry.data or null
- Fix null body crashes in mail ack route
- Added null check before casting body to object
- Returns 400 error instead of 500 for invalid requests
BD Compliance Fixes:
- Fix read-issues to use BD audit record path
- Ensures all writes go through bd audit record
- Maintains watcher/SSE parity and Dolt commit tracking
Code Quality Fixes:
- Fix path canonicalization violations
- Use canonicalizeWindowsPath() and windowsPathKey() from pathing module
- Prevents Windows edge cases and ensures machine-reproducible paths
- Fix typo: mobile-fronted → mobile-frontend
- Pin GitHub Actions tags
- softprops/action-gh-release@v1 → specific commit hash
- Register pr14 test in package.json (already registered)
Testing:
- Refactor broad exception handlers in Python scripts
- Replace except Exception: with specific exceptions
- Allows KeyboardInterrupt and SystemExit to propagate correctly
- All tests passing
- Removed broken LaunchSwarmDialog (formula-based) from TopBar/LeftPanel
- All Rocket buttons (TopBar, LeftPanel, DAG nodes, social cards) now open
AssignmentPanel (archetype-based) which actually works
- Every Rocket clears taskId first so assignMode && !taskId condition passes
- Conversation button priority: taskId always shows conversation, not assign panel
- Added TelemetryStrip: minimized right sidebar with status dots when non-telemetry
panel (conversation/assignment) is active
- Live feed has minimize button → restores last taskId or assignMode
- DAG nodes: Signal icon → restores telemetry feed
- Social button on DAG nodes: single router.push to avoid race (setView + setTaskId)
- Fixed social card message button: opens right panel with drawer:closed (no popup)
Co-Authored-By: Oz <oz-agent@warp.dev>
Resolved conflicts:
- .gitignore: kept both bd.sock.startlock and .beadboard/ entries
- package.json: kept feature branch test script (explicit enumeration)
- API routes: kept dynamic export + isValidProjectRoot from main
- globals.css: kept HEAD slideInFromRight animation
- use-beads-subscription.ts: kept HEAD onopen handler
- realtime.ts: kept main console.log in emit()
- snapshot-differ.ts: kept main type-aware dependency diff
Blue colors preserved from feature branch.
## The Collaboration Story
User requested ability to assign agent archetypes to tasks directly from
graph nodes. This was the core UI feature request.
## Design Decisions Made Together
1. **Placement**: We decided to put the assign UI at the bottom of the card
to not interfere with existing status/badges display
2. **Pattern**: Used Radix dropdown-menu (already in project) for consistency
3. **Visual feedback**: Added loading spinner during API calls, success/error
messages that auto-dismiss
4. **Closed tasks**: Excluded closed tasks from assignment (logical constraint)
## Issues We Encountered
- Initially only added POST handler for assignment
- User pointed out we needed DELETE for unassign - added that
- The unassign button (X) needed to call DELETE not POST
## API Changes
- Added DELETE handler to /api/swarm/prep for removing agent assignments
- Uses 'bd label remove' under the hood
## What the UI Shows
- Dropdown with available archetypes
- Badge showing assigned archetype name with color
- X button to unassign (on each badge)
- 'Assigned' label on already-assigned archetypes in dropdown
## Test Coverage
- Added graph-node-assign.test.tsx with 6 TDD tests
## Beads: beadboard-brq (closed)
This commit includes the new SwarmWorkspace with its 3 sub-tabs, the LeftPanel mission picker, and the comprehensive Operations Command Dashboard featuring the live interactive DAG telemetry and task assignment prep flow.
Fix merge conflicts intelligently:
- package.json: Use main's test script pattern (tests/guards/*.test.mjs && tests/**/*.test.ts)
- src/app/api/events/route.ts: Merge polling logic with telemetry event emission
- src/hooks/use-beads-subscription.ts: Merge event type handling (issues/telemetry/activity)
All changes preserve the new telemetry-based architecture while accepting
main's improved test coverage patterns.
- 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>
STORY:
The session backend needed to aggregate agent health from a live
telemetry stream rather than static bead metadata. This refactor
makes liveness signals real-time and accurate.
COLLABORATION:
We extended the ActivityEvent model with a native 'heartbeat' kind,
updated extendActivityLease() to emit through the activity bus, and
refactored getAgentLivenessMap() to prioritize heartbeat activity
history over stale bead metadata.
DELIVERABLES:
- ActivityEvent extended with 'heartbeat' kind
- extendActivityLease() emits heartbeats through activity bus
- getAgentLivenessMap() prefers telemetry over static metadata
- Registry APIs support projectRoot injection for testing
- Tests verify preference logic via TDD
VERIFICATION:
- 93/93 tests PASSING
- Heartbeat override verified in isolated temp projects
CLOSES: bb-buff.1.3
BLOCKS: bb-buff.3.2, bb-buff.3.3, bb-buff.2.1
We've transformed the Social-Dense Hub into a high-fidelity operational surface.
- BACKEND: Implemented Global Incursion Engine in agent-sessions.ts (N^2 overlap detection) and added the 60m 'Idle' state.
- API: Enriched the sessions payload with full metadata and active conflict arrays.
- HEADER: Delivered 4-state agent stations (Active/Stale/Evicted/Idle) with real-time 'time-ago' timers.
- FEED: Implemented the 'Fire Map' visuals:
* Global Incursion Ticker: High-visibility alerts for agent collisions.
* Local Conflict Badges: Pulsing pills on affected task cards.
- Refactored components for React-static compliance and strict TypeScript safety.
This commit completes the visibility track, allowing the human supervisor to monitor agent presence and friction in real-time.
OPERATIVE: silver-castle
SESSION: 2026-02-14-1430
Finalizing the backend engine for the Operative Protocol.
- Updated agent-sessions.ts to use the deriveLiveness logic and the 15m protocol threshold.
- Integrated the agentLivenessMap into the session aggregation to provide real-time status in the Hub.
- Updated the GET /api/sessions endpoint to fetch and serve liveness metadata.
- Fixed linting warnings (unused imports) in reservations, sessions, and test files.
This commit completes the backend contract for bb-u6f.6.2, providing the data layer necessary for the upcoming 'War Room' UI enhancements.
OPERATIVE: silver-castle
SESSION: 2026-02-14-1145
- Fix isValidProjectRoot() in 4 API routes to properly prevent path traversal
by using path.relative() to ensure paths stay within allowed base directory
(replaces ineffective normalized.includes('..') check)
- Fix readiness-report.mjs to remove misleading path traversal validation
that was ineffective after path.resolve() removes '..' segments
- Fix asNonEmptyString() in mutations.ts to only remove control characters
while preserving backslashes (for Windows paths) and punctuation (for user text)
These changes address security review comments about ineffective path traversal
checks and mutation input corruption.
Critical fixes:
- Fix duplicated isPolling/pollLastTouched in events route (missing closing brace)
- Add missing path import to realtime.ts (path.basename was used without import)
- Fix error.message leak in sessions and beads/read routes (security)
- Add missing NextResponse import to activity route
- Fix diffDependencies to use composite key (type:target) for accurate tracking
Code quality:
- Fix beadCounts computation in kanban-controls (was counting epic's own deps, not child issues)
- Replace require('path') with ES module imports throughout
Tests: 13/15 passing (2 contract tests remain brittle)
Co-authored-by: openhands <openhands@all-hands.dev>
- Add missing snapshot-differ.test.ts to npm test script
- Fix path traversal vulnerability in agent-mail.ts with message ID validation
- Fix readLastTouchedVersion to log errors instead of silently swallowing them
- Sanitize log statements to not leak full paths
- Add projectRoot validation to all API routes
- Fix activity persistence write race conditions with promise chaining
Co-authored-by: openhands <openhands@all-hands.dev>
We added the third major surface to the BeadBoard workspace: the Chronological Timeline. This provides the 'Audit' layer of our operational hierarchy.
Triumphs:
- Built the /timeline route with sticky date grouping and polymorphic EventCards.
- Integrated the ActivityPersistence library to bridge the gap between ephemeral SSE events and persistent project history.
- Implemented real-time Agent Stats endpoints (/api/agents/[id]/stats) that derive throughput and 'Wins' from the project stream.
Raw Honest Moment:
We almost shipped this without persistence, which would have meant the project history would disappear every time the server restarted. Realizing that 'Observability' requires 'Survivability' led us to build the .beadboard/activity.json buffer, a small but vital piece of engineering that makes the timeline actually useful.
This is our biggest UX pivot of the project. We abandoned the 'Page' model for a 'Command Workspace'.
Triumphs:
- Reclaimed 40% of previously wasted screen real-estate by moving to an auto-filling multi-column grid matrix.
- Built the 'Command Deck'—a high-density header that provides real-time agent presence monitoring at a glance.
- Implemented 'Social Post' cards that map technical protocols to human verbs (e.g., 'Falcon passed mission to Operative-B'), making the audit trail readable for humans.
- Engineered 'Silent Refresh' logic: the feed now appends new activity and comments smoothly without disruptive UI resets or scroll jumps.
Raw Honest Moment:
The original card-based social feed was a failure. It was beautiful in isolation but useless for actual supervision. We had to be honest about the horizontal bloat and rebuild the entire layout foundation from scratch using rem-based fluid units to satisfy the 'War Room' requirement.
We resolved a major project fragmentation issue today. The Graph page was technically divergent from the Kanban board, causing P0 'stale data' bugs. We realized that 'Polling' is the enemy of truth in a multi-agent system.
Triumphs:
- Refactored the core SSE transport into a shared useBeadsSubscription hook. Now Kanban, Graph, and Sessions all obey the same lifecycle: Event -> Authority Fetch -> Reconcile.
- Upgraded the Chokidar watcher to monitor the global .beadboard/agent/messages directory, ensuring agent communication arrives instantly in the social feed.
- Forced a watcher version bump to 3 to solve the ghost-listener problem where old watchers were blocking file access during HMR.
Raw Honest Moment:
We spent significant time debugging why 'closed' issues were missing from the UI, only to find we were victims of our own CLI defaults (--limit 50). The fix was simple but humiliating: we just needed to ask for the truth (--all --limit 0).