* fix: terminal servers compatible with hash-based architecture
The terminal WebSocket servers (direct-terminal-ws and terminal-websocket)
used config.dataDir to validate sessions, which no longer exists in the
hash-based architecture. Also fixed node-pty failing to find tmux via
posix_spawnp.
Changes:
- Remove config.dataDir dependency, validate via `tmux has-session` instead
- Add resolveTmuxSession() to map user-facing IDs (ao-15) to hash-prefixed
tmux names (8474d6f29887-ao-15)
- Use explicit tmux path discovery (findTmux) since node-pty's posix_spawnp
doesn't reliably inherit PATH
- Include /opt/homebrew/bin in fallback PATH for macOS ARM
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add session resolution to ttyd server and use exact tmux matching
- Add findTmux() and resolveTmuxSession() to terminal-websocket.ts
(previously only in direct-terminal-ws.ts), fixing hash-prefixed
session lookup for the ttyd-based terminal server
- Use tmux exact match prefix (=sessionId) in has-session checks
to prevent ao-1 from matching ao-15 via prefix matching
- Add server compatibility tests that verify both servers handle
hash-based architecture correctly (14 tests)
- Include server/ in tsconfig and vitest config for typecheck coverage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract tmux-utils and add proper unit tests
- Extract findTmux(), resolveTmuxSession(), validateSessionId() into
shared server/tmux-utils.ts — eliminates duplication between
direct-terminal-ws.ts and terminal-websocket.ts
- Add 20 real unit tests with injected mocks that test actual behavior:
- findTmux: candidate priority, fallback to bare name
- resolveTmuxSession: exact match, hash-prefix resolution,
= prefix for preventing tmux prefix matching (ao-1 vs ao-15),
null when no session found, tmux not running
- validateSessionId: path traversal, shell injection, whitespace
- Slim down server-compatibility.test.ts to 10 structural checks
(imports tmux-utils, no loadConfig, no config.dataDir, no existsSync)
Total: 30 tests — all pass on fix branch, 8 fail on main
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add real integration tests for direct-terminal-ws
- Refactor direct-terminal-ws to export createDirectTerminalServer()
factory so tests can control server lifecycle without side effects
- Add 10 integration tests that create real tmux sessions, start the
real server, connect via WebSocket, and verify the full flow:
- Health endpoint returns 200
- Missing session parameter → close 1008
- Path traversal in session ID → close 1008
- Shell injection in session ID → close 1008
- Nonexistent tmux session → close 1008
- Real tmux session → connects and receives terminal output
- Hash-prefixed session resolution works end-to-end
- Can send input and receive echoed output
- Resize messages don't crash the connection
- Unknown HTTP path → 404
- Tests create/destroy tmux sessions in beforeAll/afterAll
- Server runs on random port (port 0) to avoid conflicts
- Total test suite: 40 tests (20 unit + 10 compatibility + 10 integration)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: add web server tests to CI pipeline
The web package was explicitly excluded from CI test runs
(pnpm -r --filter '!@composio/ao-web' test). Add a test-web job
that installs tmux, starts the tmux server, and runs the web
package tests (unit + integration).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address CI failures and bugbot review comments
- Fix lint: replace require() with ESM import in integration test
- Fix base-path mismatch: ttyd now uses user-facing sessionId for
--base-path/URL and actual tmux name for attach-session
- Fix bare "tmux" in ttyd spawn args: use TMUX constant (full path)
- Scope CI test-web job to server/__tests__/ to avoid pre-existing
failures in src/__tests__/
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: comprehensive unit and integration test coverage
Unit tests (77): validateSessionId covers all injection vectors (shell,
path traversal, command substitution, special chars, unicode, control
chars), findTmux covers all candidate paths and error types,
resolveTmuxSession covers exact match, hash-prefix resolution, suffix
matching precision, edge cases (single char, long lists, multiple
hyphens, different tmux paths).
Integration tests (45): health endpoint lifecycle (active count tracks
connections/disconnections), HTTP routing (404s for all non-health
paths), WebSocket validation (11 injection/traversal vectors), terminal
connection (resize, multi-resize, invalid JSON, non-resize JSON),
hash-prefixed resolution (suffix match, command passthrough, session key
tracking, cross-match prevention), terminal I/O (Ctrl-C, Tab, Enter,
empty messages, rapid keystrokes, multi-line), connection lifecycle
(cleanup, rapid connect/disconnect, error recovery), server creation
(independent instances).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: validate 12-char hex prefix in hash-prefixed session resolution
The previous endsWith("-{sessionId}") suffix match was ambiguous:
"hash-my-app-1" would falsely match a lookup for "app-1". Now validates
that the prefix matches the exact format generated by generateConfigHash
(12-char lowercase hex) before comparing the remainder.
Added unit tests for the ambiguity case and invalid prefix formats.
Updated integration test session names to use proper 12-char hex prefixes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: implement web dashboard with attention-zone UI, API routes, and SSE
Implements the Next.js 15 web dashboard for INT-1332 with:
- Attention-prioritized session cards (urgent/action/warning/ok/done zones)
- 6 API routes: sessions, spawn, send, kill, merge, SSE events
- 5 components: SessionCard, AttentionZone, PRStatus, CIBadge, Terminal
- Session detail page with PR merge readiness, CI checks, unresolved comments
- Tailwind CSS 4 dark theme matching the reference bash dashboard
- Mock data layer covering all attention states for development
- SSE endpoint for real-time lifecycle event streaming
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add vitest test suite for web dashboard (77 tests)
Add comprehensive tests covering API routes, component rendering, and
attention-level classification. Fix merge button visibility when no alerts present.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address all PR review feedback
- Fix SSE memory leak: add cancel() handler to clear intervals on disconnect
- Add X-Accel-Buffering header for reverse proxy compatibility
- Add input validation on all API routes (validateString, validateIdentifier)
- Import core types (SessionStatus, ActivityState, CIStatus, etc.) from
@agent-orchestrator/core instead of redeclaring them
- Fix hydration mismatch: render timestamp client-side only via useEffect
- Add error handling on fetch calls in Dashboard (check response.ok)
- Add cn() utility for conditional class composition
- Fix setTimeout leak: use useRef + useEffect cleanup in SessionCard
- Fix getAttentionLevel edge case: status-based checks outside PR block
- Add NaN check on parseInt in merge route
- Extract duplicated sizeLabel logic into shared getSizeLabel()
- Add TODO guard comment on mock-data.ts for production removal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address Codex review feedback (iteration 1)
- Use strict /^\d+$/ validation on PR merge route (reject "432foo")
- Treat exited agents with non-terminal status as urgent (crashed agents)
- Add explicit getAttentionLevel mappings for review_pending, approved, cleanup
- Guard SSE update loop against empty sessions array
- Use encodeURIComponent on session details link
- Tighten mergeScore types to Pick<DashboardPR, ...>
- Add stripControlChars() and apply to send route for shell safety
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: apply ESLint and Prettier formatting after rebase on main
- Add next-env.d.ts to ESLint ignores (triple-slash reference)
- Merge duplicate imports using inline type syntax (no-duplicate-imports)
- Replace non-null assertions with proper null checks
- Apply Prettier formatting across all packages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address Cursor Bugbot review findings
- Add reviewDecision "none" to warning zone in getAttentionLevel
- Change ACTION zone color from green to orange for proper priority signaling
- Fix SessionDetail date hydration mismatch with ClientDateCard pattern
- Add export const dynamic = "force-dynamic" to SSE route
- Add --color-accent-orange CSS variable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address Codex review iteration 2 findings
- Gate merge route on PR state (409 if not open) and draft status (422)
- Handle pr.state === "closed" in getAttentionLevel → done zone
- Reject messages that become empty after control char stripping
- Align SSEEvent types with actual emitted events (snapshot + activity)
- Add 6 new tests for edge cases (85 total)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve typecheck error in test helper (NextRequest init type)
Cast RequestInit to NextRequest's ConstructorParameters to fix
type incompatibility between global RequestInit.signal (null allowed)
and Next.js RequestInit.signal (null not allowed).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve remaining review threads (zone colors, draft PRs, ternary)
- WORKING zone uses green (not blue) per design spec
- ACTION zone uses orange, consistent across all components
- Draft PRs with reviewDecision "none" fall to ok (not warning)
- Remove redundant ternary in getAttentionLevel
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Playwright screenshot tooling for visual verification
Agents and developers can now capture headless Chromium screenshots of
the running dashboard. Includes dev server auto-start, default page
specs, and CLI arg parsing. Screenshots committed to branch for PR
visibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add session detail screenshot after compact metadata redesign
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add screenshots for session detail redesign + zone reclassification
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address bugbot findings — unused type, kill route validation, draft PR awareness
- Remove unused SSEEvent type alias from types.ts
- Add validateIdentifier() to kill route for session ID validation
- Skip "needs review" alert for draft PRs in SessionCard getAlerts()
- Show "draft" instead of "needs review" in PRTableRow for draft PRs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: session detail redesign + attention zone reclassification
Session Detail:
- Nav bar replacing standalone back link
- Meta chips (project/branch/issue) with GitHub links
- Humanized status labels and relative timestamps
- Unified PR card with stats row, issues list, inline CI checks
- Clickable file paths in unresolved comments
Attention Zones (reordered by human action urgency):
- merge: PRs ready to merge (highest ROI per second)
- respond: agents waiting for input (quick unblock)
- review: CI failures, changes requested, conflicts
- pending: waiting on reviewer or CI
- working: agents doing their thing
- done: merged or terminated
Also fixes:
- Add validateIdentifier() to send route for session ID
- Update all tests for new zone names
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve 3 remaining review threads
- Remove duplicate eslint ignore entry for next-env.d.ts
- Export checkStatusIcon and ciCheckSortOrder from CIBadge, import in
SessionDetail instead of duplicating
- Add restore API route (was untracked)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore route validation, merged session guards
- Add validateIdentifier() to restore route (matches kill/send routes)
- Block restoring merged sessions with 409 response
- Hide kill button for merged sessions in top-row and expanded panel
- Expanded panel now shows restore OR terminate (not ternary fallback)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: exclude e2e from tsconfig include (fixes next build)
The e2e directory imports playwright which isn't resolvable during
next build. Since e2e files are standalone tooling (not app code),
exclude them from the main tsconfig include.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: unify CI check rendering via CICheckList layout prop
Add layout prop ("vertical"|"inline"|"expanded") to CICheckList,
remove duplicated InlineCIChecks from SessionDetail in favor of
reusing CICheckList with the appropriate layout mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add isDraft guard to unresolvedThreads pending check
Draft PRs with unresolved threads should fall through to "working",
not "pending". Adds the missing !pr.isDraft guard to match the
reviewDecision check on the next line.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address 9 bugbot findings across e2e, SSE, and mock-data
- server.ts: kill child process on waitForServer failure, drain stdout
to prevent backpressure
- screenshot.ts: validate Number() args for NaN with clear error messages
- events/route.ts: initialize interval variables as undefined
- mock-data.ts: exclude draft PRs from needsReview stat count
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prevent simultaneous restore and kill buttons in SessionCard
Add !isRestorable guard to kill button condition so the two buttons
are mutually exclusive. Restorable sessions show restore; non-restorable
exited sessions show kill.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove unreachable kill button from SessionCard top row
The kill button in the top row was dead code — isRestorable already
covers all activity === "exited" cases, so the !isRestorable && exited
condition could never be true.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update tests to match SessionCard after kill button removal
Tests referenced "kill session" text that no longer exists. Exited
sessions show "restore session", not "kill session". Updated 4 tests
to use onRestore/restore session instead of onKill/kill session.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>