Commit Graph

1596 Commits

Author SHA1 Message Date
prateek 77c9411f9c
Merge pull request #30 from ComposioHQ/fix/working-zone-expanded
fix: expand WORKING attention zone by default
2026-02-14 20:10:46 +05:30
prateek 90111da18d
feat: layered prompt system for agent sessions (#27)
* feat: implement layered prompt system for agent sessions

Replace hardcoded spawn prompts with a 3-layer composition system:
- Layer 1: BASE_AGENT_PROMPT constant with session lifecycle, git workflow, PR handling
- Layer 2: Config-derived context (project, repo, tracker, issue details via generatePrompt())
- Layer 3: User-customizable rules via agentRules (inline) and agentRulesFile (path)

The session-manager path fetches issue context from the tracker plugin and passes
the composed prompt via AgentLaunchConfig.prompt. The CLI spawn path delivers it
via tmux send-keys to keep agents interactive for follow-up messages.

Returns null when nothing to compose (no issue, no rules), preserving backward
compatibility for bare launches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use tmuxSendKeys for multi-line prompt delivery in CLI spawn

The buildPrompt() output contains newlines which tmux send-keys -l treats
as Enter keypresses, splitting the prompt into separate submissions. Use
the core tmuxSendKeys() helper which handles multi-line text via
load-buffer/paste-buffer.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: update spawn test to verify tmuxSendKeys usage

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 20:07:13 +05:30
Prateek a20cd9a8ca fix: expand WORKING attention zone by default
The most important section to see at a glance — agents actively working
should not be hidden behind a collapsed toggle.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 20:04:05 +05:30
prateek 24f14015e5
fix: wire dashboard to real session data with PR enrichment (#28)
* fix: wire dashboard to real session data with PR enrichment

- Parse owner/repo from GitHub PR URLs in session-manager for gh CLI calls
- Add static plugin imports in web services.ts (webpack can't resolve dynamic imports)
- Add PR enrichment to page.tsx server component with project matching fallback
- Add project matching fallback in API route for sessions without projectId
- Map bash "starting" status to "working" for backwards compatibility
- Add fallback runtime handle for bash-created sessions without runtimeHandle
- Add getPRSummary to SCM interface for fetching additions/deletions/title
- Implement getPRSummary in scm-github plugin
- Use getPRSummary in enrichSessionPR to populate PR diff stats
- Add plugin-tracker-github dependency to web package

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: update send test for fallback runtime handle behavior

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: register workspace-worktree plugin in web services

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 18:21:07 +05:30
prateek b7431424ff
feat: wire web dashboard API routes to real core services (#26)
* feat: wire web dashboard API routes to real core services

Replace all mock data in web API routes with real SessionManager, SCM,
and plugin calls. Add services singleton for lazy initialization and
a serialization layer for core Session → DashboardSession conversion.

Closes INT-1346

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: clear cached init on failure + clean up SSE intervals on stream close

- services.ts: Clear _aoServicesInit on rejection so subsequent calls
  retry instead of permanently returning a failed promise
- events/route.ts: Clear both intervals in the updates catch block
  (matching heartbeat behavior) to stop polling after disconnect

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:37:08 +05:30
prateek e5f0c08793
feat: enrich ao status with PR, CI, review, threads, and activity columns (#25)
* feat: enrich `ao status` with PR, CI, review, threads, and activity columns

The status command now displays a rich table with branch, PR number, CI
status, review decision, pending review threads, and agent activity state.
Data is fetched from the SCM plugin (GitHub) in parallel for performance.

Closes INT-1348

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address Bugbot review — thread errors, PR fallback, activity mapping

- Thread errors: `.catch(() => null)` instead of `.catch(() => [])` so
  failed getPendingComments shows `-` not `0`
- PR fallback: extract PR number from metadata URL when SCM lookup fails
- Activity fallback: use metadata `status` when agent introspection is
  unavailable (working→active, idle→idle, stuck/errored→blocked)
- Activity mapping: treat `assistant`/`result` as idle, only `tool_use`
  and `user` as active — avoids marking completed responses as working

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:33:50 +05:30
prateek 85ec8e8980
fix: remove sessions/ subdirectory from metadata paths (#24)
Core metadata module expected files at {dataDir}/sessions/{id} but bash
scripts write directly to {dataDir}/{id}. Align core with the bash
convention by removing the intermediate sessions/ subdirectory.

Closes INT-1347

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:28:58 +05:30
prateek 1eba42097b
fix: detect agent exit for all agent types, not just idle-reporting ones (#22)
The lifecycle manager only checked isProcessRunning when detectActivity
returned "idle". Agents like codex, aider, and opencode return "active"
for any non-empty terminal output (including shell prompt after exit),
so their exit was never detected. Now checks isProcessRunning for both
"idle" and "active" states.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:52:46 +05:30
prateek 7ae6ab17a2
chore: remove unused makeActions helper from event-factory (#23)
The makeActions function was exported but never imported anywhere.
All integration tests define actions inline.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:52:25 +05:30
prateek 90f14a6ca5
feat: notifier-composio plugin + integration tests for all plugins (#7)
* feat: implement notifier and terminal plugins (desktop, slack, webhook, iterm2, web)

Implement all 5 notification and terminal UI plugins for the agent
orchestrator, replacing stub files with full implementations of the
Notifier and Terminal interfaces from @agent-orchestrator/core.

Notifier plugins:
- notifier-desktop: OS notifications via osascript (macOS) / notify-send
  (Linux) with priority-based sound (urgent=sound, others=silent)
- notifier-slack: Slack Incoming Webhooks with Block Kit formatting,
  action buttons, PR links, CI status context blocks
- notifier-webhook: Generic HTTP POST with JSON payloads, configurable
  headers, and retry with backoff (default 2 retries)

Terminal plugins:
- terminal-iterm2: AppleScript-based iTerm2 tab management — detects
  existing tabs by profile name, creates/reuses tabs (ported from
  scripts/open-iterm-tab reference implementation)
- terminal-web: Web terminal session tracking for the dashboard's
  xterm.js frontend, providing URL generation and open-state tracking

All plugins follow the PluginModule pattern (manifest + create export)
and pass typecheck cleanly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add comprehensive vitest suites for all notifier and terminal plugins

Add 96 unit tests across 5 plugin packages covering:
- notifier-desktop: priority-based sound mapping, osascript/notify-send
  command generation, platform detection (macOS/Linux/unsupported),
  title formatting, error propagation
- notifier-slack: Block Kit message structure (header/section/context/
  divider blocks), priority emoji mapping, PR link and CI status
  rendering, action button generation (URL and callback variants),
  channel routing, post method
- notifier-webhook: JSON payload serialization, custom headers, retry
  logic (success after retry, exhausted retries, zero retries, network
  errors), timestamp ISO serialization
- terminal-iterm2: AppleScript command generation, tab reuse via profile
  name detection, new tab creation with tmux attach, runtimeHandle
  preference over session ID, batch openAll with delays, error fallback
- terminal-web: session open tracking, dashboard URL configuration,
  openAll batch registration, independent state per instance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review feedback — security, retry logic, side effects

- Add AppleScript injection escaping in terminal-iterm2 and notifier-desktop
- Webhook: only retry on 429/5xx (not 4xx), add exponential backoff
- terminal-iterm2: fix isSessionOpen selecting tab (side effect), remove dead openNewWindow
- notifier-slack: type-guard event.data access, add URL validation
- notifier-webhook: add URL validation, remove unused config interfaces
- Update all tests to cover new behaviors (108 tests passing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: codex review — shell injection, iTerm2 name lookup, notify-send args, retry validation

- terminal-iterm2: shell-escape session names in tmux command (single-quote wrapping)
- terminal-iterm2: use `name of aSession` instead of `profile name` for tab lookup
- notifier-desktop: fix notify-send arg order (options before title/body)
- notifier-webhook: clamp retries/retryDelayMs to safe values (non-negative, finite)
- notifier-slack: sanitize action_id to [a-z0-9_] characters only

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: lint/format compliance after rebase on main

- Fix ESLint errors: remove unused WebTerminalConfig, ignore next-env.d.ts
- Run prettier on all files for consistent formatting
- Update pnpm-lock.yaml with new lint dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: codex review round 2 — printf injection, platform guard, config validation

- terminal-iterm2: use shell-escaped name in printf title (not just tmux target)
- terminal-iterm2: add platform guard — no-op with warning on non-macOS
- notifier-webhook: validate customHeaders are string:string before spreading
- notifier-desktop: validate sound config as boolean (reject string "false")

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update lockfile after rebase on main

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add notifier-composio plugin and integration tests for all plugins

Add a new notifier-composio plugin as the recommended notification
transport using Composio's unified API for Slack, Discord, and Gmail.
Create comprehensive integration tests (79 tests across 6 files) for
all notifier and terminal plugins, mocking only I/O boundaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review feedback — security, retry logic, side effects

- Shell injection fix: double-escape (shell + AppleScript) in iTerm2 printf/tmux
- iTerm2 no-window crash: create window if none exists
- Composio graceful degradation: warn instead of throw when SDK missing
- Composio emailTo validation: require emailTo when defaultApp is gmail
- AbortSignal.timeout() replaces manual AbortController + {once: true} listener
- Discord channelName fallback for channel_id
- Slack empty action_id fallback
- Dashboard port: terminal-web default changed from 9847 to 3000
- escapeAppleScript deduplicated into core/utils.ts
- Consistent vitest version (^3.0.0) for composio plugin
- Remove duplicate eslint ignore entry
- Integration tests updated for shared event-factory helper
- Unit tests updated for new validation and escaping behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update lockfile after rebase on main

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent double ao_ prefix in Slack action_id fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: decouple Linux urgency from sound config, deduplicate validateUrl

- Linux --urgency=critical now driven by event priority, not sound config
- Moved validateUrl to core/utils.ts, imported by slack and webhook plugins

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: handle ESM ERR_MODULE_NOT_FOUND for composio-core detection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: codex review round 2 — printf injection, platform guard, config validation

- Slack action_id: append index for uniqueness (two "Retry" buttons no
  longer collide)
- Composio Gmail subject: extract GMAIL_SUBJECT constant so notify() and
  post() use the same value
- Agent integration tests: require API key env vars before running to
  prevent CI timeout when secrets are not configured
- Update lockfile after rebase on main

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: parallelize integration tests and increase CI timeout

- Remove singleFork: true from vitest config — all integration test
  files use unique session prefixes so they're safe to run concurrently
- Increase CI timeout from 15 to 20 minutes as safety margin for agent
  tests that make real API calls

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent unhandled promise rejection after timeout in composio notifier

Attach a no-op .catch() to the executeAction promise so that if the
timeout fires first and the action later rejects, it doesn't trigger
an unhandledRejection event.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:29:59 +05:30
prateek 06b5ec3267
feat: implement CLI with all commands (init, status, spawn, session, send, review-check, dashboard, open) (#6)
* feat: implement CLI with all commands (init, status, spawn, session, send, review-check, dashboard, open)

Implement the full `ao` CLI using Commander.js with 9 commands:

- ao init: Interactive setup wizard that creates agent-orchestrator.yaml
- ao status: Colored terminal table of all sessions (branch, activity, PR, CI)
- ao spawn <project> [issue]: Spawn single agent session (worktree + tmux + agent)
- ao batch-spawn <project> <issues...>: Batch spawn with duplicate detection
- ao session ls|kill|cleanup: Session management subcommands
- ao send <session> <message>: Smart message delivery with busy detection/retry
- ao review-check [project]: Scan PRs for review comments, trigger agent fixes
- ao dashboard: Start the Next.js web dashboard
- ao open [target]: Open session(s) in terminal tabs

Shared libraries:
- lib/shell.ts: Shell command helpers (tmux, git, gh wrappers)
- lib/metadata.ts: Flat metadata file read/write (backwards-compatible format)
- lib/format.ts: Terminal formatting (colors, tables, banners)

All commands code against core interfaces and flat metadata files,
matching the behavior of the reference bash scripts in scripts/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review — extract shared helpers, fix bugs, add tests

- Extract getTmuxSessions/getTmuxActivity to lib/shell.ts (was duplicated in 5 files)
- Fix readMetadata unsafe cast: return Partial<SessionMetadata> instead
- Fix dashboard webDir path: resolve from package root, not dist/
- Fix dashboard spawn error handling: add child.on("error")
- Fix getNextSessionNumber: escape regex metacharacters in prefix
- Fix fallback worktree creation: use existing branch name, not detached HEAD
- Fix post-create hooks: run before agent launch, not after
- Fix open --new-window: pass option through to openInTerminal
- Fix cleanup dry-run: separate summary message for dry-run mode
- Add Claude session introspection to status command (TTY→PID→CWD→JSONL)
- Add vitest test suite: 72 tests covering commands and lib utilities

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address all PR review issues, add lint/format compliance

Review fixes:
- Extract getTmuxSessions/getTmuxActivity to lib/shell.ts (was duped in 5 files)
- Fix readMetadata: return Partial<SessionMetadata> instead of unsafe cast
- Fix dashboard webDir: use createRequire + __dirname for ESM compat
- Fix dashboard spawn: add child.on("error") handler
- Fix getNextSessionNumber: escape regex metacharacters in prefix
- Fix fallback worktree: use existing branch name, not detached HEAD
- Fix post-create hooks: run before agent launch, not after
- Fix open --new-window: pass option through to openInTerminal
- Fix cleanup dry-run: separate summary message for dry-run mode
- Add Claude session introspection to status (TTY→PID→CWD→JSONL)
- Add port validation in init wizard
- Add clarifying comment for tmux C-u send-keys

Lint/format:
- Add no-console override for CLI package in eslint config
- Fix duplicate imports (node:fs, @agent-orchestrator/core)
- Fix unused variable in send test
- Apply prettier formatting across all files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address codex review — fd leak, input validation, security hardening

- Wrap openSync/readSync in try/finally to prevent fd leak on error
- Add timeout NaN/<=0 guard in send command
- Add port validation (1-65535) in dashboard command
- Sanitize issueId for git-ref-safe branch names
- Add --project validation in session ls/cleanup
- Fix batch-spawn same-run duplicate detection
- Replace shell interpolation with safe ps + JS filtering
- Wrap temp file usage in try/finally for cleanup on error
- Read only tail of JSONL files to avoid large file loads

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address remaining PR review comments

- Use `=== null` instead of falsy check for git worktree result (spawn.ts)
- Wrap spinner in try/catch so it stops on error (spawn.ts)
- Use exact match instead of substring for issue duplicate detection (metadata.ts)
- Check git worktree remove result before printing success (session.ts)
- Skip banner/headers/footer when --json is passed (status.ts)
- Add error handler on browser-open spawn (dashboard.ts)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address bugbot review — interrupt before send, error resilience, worktree validation

- review-check: send C-c + C-u to interrupt busy agent and clear input before
  delivering fix prompt, matching the reference bash script behavior
- review-check: wrap per-session send in try/catch so one failure doesn't abort
  remaining sessions
- spawn: check worktree creation return value and throw on failure instead of
  continuing with non-existent worktree path
- Update test mock for detached worktree to return success

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address 4 bugbot findings — status type, TTY match, retry safety, comment counting

- spawn: write status "spawning" (not "starting") to match SessionStatus type
- status: use column-based exact TTY match to prevent pts/1 matching pts/10
- send: use null-safe tmux() helper in retry loop instead of throwing exec()
- review-check: use GraphQL reviewThreads to count only unresolved comments,
  preventing fix prompts on PRs with all threads resolved

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: GraphQL variable passing and env var name sanitization

- review-check: use -F flags for GraphQL variables instead of string
  interpolation to prevent injection via repo config values
- spawn: sanitize env var name by replacing non-alphanumeric chars with
  underscores (my-app → MY_APP_SESSION, not MY-APP_SESSION)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add -l flag to tmux send-keys and remove dead activityIndicator code

- Add literal (-l) flag to send-keys so user text isn't interpreted as
  tmux key names (e.g. "Enter", "Escape")
- Remove unused activityIndicator function from format.ts and its tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: move agent-specific logic from CLI into agent plugins

detectActivity, introspect, and getLaunchCommand now live in the agent
plugins (claude-code, codex, aider) instead of being hardcoded in the
CLI commands. The Agent interface's detectActivity takes a plain string
of terminal output instead of a Session object, making plugins trivially
unit-testable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use -f for string GraphQL vars and -l flag for tmux send-keys

Addresses two bugbot findings in review-check.ts:
1. Use -f (string) instead of -F (typed) for owner/name GraphQL
   variables to prevent numeric coercion of repo names.
2. Use -l (literal) flag with send-keys and separate Enter call,
   matching the established tmux safety pattern in send.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: guard empty terminal output in lifecycle-manager and deduplicate send.ts

- lifecycle-manager: skip detectActivity when terminal output is empty
  to prevent false "killed" state when runtime probe returns no data
- send.ts: collapse identical isBusy/isProcessing into single isActive
- tests: use non-empty mock terminal output so detectActivity is exercised

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: validate message before side effects, remove dead updateMetadataField

- send.ts: move message validation before idle-wait loop and C-u clear
  so running `ao send session` with no message exits immediately without
  touching the tmux session
- metadata.ts: remove exported updateMetadataField (unused in production)
- metadata.test.ts: remove corresponding tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add -l flag and separate Enter for spawn prompt send-keys

Use literal flag (-l) when sending the initial prompt via tmux
send-keys in spawn.ts, and send Enter as a separate call. This
matches the pattern used in send.ts and review-check.ts and prevents
tmux from interpreting special characters in the issue ID.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: strict session prefix matching and validate project before use

- session-utils: add matchesPrefix() using regex ^prefix-\d+$ to prevent
  cross-project misattribution with overlapping prefixes (e.g. "app" vs
  "app-v2")
- Replace all startsWith(`${prefix}-`) calls across status.ts,
  review-check.ts, session.ts, and open.ts with matchesPrefix()
- status.ts, review-check.ts: move project validation before the
  projects map construction so invalid project IDs exit before any
  access to undefined config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: deduplicate escapeRegex and add -l flag to spawn send-keys

- Export escapeRegex from session-utils.ts, remove duplicate from spawn.ts
- Add -l (literal) flag to tmux send-keys for postCreate hooks and
  launch command, with Enter sent separately
- Prevents tmux from interpreting key names in user config or agent
  commands

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: update detectActivity to sync string signature across all plugins

After rebase on main, all agent plugins and their tests still had the
old async detectActivity(session: Session) signature. Updated to the
new sync detectActivity(terminalOutput: string): ActivityState across:
- agent-claude-code, agent-codex, agent-aider, agent-opencode plugins
- All plugin unit tests (test terminal output patterns, not process state)
- All integration tests (capture tmux pane output before calling)
- status.ts: use getSessionInfo() instead of nonexistent introspect()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: integration tests expect idle (not exited) from detectActivity after process exit

detectActivity is a pure terminal-text classifier that returns "idle" for
empty/shell-prompt output. Process exit detection is handled by isProcessRunning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: check last-line prompt before historical activity patterns in detectActivity

Reorder classifyTerminalOutput to check the last line for a prompt
character before scanning the full buffer for activity indicators like
"Reading" or "Thinking". This prevents historical output in the capture
buffer from causing false "active" results when the agent is idle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: update pnpm-lock.yaml for web dashboard dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: preserve state on getOutput failure, check permission prompts before activity indicators

- lifecycle-manager: remove .catch(() => "") from getOutput so failures
  reach the outer catch block that preserves stuck/needs_input states
- classifyTerminalOutput: check waiting_input prompts against bottom of
  buffer before full-buffer active indicators, preventing historical
  "Thinking"/"Reading" text from overriding current permission prompts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: detect agent exit via isProcessRunning, remove redundant active checks

- lifecycle-manager: replace dead "exited"/"blocked" branches with
  isProcessRunning check when detectActivity returns "idle" — correctly
  detects agent process exit inside a still-alive tmux session
- classifyTerminalOutput: remove redundant explicit active-indicator
  checks that returned the same "active" as the unconditional default

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: wrap killSession in try-catch so cleanup loop continues on failure

One session's kill failure (e.g. archiveMetadata fs error) no longer
aborts the entire cleanup loop, matching the pattern used by batch-spawn
and review-check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add comprehensive unit and integration tests for CLI

Unit tests (6 new files, 61 tests):
- session-utils: escapeRegex, matchesPrefix, findProjectForSession
- plugins: getAgent, getAgentByName lookup and error cases
- shell: exec, execSilent, tmux, git, gh, getTmuxSessions, getTmuxActivity
- review-check: GraphQL review checking, dry-run, fix prompt delivery
- open: target resolution (all/project/session), fallback, --new-window
- init: rejects existing config file

Integration tests (2 new files, 8 tests):
- spawn-send-kill: real tmux session create, send-keys, kill, graceful re-kill
- session-ls: multi-session listing, per-session capture, activity timestamps

CLI vitest config updated with explicit thread pool parallelization.
Total CLI tests: 68 → 129. Total integration tests: 20 → 28.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address 5 bugbot findings — symlink type, timeout cleanup, file validation, repo format

- spawn.ts: pass `type` param to symlinkSync based on lstat (dir vs file)
- spawn.ts: document TOCTOU gap in getNextSessionNumber (tmux rejects dupes)
- dashboard.ts: store browser timeout and clear it on child exit
- send.ts: wrap file read in try-catch with user-friendly error
- review-check.ts: validate owner/name split before GraphQL query

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:14:27 +05:30
prateek 8707faf11c
feat: implement SCM and tracker plugins (github, linear) (#4)
* feat: implement SCM and tracker plugins (github, linear)

Implement three plugin interfaces from packages/core/src/types.ts:

- scm-github: Full GitHub PR lifecycle via `gh` CLI — PR detection by
  branch, state tracking, CI checks, reviews, review decision, pending
  comments, automated bot comment detection (cursor[bot], codecov, etc.),
  and merge readiness (CI + reviews + conflicts + draft).

- tracker-github: GitHub Issues tracker via `gh` CLI — issue CRUD,
  completion check, branch naming (feat/issue-N), prompt generation,
  list/filter/update/create with label and assignee support.

- tracker-linear: Linear issue tracker via GraphQL API (LINEAR_API_KEY) —
  issue fetch, completion check, branch naming (feat/IDENTIFIER), prompt
  generation, list with team/state/label filters, state transitions via
  workflow state resolution, issue creation with teamId config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review — GraphQL injection, N+1 queries, timeouts, tests

- tracker-linear: use GraphQL variables in listIssues to prevent injection
- tracker-linear: add 30s HTTP timeout to linearQuery
- tracker-linear: fix issueUrl to use workspace slug from project config
- tracker-linear: add labels/assignee support to createIssue
- scm-github: rewrite getPendingComments to use GraphQL reviewThreads
  with real isResolved status instead of hardcoding false
- scm-github: simplify getAutomatedComments to single API call (N+1 fix)
- scm-github: add 30s timeout to gh CLI calls
- scm-github: fix parseDate to return epoch instead of fabricating dates
- scm-github: add repo format validation in detectPR
- scm-github: fix getCISummary to not count all-skipped as passing
- tracker-github: add 30s timeout to gh CLI calls
- tracker-github: fix createIssue to not use unsupported --json flag
- Add comprehensive vitest tests for scm-github and tracker-github

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address Codex review — assignee lookup, GraphQL vars, status checks

Iteration 1 fixes (8 issues from Codex review):
- tracker-linear: remove invalid assigneeDisplayName, resolve assignee
  by display name to ID via users query after creation
- tracker-linear: add HTTP status code check in linearQuery
- tracker-linear: throw on missing workflow state in updateIssue
- scm-github: use GraphQL variables ($owner, $name, $number) in
  getPendingComments instead of string interpolation
- scm-github: guard against empty comment nodes in review threads
- scm-github: use mergeStateStatus in getMergeability (BEHIND, BLOCKED)
- tracker-github: default description to "" in createIssue

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review + lint — error context, GraphQL vars, mergeability

- scm-github/tracker-github: wrap gh() errors with command context + cause
- scm-github: use GraphQL variables ($owner, $name, $number) in
  getPendingComments to prevent injection
- scm-github: guard against empty review thread nodes
- scm-github: incorporate mergeStateStatus (BEHIND/BLOCKED) in getMergeability
- tracker-linear: add identifier vs UUID comment in updateIssue
- Fix ESLint preserve-caught-error violations
- Remove unused mockGhRaw in scm-github tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add pagination to getAutomatedComments and increase thread limit

- getAutomatedComments: add --paginate flag to REST API call to fetch
  all review comments beyond the default 30-item first page
- getPendingComments: increase GraphQL reviewThreads limit from 100 to
  250 (GitHub's max for connections)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use per_page=100 instead of --paginate for JSON-safe pagination

Replace --paginate with -F per_page=100 in getAutomatedComments to
avoid concatenated JSON arrays that break JSON.parse on multi-page
responses. 100 is GitHub's max per_page value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: filter out resolved threads in getPendingComments

The method name implies it should only return unresolved/pending review
threads. The isResolved data was already fetched via GraphQL but was not
being filtered on, causing resolved threads to be included in results.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add comprehensive tracker-linear test suite (53 tests)

Covers all Tracker interface methods: getIssue, isCompleted, issueUrl,
branchName, generatePrompt, listIssues, updateIssue, createIssue.
Mocks node:https request to simulate Linear GraphQL API responses.
Tests state mapping, error handling, assignee/label resolution, and
GraphQL variable injection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address 6 bugbot review comments on PR #4

- GraphQL reviewThreads(first: 250) → first: 100 (GitHub max)
- noConflicts false when mergeable is UNKNOWN (not just CONFLICTING)
- updateIssue now handles labels and assignee (not just state/comment)
- Add res.on("error") handler in linearQuery to prevent crashes
- createIssue returns only actually-applied labels, not all requested
- Tests added/updated for all fixes (144 total across 3 plugins)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: make Linear updateIssue labels additive to match GitHub behavior

Linear's issueUpdate replaces all labels, while GitHub's --add-label is
additive. Now fetches existing label IDs first and merges with new ones
before sending to issueUpdate, ensuring consistent behavior across both
tracker implementations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: default listIssues to open state in tracker-linear

When no state filter is specified, tracker-github defaults to "open"
but tracker-linear was returning all issues. Now defaults to excluding
completed/canceled issues, matching tracker-github behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Composio SDK support to tracker-linear

Allow users with a COMPOSIO_API_KEY to use the Linear tracker without
a separate LINEAR_API_KEY. The plugin auto-detects which key is available
and routes through either the direct Linear GraphQL API or Composio's
LINEAR_RUN_QUERY_OR_MUTATION tool. All existing queries and response
parsing are reused via a GraphQLTransport abstraction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address 2 bugbot review comments on PR #4

1. getCIChecks now throws on error instead of silently returning [],
   preventing a fail-open where CI appears healthy when we can't fetch
   check status. getCISummary catches the error and returns "failing".

2. Handle GitHub's UNSTABLE mergeStateStatus (required checks failing)
   as a merge blocker in getMergeability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent unhandled promise rejection in Composio timeout race

When the timeout wins Promise.race in the Composio transport, the
resultPromise is left without a rejection handler. If the SDK call
later rejects, it becomes an unhandled promise rejection that crashes
Node.js 20+ with --unhandled-rejections=throw.

Attach no-op .catch() to both resultPromise and timeoutPromise before
the race so whichever promise loses has its rejection silently handled.

Also adds plugin integration tests (core -> real plugins -> mocked gh CLI).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: chain error cause in getCIChecks and fix core build

- getCIChecks catch now preserves the original error via { cause: err }
  instead of discarding it, matching the pattern used by the gh() helper
- Add tsconfig.build.json to core that excludes __tests__/ from build
  compilation, preventing TS5055 errors from circular workspace deps
  (integration tests import plugin packages that depend back on core)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove circular devDeps, address bugbot comments

- Remove plugin devDependencies from core/package.json that created a
  circular dependency (core -> plugins -> core), breaking CI build order
- Document in_progress state as intentional no-op in tracker-github
  updateIssue (GitHub Issues only supports open/closed)
- Remove @composio/core optional peerDependency from tracker-linear;
  the dynamic import() already handles the missing package gracefully,
  and the peer dep was pulling composio + transitive deps into lockfile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add real integration test for tracker-linear against Linear API

Creates a test that exercises the full tracker-linear plugin lifecycle
against the real Linear API — createIssue, getIssue, isCompleted,
listIssues, updateIssue (comment, close, reopen), generatePrompt,
branchName, issueUrl. Each run creates a throwaway test issue and
trashes it in cleanup. Skipped when LINEAR_API_KEY is not set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: pass LINEAR_API_KEY and LINEAR_TEAM_ID to integration tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: support both LINEAR_API_KEY and COMPOSIO_API_KEY in integration test

The tracker-linear integration test now runs with either credential:
- LINEAR_API_KEY: direct Linear API (full cleanup via trash)
- COMPOSIO_API_KEY: via Composio SDK (cleanup falls back to closing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: don't pass COMPOSIO_API_KEY to CI integration tests

When both LINEAR_API_KEY and COMPOSIO_API_KEY are set, the plugin
prefers the Composio transport which requires @composio/core SDK.
The SDK isn't installed in CI, so use the direct LINEAR_API_KEY
transport which has no extra dependencies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use import.meta.url in vitest config, default createIssue description

- Replace __dirname with import.meta.url-derived dirname in ESM config
- Default createIssue description to "" for defensive safety

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: map CANCELLED and ACTION_REQUIRED CI conclusions to failed

Terminal check conclusions (CANCELLED, ACTION_REQUIRED) were falling
through to "pending", causing the lifecycle manager to wait forever.
Also changed the default else branch to "failed" (fail-closed).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 15:45:51 +05:30
prateek 925a7aae92
feat: implement runtime and workspace plugins (tmux, process, worktree, clone) (#2)
* feat: implement runtime and workspace plugins (tmux, process, worktree, clone)

Implement all 4 runtime/workspace plugins for the agent orchestrator,
replacing the stub files with full implementations of the Runtime and
Workspace interfaces from @agent-orchestrator/core.

- runtime-tmux: tmux session lifecycle with busy detection, wait-for-idle
  message delivery, capture-pane output, and load-buffer for long messages
- runtime-process: child process management with rolling output buffer,
  graceful SIGTERM/SIGKILL shutdown, and stdin message delivery
- workspace-worktree: git worktree create/destroy/list with symlink support
  for shared resources and postCreate hook execution
- workspace-clone: git clone --reference for fast isolated clones with
  postCreate hooks

Closes INT-1328

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address all PR review comments on runtime/workspace plugins

Fixes for all 13 review issues:

1. Move processes Map inside create() for per-instance isolation (#1)
2. Wrap stdin.write in promise with error/backpressure handling (#2)
3. Use child.once("exit") instead of on("exit") to prevent leaks (#3)
4. Use child.once("exit") in destroy() timeout to prevent leaks (#4)
5. Add assertValidSessionId() with /^[a-zA-Z0-9_-]+$/ regex (#5)
6. Single capture-pane call in isBusy() to avoid TOCTOU (#6)
7. Throw error when sendMessage sends to busy session after timeout (#7)
8. Use crypto.randomUUID() for temp file names to avoid collisions (#8)
9. Document that postCreate commands run with full shell privileges (#9)
10. Inspect worktree add error — only retry on "already exists" (#10)
11. Delete orphaned branch after worktree remove in destroy() (#11)
12. Log warning for corrupted clones in list() instead of silent skip (#12)
13. Return "not running" attach info for exited processes (#13)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: harden runtime/workspace plugins (spawn errors, stdin races, git injection)

- runtime-process: catch spawn errors with setImmediate pattern, prevent
  double resolve/reject in sendMessage with done-flag, add late error
  handler to prevent unhandled crashes, eliminate non-null assertions
- runtime-tmux: guard config.environment with ?? {} fallback
- workspace-worktree: add -- separator to git checkout/branch commands
  to prevent option injection, guard branch deletion to feature branches
  only (must contain /), ensure parent dirs exist before symlink creation
- workspace-clone: add -- separator to git checkout commands, suppress
  no-console lint for expected diagnostic warning

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address codex review — spawn race, path traversal, temp file perms

- runtime-process: replace setImmediate with spawn/error event listeners
  to fix race where create() returns handle for failed process; document
  intentional shell:true usage
- runtime-tmux: restrict temp file permissions to 0o600
- workspace-worktree: validate projectId/sessionId as safe path segments,
  reject absolute/traversal symlink paths, verify resolved targets stay
  within workspace
- workspace-clone: validate projectId/sessionId as safe path segments
  in both create() and list()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address all review comments and fix CI

- workspace-clone/worktree: remove incorrect -- from git checkout
  (switches to path mode, breaking branch operations)
- workspace-worktree: fix prefix collision in list() by appending /
  to startsWith check (foo no longer matches foobar)
- runtime-tmux: use named tmux buffers (-b flag) to prevent concurrent
  sendMessage calls from overwriting each other's paste content
- runtime-process: check signalCode in addition to exitCode for
  accurate liveness detection of signal-killed processes
- runtime-process: reject duplicate session IDs to prevent orphaned
  child processes from overwritten map entries
- runtime-tmux: anchor $ in busy detection to line start (^\$\s) to
  avoid false idle detection from dollar signs in output

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: forward plugin config through registry so workspace dirs take effect

The plugin registry was calling plugin.create() with no arguments,
silently ignoring configured worktreeDir/cloneDir settings. Added
extractPluginConfig() to map orchestrator config to per-plugin config,
and updated register()/loadBuiltins() to pass config through.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: clean up orphaned worktrees/clones on checkout failure, remove risky branch -D

- Worktree: remove git branch -D from destroy() — the "/" heuristic
  could delete pre-existing local branches unrelated to the workspace
- Worktree: wrap fallback checkout in try/catch, clean up orphaned
  worktree if checkout fails
- Clone: wrap fallback checkout in try/catch, rmSync the orphaned clone
  directory if both checkout attempts fail

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: clean up partial clone directory when git clone fails

Wrap the git clone call in try/catch so a failed clone removes any
partial clonePath left on disk, preventing orphaned directories that
block retries for the same sessionId.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: remove busy detection from tmux runtime

Busy/idle detection is an agent-layer concern, not a runtime concern.
Each agent (Claude Code, Codex, Aider) has its own native mechanism for
checking activity status. The tmux runtime should only manage sessions
and send messages immediately without polling.

Removed: isBusy() heuristics, sleep-based polling loop, sentWhileBusy
error. sendMessage() now sends immediately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: clean up tmux session when launch command send-keys fails

If send-keys fails after new-session succeeds, the orphaned tmux
session was left running and unmanaged. Now wraps send-keys in
try/catch that kills the session before rethrowing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent clone error handler from deleting pre-existing workspace

Add early existence check before git clone so create() fails
immediately if clonePath already exists, rather than cloning into it,
failing, and then deleting the pre-existing workspace in the catch block.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: clean up leaked tmux buffer when paste-buffer fails

The -d flag on paste-buffer only deletes the named buffer on success.
If paste fails, the buffer persists in the tmux server. Added
delete-buffer in the finally block to ensure cleanup regardless of
paste outcome.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent orphan processes on duplicate sessionId, auto-remove exited sessions

- Move processes.has() check before spawn() so duplicate sessionIds are
  rejected before any child process is created
- Auto-remove exited sessions from the process map in the exit handler
  so the sessionId can be reused without manual destroy()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: register process in map before exit handler to prevent stale entries

A fast-exiting child could fire the exit event before processes.set()
ran, causing delete() to no-op and set() to insert a permanently stale
entry. Moving set() before the exit handler registration ensures
delete() always finds the entry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: atomic session reservation and immediate exit handler registration

- Reserve map slot synchronously (no await gap between has() and set())
  so concurrent create() calls cannot both pass the duplicate check
- Register exit handler immediately after spawn(), before any await,
  so fast-exiting processes cannot miss cleanup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: kill process group on destroy, use literal mode for tmux send-keys

- runtime-process: use process.kill(-pid) to kill the entire process
  group, not just the shell. Added detached:true so child gets its own
  process group. Falls back to child.kill() if group kill fails.
- runtime-tmux: add -l flag to send-keys for short messages so text
  like "Enter" or "Space" is sent literally, not as key names.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add comprehensive unit and integration tests for all 4 plugins

Add 136 unit tests and 35 integration tests covering runtime-tmux,
runtime-process, workspace-worktree, and workspace-clone plugins.
Also adds 13 unit tests for the core plugin-registry.

Unit tests (136 total):
- runtime-tmux (25): create/destroy, send-keys modes, getOutput, isAlive, getMetrics
- runtime-process (40): spawn lifecycle, SIGKILL escalation, output buffering, isolation
- workspace-worktree (40): create/destroy, tilde expansion, branch fallback, symlinks
- workspace-clone (31): create/destroy, --reference clone, pre-existence check, cleanup

Integration tests (35 total):
- runtime-tmux (8): full lifecycle with real tmux sessions
- runtime-process (11): full lifecycle with real child processes
- workspace-worktree (8): real git worktree operations
- workspace-clone (8): real git clone operations

Plugin-registry tests (13):
- register/get/list, config forwarding, slot isolation, loadBuiltins, loadFromConfig

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: guard against null process in runtime-process methods

ProcessEntry.process is null between reservation and spawn completion.
Add explicit null checks in destroy(), sendMessage(), isAlive(), and
getAttachInfo() to prevent TypeError if called during that window.

Addresses bugbot review comment about transient null process crash.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: buffer partial lines in runtime-process output capture

Stream chunks split at arbitrary boundaries, so splitting on "\n" and
pushing each piece as a complete line corrupts output when a line spans
two chunks. Buffer the trailing partial line and only push complete
(newline-terminated) lines. Flush remaining partial on process exit.

Addresses bugbot review comment about incorrect output chunk splitting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: correct vitest peer dep resolution in pnpm-lock.yaml

The lockfile referenced vitest@3.2.4 without jiti/jsdom/lightningcss
peer deps, but that resolution didn't exist in the packages section.
Update to match the full resolution available on main.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use per-stream partial buffers in runtime-process output capture

stdout and stderr shared one partial-line buffer, so interleaved chunks
from different streams could be concatenated into corrupted lines. Each
stream now gets its own closure with an independent partial buffer.

Addresses bugbot review comment about mixed stream chunk corruption.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 15:13:57 +05:30
prateek e450fed462
fix: upgrade @testing-library/jest-dom for vitest 2.x type compatibility (#21)
The v6.6.0 module augmentation for vitest's Assertion interface was
broken with vitest 2.x. Upgrading to v6.9.1 fixes the ~47 typecheck
errors (toBeInTheDocument, toHaveAttribute). Also adds vitest.d.ts to
ensure the type augmentation is included in tsc compilation.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 14:40:44 +05:30
prateek 343c8a65b5
feat: implement web dashboard with attention-zone UI and API routes (#1)
* 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>
2026-02-14 14:26:59 +05:30
prateek 7dd7de6249
fix: address Bugbot followup — stale paths in JSDoc, unused exports (#20)
- Remove developer-specific paths from JSDoc comments in integration tests
- Remove unused sessionExists and capturePane helper exports

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 13:30:25 +05:30
prateek 4b6d62d142
feat: agent plugins, OpenCode plugin, integration tests, CI (#5)
* feat: implement agent plugins (claude-code, codex, aider)

Implement the Agent interface from @agent-orchestrator/core for three
AI coding tools, enabling the orchestrator to launch, detect activity,
and introspect agent sessions.

Claude Code plugin (full implementation):
- JSONL session introspection (summary, session ID, cost, last message type)
- Activity detection via terminal output pattern matching
- Process detection via tmux pane TTY → ps lookup chain
- Launch command generation with permissions/model/prompt support

Codex and Aider plugins (functional stubs):
- Launch command generation with tool-specific flags
- Process detection via TTY lookup
- Stub introspection (no JSONL support yet)

Closes INT-1329

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review — shell escaping, activity detection, tests

- Replace JSON.stringify with POSIX shell escaping (single quotes) for
  all agent launch commands to prevent command injection
- Remove `unset CLAUDECODE &&` shell syntax from getLaunchCommand (breaks
  execFile); moved to getEnvironment as empty string
- Fix detectActivity returning "exited" for empty pane when process is
  confirmed alive — now returns "idle"
- Tighten BLOCKED_PATTERNS to specific error framing (^Error:, ENOENT:,
  APIError:, etc.) to avoid false positives on code output
- Check blocked patterns before input patterns to prevent "EACCES:
  permission denied" matching INPUT_PATTERNS on the word "permission"
- Remove pgrep fallback in findClaudeProcess (could match wrong session)
- Fix extractCost double-counting: prefer costUSD, fall back to
  estimatedCostUsd only when costUSD is absent
- Trim ACTIVE_PATTERNS to stable indicators only
- Add comprehensive vitest tests (127 tests across 3 plugins)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address Codex review + lint fixes

- Use `ps -eo pid,tty,args` instead of `comm` for process detection so
  CLI wrappers (node, python) are correctly identified
- Coerce PID from handle.data with Number() + isFinite() to handle
  string-serialized PIDs
- Fix token double-counting: prefer `usage` object, only fall back to
  flat `inputTokens`/`outputTokens` when `usage` is absent
- Add `--` before Codex prompt to prevent prompts starting with `-`
  from being parsed as CLI flags
- Check blocked patterns before input patterns so "EACCES: permission
  denied" isn't misclassified as waiting_input
- Fix lint: merge duplicate imports, strict equality, remove non-null
  assertions
- Format with prettier

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: multi-pane tmux, EPERM handling, strict process matching

Codex review iteration 1 fixes:
- Iterate all tmux pane TTYs instead of only the first — prevents
  false "exited" in multi-pane sessions
- Handle EPERM from process.kill(pid, 0) as "process exists" — EPERM
  means the process is alive but we lack permission to signal it
- Use word-boundary regex for process name matching — prevents false
  positives on similar names like "claude-code" or paths containing
  the substring
- Add tests for EPERM handling, multi-pane detection, and strict
  name matching (134 tests total)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: exclude next-env.d.ts from eslint

Auto-generated Next.js type file triggers triple-slash-reference rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address Bugbot review — JSON.parse guard, detectActivity pattern priority

- Guard parseJsonlFile against non-object JSON values (null, arrays,
  primitives) that would cause TypeError in downstream extractors
- Split null output (non-tmux → "active") from empty output (tmux → "idle")
  so non-tmux runtimes don't falsely report idle
- Reorder pattern matching: idle before blocked so stale errors in the
  tmux scrollback don't mask an idle prompt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract shellEscape to @agent-orchestrator/core

Move the duplicated POSIX shell-escaping utility from all three agent
plugins into packages/core/src/utils.ts and re-export from the package
entry point. Plugins now import { shellEscape } from the shared package.

Addresses Bugbot review comment on PR #5.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add execFileAsync timeouts, use satisfies for plugin exports

- Add { timeout: 30_000 } to all execFileAsync calls (tmux, ps) per
  CLAUDE.md convention to prevent hanging on stuck processes
- Replace `const plugin: PluginModule<Agent>` with inline
  `export default { ... } satisfies PluginModule<Agent>` to preserve
  narrow literal types on manifest fields

Addresses Bugbot review comments on PR #5.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: rename introspect → getSessionInfo, detectActivity uses JSONL

- Rename Agent.introspect() → getSessionInfo(), AgentIntrospection → AgentSessionInfo
- Claude Code detectActivity: replace tmux screen-scraping with JSONL-based
  detection (file mtime + last entry type)
- Remove terminal pattern constants (ACTIVE_PATTERNS, IDLE_PATTERNS, etc.)
- Update all tests: JSONL-based activity tests, renamed introspect blocks
- waiting_input/blocked states deferred to hooks-based implementation (#16)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add OpenCode agent plugin, integration tests, and CI workflow

- Add agent-opencode plugin with getLaunchCommand, detectActivity,
  isProcessRunning, getSessionInfo (27 unit tests)
- Add integration test suite for all 4 agents (claude-code, codex,
  aider, opencode) using real binaries in tmux sessions
- Add GitHub Actions CI workflow for integration tests
- Add test:integration script to root package.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: update core test mocks — introspect → getSessionInfo + isProcessing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address Bugbot review — Windows paths, bytesRead, hardcoded paths

- toClaudeProjectPath: handle Windows backslashes and drive letters
- readLastJsonlEntry: use bytesRead to avoid null character corruption
- Remove duplicate next-env.d.ts eslint ignore entry
- Remove hardcoded developer-specific binary paths from integration tests
- Add isProcessing limitation comments with issue references (#17-19)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 11:28:42 +05:30
prateek 134bc6e177
Merge pull request #3 from ComposioHQ/feat/INT-1327
feat: implement core services (metadata, event-bus, tmux, session-manager, lifecycle-manager)
2026-02-14 07:24:07 +05:30
Prateek f2e0ac1867 fix: filter invalid session IDs in listMetadata to prevent downstream crashes
listMetadata() now validates filenames against VALID_SESSION_ID regex
before returning them, preventing readMetadataRaw() from throwing on
unexpected files in the sessions directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 06:30:13 +05:30
Prateek 660b8bb13d fix: restrict temp file permissions, validate session prefix format
- tmux sendKeys: write temp files with mode 0o600 instead of default umask
- config: validate sessionPrefix matches [a-zA-Z0-9_-]+ (Zod schema)
- config: sanitize derived prefix from project ID to match metadata ID rules

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 05:17:53 +05:30
Prateek 4028e5b77d fix: suppress immediate notification when send-to-agent reaction handles event
When a send-to-agent reaction was configured (e.g. for ci-failed), the
reactionHandledNotify flag stayed false, causing checkSession() to also
call notifyHuman() immediately — bypassing the retry/escalation workflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 04:07:38 +05:30
Prateek 9e7a767730 fix: notify on significant transitions, preserve stuck state on probe failure
Two bugs fixed:

1. Transitions with non-info priority (e.g. merge.completed,
   review.approved, session.errored) now call notifyHuman() directly
   when no reaction config handles notification. Before, these
   transitions updated state silently with no human notification.

2. When agent.detectActivity() throws, sessions in stuck/needs_input
   state now preserve that state instead of being coerced to "working"
   by the fallback at the bottom of determineStatus().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 03:55:19 +05:30
Prateek 95fd47ef2a refactor: remove EventBus, simplify lifecycle manager
The event bus was over-engineered for a system with ~50 sessions. The
dashboard can read metadata files + poll GitHub directly. Remove the
EventBus interface, event-bus.ts module, and all emit/subscribe wiring
from session-manager and lifecycle-manager. The reaction engine (the
valuable part) stays — executeReaction now takes sessionId/projectId
directly instead of going through OrchestratorEvent.

Also fix listMetadata() to filter out directories (not just the
"archive" dir by name) so readMetadataRaw() can't crash on non-file
entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 03:38:11 +05:30
Prateek 4c47a42b6e fix: use literal mode for tmux send-keys, prune stale lifecycle trackers
- Add -l flag to tmux send-keys for short messages so key-like payloads
  (e.g. "Enter", "Escape") are sent as text, not interpreted as keypresses
- Prune states and reactionTrackers maps in pollAll() for sessions that
  no longer appear in the session list, preventing unbounded memory growth
  and stale retry counters from applying to reused session IDs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 21:09:49 +05:30
Prateek 07b3a553f7 fix: allow notify reactions when auto is false, clean up reserved IDs on spawn failure
- Reactions with action: "notify" bypass the auto: false gate so
  merge-ready notifications fire for approved-and-green config
- All spawn failure paths now delete the reserved session metadata file

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 21:00:58 +05:30
Prateek 65787f08e3 fix: use handle.runtimeName for plugin lookup, atomic session ID reservation
- kill() and send() prefer handle.runtimeName over project defaults
- spawn() uses O_EXCL atomic file creation to prevent concurrent ID collisions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 20:41:11 +05:30
Prateek a225bc17f5 fix: runtime plugin lookup, post-create guard, restart transition detection
- kill() uses handle.runtimeName to find the correct runtime plugin
- postCreate cleanup skips destroy when workspacePath is project root
- checkSession reads persisted metadata status on first poll after restart

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 19:16:50 +05:30
Prateek 88ea7d1491 fix: prevent project workspace deletion and agent config workspace leak
- kill() skips workspace.destroy when worktree matches project.path
- spawn() wraps getLaunchCommand/getEnvironment in workspace cleanup guard

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:55:07 +05:30
Prateek ad0b45006c fix: session recovery, reaction config merge, summary priority
- stuck/needs_input sessions recover to working when agent is active
- Project reaction overrides merge with global defaults (not replace)
- summary.all_complete events get info priority (not action)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:45:33 +05:30
Prateek 24391c874e fix: ignore next-env.d.ts in ESLint config
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:42:45 +05:30
Prateek f6f5e24e3c fix: add missing cleanup status to VALID_STATUSES set
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:42:45 +05:30
Prateek 4a804503b6 fix: execute all-complete reaction and guard workspace postCreate
- summary.all_complete reactions now execute in pollAll() after emission
- workspace.postCreate errors trigger workspace cleanup to prevent leaks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:42:45 +05:30
Prateek 2ecb011982 fix: address all review comments, lint/format, bugbot issues
Review fixes:
- metadata: sessionId validation to prevent path traversal
- event-bus: guard appendFileSync, remove unused import
- lifecycle-manager: fix reaction retry counting (track attempts,
  clear on status transition), allow retry on send failure instead
  of immediate escalation, detect killed sessions in polling loop,
  map session.killed to agent-exited reaction
- session-manager: validate status against union, clean up runtime
  and workspace on spawn failure, kill with default plugins when
  project config is missing

Lint/format fixes:
- Remove unused imports across all test files
- Replace `Function` type with typed callback in tmux tests
- Replace require() with dynamic import() in tests
- Replace dynamic delete with object spread in metadata
- Apply prettier formatting to all files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:42:45 +05:30
Prateek c49649c2c1 fix: address codex review — path traversal, retry logic, type safety
- metadata: validate sessionId against ^[a-zA-Z0-9_-]+$ to prevent
  path traversal attacks
- lifecycle-manager: fix reaction retry/escalation to count attempts
  (not just successes), escalate after maxRetries attempts; map
  session.killed to agent-exited reaction (was unreachable session.exited)
- session-manager: validate status against known SessionStatus union
  (default to "spawning" for unknown values); clean up workspace on
  runtime creation failure to prevent resource leaks
- event-bus: guard appendFileSync in try/catch so disk errors don't
  crash event emitters

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:42:45 +05:30
Prateek d6ec95402d fix: address PR review — unsafe casts, tmux race, concurrent polling
- session-manager: replace readMetadata with readMetadataRaw (no unsafe casts),
  add safeJsonParse for JSON.parse guards, escapeRegex for session prefix,
  explicit branch priority, convert from this-based to closure-based methods
- tmux: named buffers (load-buffer -b / paste-buffer -b -d) to prevent
  global paste buffer race on concurrent sendKeys; send Escape before text
  to clear partial input
- lifecycle-manager: concurrent polling with Promise.allSettled instead of
  sequential for-of await, allCompleteEmitted guard to prevent repeated
  summary.all_complete events, polling re-entrancy guard, safe reaction
  config handling with action/auto checks
- tests: update tmux tests for Escape-first and named buffer changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:42:45 +05:30
Prateek 2b9a28a031 test: add comprehensive unit tests for all core services
Add vitest test suite with 103 tests across 5 test files:

- metadata.test.ts (19 tests): read/write/update/delete/list, key=value
  parsing, comments, values with equals signs, archiving, dotfile exclusion
- event-bus.test.ts (22 tests): emit/on/off, wildcard listeners, handler
  error resilience, priority inference, JSONL persistence round-trip,
  filtered history with sessionId/projectId/type/priority/since/limit
- tmux.test.ts (23 tests): all tmux wrappers with mocked child_process,
  session listing/parsing, send-keys short vs load-buffer long text,
  capture-pane, kill, getPaneTTY
- session-manager.test.ts (23 tests): spawn pipeline with mocked plugins,
  session numbering, branch derivation, event emission, metadata writing,
  list with dead runtime detection, kill with cleanup, send via runtime
- lifecycle-manager.test.ts (16 tests): state transitions (spawning→working,
  killed, stuck, needs_input, ci_failed, merged, mergeable), reaction
  triggering, auto=false suppression, on/off handlers, getStates isolation

Also fixes inferPriority to handle merge.completed as "action" priority.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:42:45 +05:30
Prateek de86054b9d feat: implement core services (metadata, event-bus, tmux, session-manager, lifecycle-manager)
Implement the 5 core service modules in packages/core/src/:

- metadata.ts: Flat-file key=value session metadata read/write matching
  existing bash script format. Supports read, write, update, delete
  (with archive), and list operations.

- event-bus.ts: In-process pub/sub with JSONL append-only persistence.
  Supports typed and wildcard listeners, priority inference from event
  type, filtered history queries, and a createEvent() helper.

- tmux.ts: Async wrappers around tmux commands using child_process.execFile
  (no shell injection). Handles long/multiline messages via load-buffer/
  paste-buffer. Covers list, create, send-keys, capture-pane, kill, and
  pane TTY discovery.

- session-manager.ts: Session CRUD that orchestrates Runtime + Agent +
  Workspace plugins. Full spawn pipeline (workspace → runtime → agent),
  list with live runtime checks, kill with cleanup, batch cleanup checking
  PR state + issue state + runtime liveness, and message delivery.

- lifecycle-manager.ts: State machine per session with configurable polling
  interval. Detects status transitions, emits events, triggers configured
  reactions (send-to-agent, notify, auto-merge) with retry counting and
  time-based escalation. Routes notifications by priority to configured
  notifier plugins.

All services implement interfaces defined in types.ts. Updated index.ts
to export all new modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:42:45 +05:30
prateek 0c535c98d7
fix: CI handles Next.js build separately, fix web tsconfig (#14)
* fix: CI workflow handles Next.js build separately, fix web tsconfig rootDir

- Remove rootDir from web tsconfig (conflicts with Next.js generated .next/types)
- Include .next/types/**/*.ts in web tsconfig
- CI: build non-web packages first, web build allowed to soft-fail
- CI: typecheck excludes web (typechecked by next build)
- CI: test excludes web build dependency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove || true from web build — bugbot caught it, web errors should fail CI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: override rootDir in web tsconfig to "." for Next.js compat

Next.js generates .next/types/ files that must be under rootDir.
Base tsconfig sets rootDir to "src", web needs "." to include both
src/ and .next/types/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:41:58 +05:30
Prateek c8061ce03f chore: add ESLint, Prettier, CI workflow, and comprehensive CLAUDE.md conventions
- ESLint flat config with typescript-eslint strict rules
- Prettier config (double quotes, semicolons, 2-space indent)
- GitHub Actions CI: lint + typecheck + test on PRs
- Cursor BugBot config (.cursor/BUGBOT.md)
- Comprehensive CLAUDE.md with code conventions, security rules,
  plugin patterns, and common mistakes to avoid
- Fix unused param lint error in plugin-registry.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:01:52 +05:30
Prateek 5058c409d5 feat: scaffold TypeScript monorepo with all plugin interfaces
Phase 0 complete. Establishes:
- pnpm workspace with 18 packages (core, cli, web, 15 plugins)
- Complete type definitions in packages/core/src/types.ts defining
  all 8 plugin slot interfaces (Runtime, Agent, Workspace, Tracker,
  SCM, Notifier, Terminal) + core service interfaces
- YAML config loader with Zod validation and sensible defaults
- Plugin registry with built-in discovery
- CLAUDE.md with conventions for spawned agents

All agents can now branch from main and implement their assigned
packages against the interfaces defined in types.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 17:02:42 +05:30
Prateek 36b0792eb6 docs: add parallel implementation plan with 7-agent work breakdown
Maps dependency graph, identifies 7 truly independent work streams
after Phase 0 (scaffold + types), with Linear tickets and spawn
commands ready to go.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 16:41:06 +05:30
Prateek 3eecca8460 docs: update architecture with push-first notification model
The human never polls. The system notifies the human. Two-tier event
handling: auto-handle routine issues silently, push notifications only
when human judgment is required. Escalation chains, priority-based
routing, and the dashboard is a drill-down tool, not the primary interface.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 16:38:00 +05:30
Prateek 74001b22d0 docs: add competitive research and architecture design artifacts
Comprehensive analysis of 16+ competing agent orchestration tools
(Gas Town, OpenHands, SWE-ReX, Par, claude-flow, etc.) and detailed
architecture design with 8-slot plugin system, session lifecycle state
machine, and human attention optimization patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 16:35:46 +05:30
Prateek 1601ad75c0 feat: add agent-orchestrator (ao) as a self-hosting project
- Create claude-ao-session: worktree-based session manager for ao
  (Linear tickets, GitHub PRs, AO_SESSION env var)
- Add ao|agent-orchestrator case to all shared scripts:
  claude-batch-spawn, claude-spawn, claude-status, claude-open-all,
  claude-review-check, claude-bugbot-fix
- Rewrite CLAUDE.orchestrator.md with full self-hosting instructions:
  commands reference, workflows, architecture, tips
- Session prefix: ao, metadata: ~/.ao-sessions/, worktrees: ~/.worktrees/ao/

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-13 15:44:17 +05:30
Prateek c16103fb4d rename CLAUDE.local.md to CLAUDE.orchestrator.md
This file contains the shared orchestrator instructions that ship with
the repo. It's loaded explicitly via --append-system-prompt-file when
launching an orchestrator session, rather than auto-discovered as a
CLAUDE.local.md. Users can still have their own CLAUDE.local.md for
personal overrides.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-13 15:34:24 +05:30
Prateek 0273e8f3d0 feat: initial commit with orchestrator scripts and dev instructions
Import all 18 production orchestrator scripts from ~/. These are direct
copies with hardcoded paths — generalization is the next step. Includes
CLAUDE.local.md with project overview, architecture notes, and roadmap.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-13 15:25:31 +05:30