13 Commits
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
9ac659066a
|
revert: remove model rate-limit pause functionality (PR #367) (#908)
* Revert "fix: pause workers on model limits and stabilize session visibility (#367)"
This reverts commit
|
|
|
|
844c97cb63 | minor fix | |
|
|
124cc33745 | perf(cli): parallelize I/O calls in 'ao session ls' for faster listing | |
|
|
1d960feb6b
|
fix: protect orchestrators from session cleanup (#453)
* fix: harden orchestrator cleanup selection Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix: suppress orchestrators in cleanup command output Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix: remove unreachable cleanup guard branch Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> --------- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> |
|
|
|
650d3ad695
|
fix(core): enforce single-owner PR claim consolidation (#390)
* fix(core): enforce single-owner PR claim consolidation * refactor: remove dead `takeover` option from claimPR Since PR consolidation is now automatic (single-owner enforcement), the `--takeover` flag became dead code. Users passing it got no error but it had zero effect. This commit: - Removes takeover from ClaimPROptions interface - Removes --takeover flag from spawn and session CLI commands - Updates orchestrator-prompt.ts examples - Updates tests to reflect automatic consolidation Tests: ao-core 403 passing, ao-cli 189 passing (spawn+session) * fix: remove stale --takeover from Quick Start example Remove remaining --takeover reference in orchestrator prompt Quick Start section. * feat(core): document and test asymmetric PR ownership model - Add JSDoc to claimPR documenting RULE A (exclusive PR->Agent) and RULE B (Agent->Many PRs) ownership contract - Add tests for: - Same session claiming multiple PRs sequentially (RULE B) - Idempotent re-claim by same owner - Stale/dead prior owner handoff - Exclusive PR ownership enforcement (RULE A) 139 tests passing --------- Co-authored-by: Harsh <harsh@Ubuntu-24-Forrest.lan> |
|
|
|
003eb78adb
|
fix: pause workers on model limits and stabilize session visibility (#367)
* fix: pause workers on model limits and stabilize session visibility Detect model limit exhaustion, pause project worker operations until reset, and expose pause state in dashboard/API. Also harden killed-session cleanup and SSE session reconciliation so sessions do not appear ghost-active or disappear until reload. * fix: satisfy lint in rate-limit pause probe * fix: address bugbot feedback for pause handling * fix(lifecycle): prevent infinite re-pause loop for duration-based rate limits Duration-based rate limits (e.g., 'usage limit reached for N hours') were causing infinite re-pause loops because they always calculate reset time as Date.now() + duration, which extends the pause on every poll cycle if the message remains in terminal output. Now checks for existing active pause before setting a new one: - Skips override if same session already has active pause - Preserves longer pauses from other sessions Fixes infinite loop described in PR #367 review comment. * fix(core): export global pause constants to prevent duplication Export GLOBAL_PAUSE_*_KEY constants and parsePauseUntil utility from @composio/ao-core so web package can import them instead of hardcoding. This prevents silent breakage if key values ever change - now there's a single source of truth. Addresses review comment on PR #367. * fix(web): globalPause as first-class state in SSE event flow globalPause is now part of the same reducer/event flow as sessions, not derived from provider-specific output text in the UI. Changes: - useSessionEvents: manage globalPause alongside sessions in reducer state - Dashboard: consume globalPause from hook instead of SSR-only prop - types: re-export GlobalPauseState from shared lib (provider-agnostic contract) - Tests: 15 new tests proving banner appears/disappears from state updates alone, regardless of agent model/plugin (Claude Code, OpenCode, Codex) Design requirements satisfied: - First-class state in same reducer/event flow as sessions - Key names sourced from shared core contract via export/import - Provider-neutral: no Anthropic/OpenAI string coupling in control logic - State-driven: banner visibility from reducer state updates via SSE Addresses Bugbot finding: Dashboard pause banner never updates after initial render (eba24a0b-9e4c-47e3-91c9-7d10be01e3cf) * fix: remove unused import in test file * fix(cli): consistent purge default for session kill and stop commands The kill method's default changed from opt-in (=== true) to opt-out (!== false). Both session kill and stop commands now use the same logic: purgeOpenCode = opts.purgeSession === true ? true : opts.keepSession !== true This ensures consistent behavior across all kill paths. Adds --keep-session flag to both commands. Adds regression tests for provider-agnostic behavior verified. Addresses Bugbot finding: orchestrator start cleanup inconsistent with new purge default (2bc2535f-b2d1-4f64-96d6-150f97ed8564) |
|
|
|
4e2144d99e
|
feat: OpenCode session lifecycle and CLI controls (#315)
* feat: refine OpenCode session reuse strategy and cleanup * fix: harden OpenCode session selection and lint errors * refactor: centralize OpenCode reuse resolution flow * fix: return 404 for missing session in message route * feat: replace force remap with terminal reload control * fix: protect project path from session kill cleanup * fix: preserve fullscreen alignment without reload action * fix: harden OpenCode session id handling and title reuse selection * fix: show OpenCode reload control and remap before restart * fix: preserve title-only OpenCode reuse with fallback mapping persistence * fix: resolve remaining PR315 Bugbot findings * fix: keep OpenCode discovery title-based without timestamp sorting * fix: avoid enrichment race fallout in session listing * fix: guard OpenCode discovery parse with array check * fix: stabilize Linear comment integration check * fix: harden OpenCode discovery and prompt option flow * ux: clarify OpenCode terminal restart action * fix: remap OpenCode session fresh on each restart * fix: validate remap session ids before reuse * fix: clean archived metadata only after purge * docs: align OpenCode remap selection with title-based behavior * fix: harden opencode cleanup and ignore local sisyphus state * test: add timeout cleanup coverage for session enrichment * fix: harden Linear integration helper against transient non-JSON errors * fix: make linear integration assertions resilient to eventual consistency * fix: remove unused fs import after rebase * fix: address remaining Bugbot blockers for opencode session handling * fix: avoid stale metadata overwrite during restore post-launch * fix: forward subagent in orchestrator flows and defer reuse lookup * fix: apply configured subagent fallback for session spawn * fix: scope archived cleanup by project and delay archive restore write * fix: normalize orchestrator strategy aliases in start display logic * fix: centralize orchestrator strategy normalization in core * fix: derive orchestrator reuse display from spawn result * fix: keep cleanup results consistent across project-id collisions * fix: namespace cleanup results when session IDs collide * fix: harden GitHub issue stateReason fallback Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix: avoid false failing CI state mapping Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix: add tmux command timeouts Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix: bound session API enrichment latency Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix: repair scm-github merge resolution Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix: repair lifecycle-manager test merge Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix: delay archive metadata recreation until restore passes Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * test: restore claim-pr session mocks Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix: address review findings for OpenCode lifecycle PR - Fix stripControlChars to preserve newlines for reload commands - Add SessionNotFoundError and use instanceof checks in API routes - Document orchestratorSessionStrategy in YAML example - Validate existingSessionId with asValidOpenCodeSessionId() - Extract inline Node script to buildSessionLookupScript helper - Create OpenCodeSessionManager interface for remap capability - Create OpenCodeAgentConfig type for agent-specific config - Change default orchestratorSessionStrategy from delete to reuse Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: guard reused session display without metadata * chore: add agent config files to .gitignore Agent configuration files (CLAUDE.md, AGENTS.md, IMPROVEMENTS.md, etc.) are personal and project-specific. They should not be committed to the repository. Changes: - Remove CLAUDE.md from git tracking - Add agent config files to .gitignore - Create .gitignore-template for reference Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: update gitignore for agent config folder structure Reorganized agent configuration files: - CLAUDE.md and AGENTS.md stay in root (agents read them there) - Tracking files move to .opencode/ (IMPROVEMENTS.md, etc.) - Optional Claude files in .claude/ Updated .gitignore to ignore folders instead of individual files. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: tighten opencode remap and session discovery safeguards * fix: address Bugbot findings in session manager * fix: scope opencode discovery to opencode sessions * fix: restore concurrent listing and strict permission literals * fix: throw SessionNotFoundError, parallelize list enrichment, fix permissions type - Session manager now throws SessionNotFoundError instead of plain Error for missing sessions, so web API routes correctly return 404 (not 500) - Parallelize session enrichment in list() — was sequential, causing O(N) latency for N sessions with subprocess enrichment - Fix AgentLaunchConfig.permissions type to accept legacy "skip" value (AgentPermissionInput instead of AgentPermissionMode) - Add happy-path and validation tests for /api/sessions/:id/message route - Update all test mocks to use SessionNotFoundError Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: route ao send through session manager * feat: add purge option to orchestrator stop * fix: register opencode agent in web services * test: align web API missing-session coverage * fix: match notifier config by plugin name * refactor: dedupe session lookup and tmux buffer send flow * test: update send lifecycle wait expectation * fix: harden send routing and cleanup purge controls --------- Co-authored-by: Harsh <harsh@Ubuntu-24-Forrest.lan> Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Prateek <karnalprateek@gmail.com> |
|
|
|
8801502871
|
feat: add PR claim flow for agent sessions (#326)
* feat: add PR claim flow for agent sessions
* feat: support PR claim during spawn
* fix: address PR review feedback
* feat: add lifecycle worker automation
* fix: prevent duplicate messages on unconfirmed delivery and deduplicate review prompt
sendWithConfirmation now treats unconfirmed delivery as a soft success
since the message was already sent, preventing the dispatch hash from
staying stale and causing duplicate sends on the next poll cycle.
review-check command now sources its prompt from the lifecycle reaction
config instead of hardcoding it, keeping both paths aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: pass AO_CONFIG_PATH to spawned lifecycle worker and log duplicate-worker early exit
The spawned lifecycle worker now receives AO_CONFIG_PATH in its
environment so it finds the correct config regardless of CWD. Also
added a log message when exiting early due to an existing worker.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prevent lifecycle worker from clearing metadata on SCM fetch failures
SCM methods (getPendingComments, getAutomatedComments) silently returned []
on error, causing maybeDispatchReviewBacklog to treat fetch failures as
"no comments" and clear all tracking metadata every poll cycle. The worker
appeared to do nothing because it kept resetting its own state.
- SCM methods now propagate errors instead of returning []
- maybeDispatchReviewBacklog distinguishes null (fetch failed, skip) from
[] (confirmed empty, safe to clear)
- pollAll() logs errors instead of silently swallowing them
- Added heartbeat logging and stdout flush before process.exit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add debug breadcrumbs when review comment fetch fails
Logs a console.debug message when getPendingComments or
getAutomatedComments fails, making it clear the lifecycle loop is
preserving metadata due to a fetch failure rather than genuinely
having no comments.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: scope lifecycle worker polling to its project and add missing test
pollAll() now passes the worker's projectId to sessionManager.list(),
so each per-project lifecycle worker only polls its own sessions. This
prevents duplicate SCM API calls and race conditions in multi-project
configs.
Also fixed misleading test name and added a test verifying that
--no-dashboard alone still starts the lifecycle worker.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove dead confirmation check and respect project-level reaction overrides
Removed the unreachable "could not be confirmed" guard from the retry
logic since sendWithConfirmation now returns silently on unconfirmed
delivery.
review-check now resolves the prompt per-session by checking
project-level reaction overrides before falling back to the global
config, matching lifecycle worker behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: close TOCTOU race in lifecycle worker spawn and unexport getRegistry
Write the child PID from the parent immediately after spawn, closing
the window where a concurrent ensureLifecycleWorker could pass the
"not running" check and spawn a duplicate worker.
Also removed the unnecessary export on getRegistry since it has no
external consumers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: always include ao base prompt on spawn
* fix: clear heartbeat on shutdown and add initial delay to confirmation loop
Clear the heartbeat interval in the shutdown handler to prevent it
firing during the stream-flush window after lifecycle.stop().
Move the sleep in sendWithConfirmation to before each check (including
the first), so the runtime has time to reflect the message before
the first confirmation attempt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve lint errors in lifecycle and session manager
- Replace dynamic delete with object reconstruction in lifecycle-manager
- Add { cause } to re-thrown errors in session-manager for preserve-caught-error rule
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
|
|
|
|
65fa811b3b
|
feat: implement session restore for crashed/exited agents (#104)
* feat: implement session restore for crashed/exited agents
Add true in-place session restore: same session ID, same worktree, same
metadata — optionally resuming the Claude Code conversation via --resume.
Core changes:
- Add TERMINAL_STATUSES, TERMINAL_ACTIVITIES, NON_RESTORABLE_STATUSES sets
and isTerminalSession/isRestorable helpers to types.ts
- Add SessionNotRestorableError and WorkspaceMissingError error classes
- Add restore() to SessionManager with 9-step flow: find metadata →
validate restorability → check/recreate workspace → get restore or
launch command → create runtime → update metadata
- Add restoredAt field to Session and SessionMetadata
Plugin extensions:
- workspace-worktree: exists() + restore() (git worktree prune + re-add)
- workspace-clone: exists() + restore() (git clone + checkout)
- scm-github: branchExists() via git rev-parse
- agent-claude-code: getRestoreCommand() finds latest JSONL session file
and builds claude --resume command
CLI + Web:
- Add `ao session restore <id>` subcommand
- Web restore API route uses sessionManager.restore() instead of spawn()
- SessionCard uses centralized TERMINAL_STATUSES/TERMINAL_ACTIVITIES
- Web types re-export core constants with sync tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add "merged" to TERMINAL_STATUSES
The old inline isTerminal check included "merged" but when refactored
to use the TERMINAL_STATUSES set, "merged" was omitted. This caused
merged sessions (whose activity is not "exited") to incorrectly show
the "terminal" link and "terminate session" button.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: enrich runtime state before restore check, remove dead branchExists
- Add enrichSessionWithRuntimeState() call before isRestorable() in
restore() so crashed sessions (status "working", agent exited) are
correctly detected as terminal and eligible for restore.
- Remove dead branchExists from SCM interface and scm-github plugin
(defined but never called anywhere in the codebase).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: allow restore of crashed working sessions
Remove "working" from NON_RESTORABLE_STATUSES. The isTerminalSession()
gate already prevents restoring truly active sessions (activity is not
"exited"). This fix allows crashed agents (status "working", activity
"exited") to be restored, aligning core behavior with the UI which
already shows the restore button for this case.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: distinguish missing branch from missing restore support
Split the compound condition so workspace restore gives an accurate
error message when branch metadata is null ("branch metadata is
missing") vs when the workspace plugin lacks a restore method.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
|
|
|
|
b75c6b04ea
|
refactor: delegate CLI commands to core SessionManager (#88)
* refactor: delegate CLI commands to core SessionManager Replace direct tmux/metadata manipulation in CLI commands with calls to core's SessionManager (spawn, list, kill, cleanup, send). This removes ~700 lines of reimplemented session logic and makes the CLI a thin layer over the core service. - Add create-session-manager.ts factory (cached PluginRegistry + SM) - spawn: delegate to sm.spawn() instead of manual worktree/tmux setup - session ls/kill/cleanup: delegate to sm.list()/kill()/cleanup() - status: use sm.list() for session discovery and activity from Session - send: resolve tmux target from session.runtimeHandle - review-check: use sm.list() for session discovery - dashboard/start: add --rebuild flag for stale .next cache cleanup - Update all tests to mock create-session-manager.js Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add regression tests for spawn delegation and dashboard stale cache - spawn.test.ts: verify spawn delegates to sm.spawn() with correct args, shows hash-based tmux name in attach hint (regression for flat-naming bug) - dashboard.test.ts: verify stale .next cache detection patterns match the actual error (Cannot find module vendor-chunks/xterm@5.3.0.js) and that rebuildDashboard cleans .next directory Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: ao dashboard --rebuild properly kills and restarts running server Before: --rebuild deleted .next under the running dev server, leaving it in a broken state (500s, missing chunks). Couldn't recover without manual intervention. Now: --rebuild detects the running dashboard via lsof, kills it, cleans .next, then starts a fresh dev server on the same port. Works correctly from any worktree since findWebDir resolves to the same web package. - Add findRunningDashboardPid/findProcessWebDir/waitForPortFree utilities - Split cleanNextCache from rebuildDashboard (cache-only vs full rebuild) - Update tests for new dashboard-rebuild exports Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address bugbot review comments on PR #88 1. Cleanup dry-run now delegates to sm.cleanup({dryRun: true}) instead of checking local status fields — ensures dry-run uses same live checks (PR state, runtime alive) as actual cleanup. 2. Remove module-level mutable config in status.ts — pass config as parameter to gatherSessionInfo() to avoid stale state bugs. 3. Batch-spawn duplicate detection uses sm.list() instead of broken findSessionForIssue() which relied on flat tmux naming incompatible with hash-based architecture. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: remove dead exports getPluginRegistry and rebuildDashboard Both functions were exported but never imported outside test mocks. Clean up test mocks and unused imports accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: deduplicate config/session lookups in send, hoist sm.list() in batch-spawn 1. Send command: merge resolveTmuxTarget() and resolveAgentForSession() into a single resolveSessionContext() that loads config and calls sm.get() once instead of twice. 2. Batch-spawn: move sm.list() call before the loop and build a Map for O(1) duplicate lookups, avoiding repeated metadata reads + runtime enrichment on every iteration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: remove unused project parameter from spawnSession After delegating to sm.spawn(), the ProjectConfig parameter is no longer referenced — sm.spawn() resolves it internally from the projectId. Remove the parameter and simplify both callers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: handle process.kill race, waitForPortFree timeout, dry-run errors 1. Wrap process.kill() in try-catch to handle ESRCH when the target process exits between detection and kill (race condition). 2. waitForPortFree() now throws on timeout instead of silently returning, preventing the dashboard from starting on a busy port. 3. Dry-run cleanup now displays errors from PR/issue checks, matching the non-dry-run branch behavior. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: hoist session manager in cleanup, skip dead sessions in batch-spawn 1. Hoist getSessionManager() before the dry-run if/else in cleanup since both branches create the same instance. 2. Batch-spawn duplicate detection now excludes dead/killed/exited sessions so crashed sessions don't block respawning the same issue. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: lsof flags, dry-run error guard, remove unused --regenerate 1. Fix lsof flag from -Fn to -Ffn so findProcessWebDir() actually gets the file descriptor field needed to match "fcwd" markers. 2. Dry-run cleanup now guards "no sessions" message with both killed.length === 0 AND errors.length === 0, matching the non-dry-run branch behavior. 3. Remove unused --regenerate CLI option from ao start (defined but never referenced in the action body). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: cache registry promise and cap stderr buffer - Cache the Promise instead of the resolved PluginRegistry to prevent concurrent callers from racing past the null check and creating multiple registries before loadFromConfig completes. - Cap stderrChunks to 100 entries since only early startup errors (stale build detection) are checked — unbounded growth wastes memory for long-running dashboard processes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
|
|
|
599710296d
|
fix: migrate to hash-based project isolation architecture
Complete migration to hash-based directory structure for project isolation. All bugbot issues resolved. |
|
|
|
21335db8af
|
feat: publish to npm under @composio scope (#32)
* feat: add npm publishing support with @composio scope Set up Changesets for version management, add publish metadata to all 20 packages under the @composio scope, create an unscoped wrapper package (@composio/agent-orchestrator) for global install, and add a GitHub Actions release workflow. - Rename all packages from @agent-orchestrator/* to @composio/ao-* - Add @composio/agent-orchestrator wrapper (bin shim → @composio/ao-cli) - Add license, repository, homepage, bugs, files, engines to all packages - Add .npmrc (access=public), MIT LICENSE file - Add .changeset/ config with linked versioning for all packages - Add .github/workflows/release.yml (changesets publish CI) - Add changeset, version-packages, release scripts to root Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix: exclude private web package from release build The release script now filters out @composio/ao-web, matching the workflow's existing exclusion and preventing a Next.js build failure from blocking npm publishing. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> |
|
|
|
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>
|