Commit Graph

54 Commits

Author SHA1 Message Date
Harsh Batheja f48c939d9b feat: lifecycle manager, backlog auto-claim, task decomposition, and verification gate (#365)
* feat: wire lifecycle manager, backlog auto-claim, and dashboard overhaul

- Start LifecycleManager in dashboard server (30s polling) so reactions
  actually fire: CI failures, review comments, merge conflicts are now
  auto-forwarded to agents
- Add backlog auto-claim poller (60s interval) that watches for issues
  labeled `agent:backlog` and auto-spawns agent sessions up to max
  concurrent limit (5)
- Add tabbed dashboard UI: Board (kanban), Backlog (issue queue), PRs
- Add issue creation form in dashboard — creates GitHub issues with
  `agent:backlog` label for immediate agent pickup
- Add API routes: /api/backlog, /api/issues, /api/setup-labels
- Pass notifier config through plugin registry (slack webhook fix)

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

* feat: add task decomposition layer (classify → decompose → recurse)

Adds LLM-driven recursive task decomposition upstream of session spawning.
Complex issues are broken into atomic subtasks before agents start working.
Each agent receives lineage context (where it fits in the hierarchy) and
sibling awareness (what parallel agents are doing).

Core changes:
- New decomposer module (core/src/decomposer.ts) — classify, decompose,
  plan tree, lineage formatting, using Claude API
- Extended SessionSpawnConfig with lineage/siblings fields
- Prompt builder Layer 4: decomposition context (hierarchy + siblings)
- ProjectConfig.decomposer config section with Zod validation
- Tracker plugin: added removeLabels support for label management

CLI:
- `ao spawn <project> <issue> --decompose` flag
- `--max-depth <n>` option for decomposition depth
- Spawns multiple sessions with lineage context for composite tasks

Backlog poller:
- Respects project.decomposer.enabled for auto-decomposition
- Posts plan as issue comment when requireApproval=true
- Auto-spawns subtasks with lineage when requireApproval=false

Config example:
  projects:
    my-app:
      decomposer:
        enabled: true
        maxDepth: 3
        requireApproval: true

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

* feat: add verification gate — issues stay open until human confirms fix

PR merge no longer auto-closes GitHub issues. Instead:

1. On PR merge: issue labeled `merged-unverified`, stays open
2. Human checks staging, then runs `ao verify <issue>` to close
3. Or `ao verify <issue> --fail` to flag verification failure

Changes:
- services.ts: labelIssuesForVerification() replaces closeIssuesForMergedSessions()
- New CLI command: `ao verify` (verify/fail/list modes)
- New API route: GET/POST /api/verify
- Dashboard: new Verify tab with one-click verify/fail buttons
- ao status: shows count of issues awaiting verification
- Idle session detection + auto-nudge reaction
- Use TERMINAL_STATUSES in batch-spawn dedup check

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

* fix: rename decomposerConfig to avoid variable shadowing

Addresses Bugbot medium severity issue where inner  variable
shadowed outer  from getServices().

* fix: update pnpm-lock.yaml for new @anthropic-ai/sdk dependency

* fix: resolve remaining merge conflicts and syntax errors

- Remove leftover conflict markers in types.ts
- Remove orphaned code in services.ts
- Fix semicolon to comma in config.ts
- Remove unused import in verify.ts

* fix: address final Bugbot issues

- requireApproval path now exits early with continue to prevent
  fall-through to in-progress label and session spawned comment
- remove packages/core/package-lock.json (pnpm workspace should only
  use root pnpm-lock.yaml)

* fix: idle sessions now transition back to working

When agent resumes activity after being idle, the status correctly
transitions to 'working' instead of remaining stuck in 'idle' state.

* fix(backlog): remove agent:backlog label when claiming issues

When claiming issues from the backlog, the poller now removes the
agent:backlog label in addition to adding agent:in-progress. This
prevents duplicate work if all spawned sessions reach terminal status
and the poller rediscovers the issue.

* fix(test): use Set for TERMINAL_STATUSES mock

The mock for TERMINAL_STATUSES was an array, but the real export is a
ReadonlySet. Changed to use a Set so tests with non-empty sessions won't
crash when calling .has().

* fix(web): resolve backlog/dashboard regressions after branch sync

* fix(web): align dashboard events hook and SSE test mocks

* fix(notifier-openclaw): apply exponential delay from retry index

* fix(integration-tests): align openclaw retry delay expectation

* fix(web): keep dashboard header stats in sync

* fix(openclaw): keep first retry at base delay

---------

Co-authored-by: Agent <agent@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Harsh <harsh@Ubuntu-24-Forrest.lan>
Co-authored-by: Harsh <harsh@example.com>
2026-03-11 06:41:03 +00:00
Harsh Batheja aefa6ef001 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>
2026-03-11 06:41:03 +00:00
Harsh Batheja ffae671884 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)
2026-03-11 06:41:03 +00:00
prateek 1521c0732c feat(ao): add OpenClaw notifier plugin for AO escalations
Adds AO notifier-openclaw (webhook-first Phase 0), registry wiring, tests, docs, and BugBot fixes.
2026-03-11 06:41:03 +00:00
Harsh Batheja cc2031f0f6
fix: bugbot follow-ups from PR #315 (#357)
* fix: address bugbot follow-ups in send and opencode discovery

* fix(test): update stale integration test expectation for opencode bootstrap

The getLaunchCommand implementation now uses a robust bootstrap pattern
that captures the session ID between 'opencode run' and 'exec opencode --session'.
Updated test to check both parts separately instead of expecting a contiguous
string that no longer exists.

* fix: avoid NaN in sort comparator when both timestamps are missing

The comparator (b.updatedAt ?? -Infinity) - (a.updatedAt ?? -Infinity)
produces NaN when both timestamps are missing because -Infinity - (-Infinity)
is NaN in IEEE 754. A comparator returning NaN violates ECMA-262's
'consistent comparison function' requirement, making sort results
implementation-defined.

Fixed by adding equality check before subtraction:
- If both timestamps are equal (including both -Infinity), return 0
- Otherwise return the difference
2026-03-08 12:50:54 +05:30
Harsh Batheja 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>
2026-03-08 09:55:44 +05:30
Prateek db79ad9423 test: align codex plugin version checks and stabilize init port tests 2026-03-07 21:02:07 +05:30
Jayesh Sharma 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>
2026-03-07 03:54:19 +05:30
prateek a4a1a32e96
fix: auto-detect free port in ao start <url> config generation (#275)
* fix: auto-detect free port in ao start <url> config generation

When generating a new config via `ao start <url>`, auto-detect a free
port instead of hardcoding 3000. Reuses `findFreePort` (extracted to
shared web-dir.ts from init.ts). When port was auto-selected, skip the
strict preflight port check and silently find another free port if
needed (race condition).

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

* fix: add polling to listIssues integration test for Linear API consistency

The listIssues test was failing because Linear's API has eventual
consistency — a just-created issue may not appear in list queries
immediately. Other tests in this file already use pollUntilEqual for
the same reason. Use pollUntil to retry until the issue appears.

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

* fix: restore coupling between MAX_PORT_SCAN and findFreePort

MAX_PORT_SCAN was decoupled from findFreePort after extraction to
web-dir.ts — init.ts kept a local constant only used in messages while
findFreePort had its own default, and start.ts hardcoded 99. Export
MAX_PORT_SCAN from web-dir.ts as the single source of truth, use it as
findFreePort's default parameter, and import it in both callers.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 15:03:20 +05:30
prateek b5128cf69f
fix: use gh repo clone for private repo auth in ao start <url> (#273)
HTTPS clone fails on private repos without credentials. Now tries
three strategies in order:
1. gh repo clone (handles GitHub auth via gh auth token)
2. git clone with SSH URL (works with SSH keys)
3. git clone with HTTPS URL (public repos fallback)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 14:42:35 +05:30
prateek e72593d4f8
fix: URL start multi-project resolution and cross-platform browser open (#272)
* fix: URL start handles multi-project configs and cross-platform browser open

When `ao start <url>` loads an existing config with multiple projects,
resolve the correct project by matching the URL's owner/repo against
each project's `repo` field instead of failing with "Multiple projects".

Also fix browser open to use platform-appropriate command (open/xdg-open/start)
instead of hardcoded macOS `open`.

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

* fix: use shallow clone (--depth 1) for faster repo onboarding

Full clones of large repos can take minutes. Shallow clone with
depth 1 fetches only the latest commit, making `ao start <url>`
near-instant for any repo size.

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

* fix: use cmd.exe /c start for Windows browser open

`start` is a cmd.exe builtin, not a standalone executable — spawn
would fail with ENOENT. Run via cmd.exe /c with empty title arg.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 14:26:41 +05:30
prateek 80a1d59999
feat: add `ao start <url>` one-command project onboarding (#267)
* feat: add `ao start <url>` one-command project onboarding

When `ao start` receives a repo URL instead of a project ID, it now:
1. Parses the URL (supports GitHub, GitLab, Bitbucket — HTTPS and SSH)
2. Clones the repo (or reuses if already present with matching remote)
3. Auto-generates agent-orchestrator.yaml with sensible defaults:
   - SCM/tracker plugins inferred from URL host
   - Default branch detected from local refs
   - Project type detected (language, package manager)
   - Post-create install commands set based on package manager
4. Starts the orchestrator + dashboard as usual

New core module `config-generator.ts` handles URL parsing, SCM detection,
project info detection, and config generation. All logic is in core (not
CLI) for reusability. 36 unit tests cover URL parsing, SCM detection,
default branch detection, project info, config generation, and clone
target resolution.

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

* fix: address bugbot review feedback

1. Non-JS postCreate fix: Only set postCreate install commands for JS
   package managers (pnpm/yarn/bun/npm). Rust/Go/Python projects no
   longer get an incorrect "npm install" command.

2. Deduplicate startup logic: Extract shared `runStartup()` helper used
   by both URL-mode and normal-mode start flows. Eliminates ~100 lines
   of duplicated dashboard+orchestrator startup code.

3. SSH URL matching: Normalize SSH URLs to HTTPS format in
   `isRepoAlreadyCloned()` so repos cloned via SSH
   (git@github.com:owner/repo) are correctly detected as matching.

4. SCM/tracker always explicit: Always set scm and tracker fields in
   generated config so `applyProjectDefaults()` doesn't silently
   override with github defaults for non-GitHub repos.

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

* fix: sanitize repo names for project IDs and session prefixes

Address remaining bugbot comments:
- Add sanitizeProjectId() to handle dots/special chars in repo names
  (e.g. "my.app" → "my-app" instead of failing Zod validation)
- Preserve original case for generateSessionPrefix() so CamelCase
  detection works (e.g. "DevOS" → prefix "dos", not "dev")
- Strip invalid chars before prefix generation to prevent dots
  leaking into sessionPrefix

Also update README and SETUP docs with `ao start <url>` quick
onboarding flow.

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

* feat: auto-open browser to orchestrator page on `ao start`

Opens the orchestrator session page (/sessions/{id}) instead of the
root dashboard, since the Kanban board is empty on first startup
with no worker sessions yet.

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

* fix: replace fixed 3s delay with port polling for browser open

Poll the dashboard port via TCP connection attempts (300ms intervals,
30s timeout) instead of a hardcoded setTimeout. Opens the browser only
once the server is actually accepting connections — deterministic
regardless of how long Next.js takes to compile.

Applied to both `ao start` and `ao dashboard` commands.

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

* test: add CLI tests for start/stop commands

12 new tests covering:
- Project resolution: single project, explicit arg, missing project,
  multi-project ambiguity, empty config
- URL argument: reuse existing clone, git clone flow, existing config
  detection, clone failure error handling
- Port polling: waitForPortAndOpen polls isPortAvailable before opening
  browser (proves deterministic behavior vs fixed delay)
- Stop command: session kill + dashboard stop, graceful missing session

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

* refactor: deduplicate waitForPortAndOpen into shared utility

Move waitForPortAndOpen to lib/web-dir.ts (alongside isPortAvailable)
so both start.ts and dashboard.ts share a single cancellation-aware
implementation. start.ts now uses AbortSignal like dashboard.ts —
polling stops immediately if the dashboard process exits early.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 13:52:49 +05:30
suraj_markup 7aa883c217
Merge pull request #254 from suraj-markup/feat/try-pr-script
feat(scripts): add try-pr.sh for testing PR worktrees locally
2026-03-03 01:18:06 +05:30
suraj_markup 01a6c56e1b
Merge pull request #246 from suraj-markup/feat/issue-245
fix(setup): improve setup.sh prereq validation and add start/spawn pre-flight checks
2026-03-02 03:24:14 +05:30
suraj-markup 6603685a2f feat(scripts): add try-pr.sh for testing PR worktrees locally
Adds scripts/try-pr.sh — a helper to switch the global 'ao' command to
any session's worktree for manual testing, then restore back to main.

Usage:
  bash scripts/try-pr.sh <session-id>            # CLI/core/plugins only
  bash scripts/try-pr.sh <session-id> --with-web # also builds + starts dashboard
  bash scripts/try-pr.sh --restore               # switch back to main

Also fixes isPortAvailable() to use a connect-based probe instead of
bind-based. The old approach had false positives on macOS when Next.js
listens on :: (IPv6 wildcard) — binding 127.0.0.1 succeeded even though
the port was taken. A TCP connect correctly detects any listener
regardless of which address it's bound to.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 03:19:49 +05:30
suraj-markup 18dea011e7 fix: address review feedback and add preflight tests
- Consistent error formatting: single spawn now uses err.message like
  batch-spawn, avoiding redundant "Error:" prefix
- checkBuilt distinguishes missing node_modules ("pnpm install") from
  missing dist ("pnpm build") so users get the right guidance
- Add 13 preflight unit tests covering port, build, tmux, and gh checks
- Add 5 spawn integration tests covering runtime-aware tmux check,
  tracker-aware gh check, and error message formatting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 03:05:00 +05:30
suraj-markup 62cd42b30f fix: resolve checkBuilt from web node_modules and remove unused imports
Check ao-core/dist/index.js relative to webDir's node_modules instead
of using require.resolve which fails under npm link. Also removes
unused existsSync/resolve/createRequire imports that caused lint errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:50:56 +05:30
suraj-markup b43b1556c0 fix: check core dist output instead of .next/BUILD_ID in checkBuilt
.next/BUILD_ID only exists after next build (production) but the
dashboard runs with next dev. Check @composio/ao-core resolve instead,
which is the actual cause of module resolution errors when packages
are not compiled.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:44:19 +05:30
suraj-markup cb071e122d fix: wrap batch-spawn preflight in try-catch
Preflight errors in batch-spawn were unhandled, causing unformatted
rejection output. Now caught with chalk.red formatting and
process.exit(1), matching the single spawn handler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:40:12 +05:30
suraj-markup 96c7ae534a fix: run spawn preflight checks once before batch loop
Extract preflight checks from spawnSession into runSpawnPreflight and
call it once upfront in both registerSpawn and registerBatchSpawn. A
missing prerequisite now fails fast with one clear error instead of
repeating N times across the batch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:01:15 +05:30
suraj-markup f14465335d fix: address review feedback on preflight checks
- Consistent error handling: all preflight functions now throw instead
  of mixing process.exit and throw, so callers can catch and clean up
- checkGhAuth distinguishes "not installed" (ENOENT) from "not
  authenticated" by checking gh --version first
- Move checkBuilt() after package.json existence check in start.ts so
  missing web package shows "Run: pnpm install" not "Run: pnpm build"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 01:45:39 +05:30
suraj-markup 42442e007e fix(cli): correct misleading comment on isPortAvailable IPv6 coverage
The comment claimed connect-based detection works for IPv6 listeners,
but the probe only connects to 127.0.0.1 (IPv4). Updated the comment
to accurately document this limitation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:53:11 +05:30
suraj-markup dfcc0841ec fix(cli): remove unused createServer import and fix flaky port test
- Remove unused `createServer` import from web-dir.ts (only `Socket`
  is used) — fixes the lint error in CI
- Mock `isPortAvailable` in the "uses port 3000" test so it doesn't
  depend on port 3000 actually being free on the host machine

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:41:08 +05:30
suraj-markup 06ba99eeb4 fix(cli): check both IPv4 and IPv6 in isPortAvailable to prevent false positives
pnpm dev and Next.js listen on :: (dual-stack IPv6) which doesn't conflict
with a 127.0.0.1-only bind check, causing isPortAvailable to return true
even when the port is in use. Now checks both 127.0.0.1 and ::1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 21:31:44 +05:30
suraj-markup c0b998a445 fix: scope pre-flight checks to when they are relevant
- Move port/build checks inside the `--no-dashboard` guard so they only
  run when the dashboard is actually being started
- Only check for tmux when the resolved runtime is "tmux", respecting
  per-project runtime overrides (e.g. "process")

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:47:02 +05:30
suraj-markup e40275b53c fix(setup): improve setup.sh with prereq validation and add ao start/spawn pre-flight checks
- Rewrite setup.sh with hard stops for Node<20 and git<2.25, soft warns
  with interactive fix for tmux/gh-auth/claude, corepack-based pnpm
  install, and PATH verification after npm link
- Add pre-flight checks to `ao start`: verify dashboard port is free and
  packages are built before proceeding
- Add pre-flight checks to `ao spawn`: verify tmux is installed and gh
  is authenticated (when using github tracker) before session creation
- Extract shared preflight utilities into packages/cli/src/lib/preflight.ts

Closes #245

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:39:29 +05:30
suraj-markup 308d9dcf0d fix(cli): remove unused Server import to fix lint error
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:23:58 +05:30
suraj-markup b027f5686e fix(cli): tell user when default port is busy and suggest alternative
In interactive mode: "Port 3000 is busy — suggesting 3001 instead.
Press Enter to accept, or type a different port."

In auto mode: "Port 3000 is busy — using 3001 instead."

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:15:11 +05:30
suraj-markup 8eff44aa3f test(cli): add unit tests for init sessionPrefix, name, port, and --smart fixes
- Verify auto mode writes `name` and `sessionPrefix` to project config
- Verify warning is shown when no free port is found in range
- Verify `--smart` flag description includes "coming soon"
- Verify `sessionPrefix` matches core `generateSessionPrefix` output

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:04:50 +05:30
suraj-markup c52caec441 fix(cli): use generateSessionPrefix from core instead of naive slice
Address review feedback: use the core utility `generateSessionPrefix`
which produces proper short-form prefixes (kebab-case initials,
CamelCase extraction, etc.) instead of `projectId.slice(0, 8)`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:00:00 +05:30
suraj-markup 5f9481bc01 fix(cli): add missing sessionPrefix, name, and port validation in ao init
- Add `sessionPrefix` (derived from projectId.slice(0, 8)) to project
  config in both interactive and auto modes so session IDs are valid
  instead of `undefined-1`
- Add `name` field to project config so `ao start` prints the actual
  project name instead of "undefined"
- Mark `--smart` flag as "(coming soon)" in help text since it is a
  no-op stub
- Return `null` from `findFreePort` when no port is available and warn
  the user to set the port manually

Closes #236

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 18:47:09 +05:30
suraj-markup 2bdc6bb59c test: add tests for --agent override and codex plugin registration
- Session manager: agent override uses correct agent, unknown agent throws, default used without override
- CLI spawn: --agent flag passthrough with and without issue ID
- Plugin registry: multiple agent plugins registered via loadBuiltins importFn

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:26:54 +05:30
suraj-markup 71ee3a2144 feat(cli): add --agent flag to override agent plugin at spawn time
Allows starting sessions with a different agent without changing config:
  ao spawn ao --agent codex
  ao spawn ao --agent codex INT-1234

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 23:23:54 +05:30
prateek 9822aba4e0
fix: auto-detect free port in `ao init` instead of hardcoding 3000 (#120)
When port 3000 is occupied, `ao init` now scans upward (3001, 3002, ...)
until a free port is found. Applies to both interactive and --auto modes.
Uses the existing isPortAvailable() from web-dir.ts (now exported).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 07:27:39 +05:30
prateek 520010d5a2
feat: configurable terminal server ports for multi-dashboard support (#113)
* feat: make terminal server ports configurable to fix multi-dashboard EADDRINUSE

When multiple ao dashboards run simultaneously (e.g., ao on port 3000,
integrator on port 3002), both try to start terminal WebSocket servers
on hardcoded ports 3001/3003, causing EADDRINUSE. Add terminalPort and
directTerminalPort to config schema so each instance can use unique ports.

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

* fix: use optional() instead of default() for terminal port schema

Zod .default() always fills in the value, making config.terminalPort
never undefined and the env var fallback in buildDashboardEnv dead code.
Switch to .optional() so the priority chain works correctly:
config value > TERMINAL_PORT env var > hardcoded default.

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

* fix: move terminal server defaults from 3001/3003 to 14800/14801

The 3000-3009 range is the most contested in dev tooling (Next.js
auto-increments, BrowserSync, Grafana, Rails, Express all default to
3000+). Port 14800-14899 has zero IANA registrations, zero known dev
tool conflicts, and is safely below OS ephemeral ranges.

Updated all hardcoded fallbacks, .env.local.example, docker-compose
port mappings, and documentation.

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

* feat: auto-detect available terminal ports for zero-config multi-dashboard

When no terminal ports are configured (no config, no env vars),
buildDashboardEnv now probes for an available port pair starting at
14800. The second `ao start` automatically gets 14802/14803 (or the
next free pair), eliminating EADDRINUSE without any user configuration.

Port detection scans in steps of 2 to keep the pair consecutive.
Explicit config/env values bypass auto-detection entirely.

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

* fix: update onboarding test to use new default terminal port (14801)

The onboarding integration test had port 3003 hardcoded for the
WebSocket health check. Updated to read from DIRECT_TERMINAL_PORT
env var with 14801 as the default, matching the new port defaults.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 04:00:19 +05:30
prateek 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>
2026-02-19 01:12:57 +05:30
prateek de6653e258
feat: first-class orchestrator session + file-based system prompt (#101)
* feat: first-class orchestrator session + file-based system prompt

Make the orchestrator a first-class managed session that flows through
the same SessionManager pipeline as worker sessions, and fix a blocking
bug where long system prompts get truncated by tmux/zsh.

Changes:
- Add OrchestratorSpawnConfig type and spawnOrchestrator() to
  SessionManager interface
- Implement spawnOrchestrator() in session-manager.ts: proper
  hash-based tmuxName, runtimeHandle, plugin lifecycle — no workspace
  creation (uses project.path directly)
- Refactor `ao start` to use SessionManager.spawnOrchestrator()
  instead of manual tmux calls + metadata writes (~80 lines removed)
- Refactor `ao stop` to use SessionManager.kill() instead of manual
  tmux kill + metadata delete
- Update `ao init` next steps: guide users to `ao start` before
  `ao spawn`
- Add systemPromptFile to AgentLaunchConfig for file-based system
  prompts (avoids tmux truncation of 2000+ char inline prompts)
- Update agent-claude-code, agent-codex, agent-aider plugins to use
  shell command substitution "$(cat '/path')" when systemPromptFile
  is set
- Update runtime-tmux create() to use load-buffer/paste-buffer for
  launch commands >200 chars
- Add 8 tests for spawnOrchestrator
- Fix SessionManager mock in 8 test files (add spawnOrchestrator)

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

* fix: use hash-based tmux name in orchestrator attach hint

The tmux attach hint after `ao start` printed the user-facing session
ID (e.g. app-orchestrator) instead of the hash-based tmux session name
(e.g. a3b4c5d6e7f8-app-orchestrator), causing "session not found"
errors. Now captures the runtimeHandle.id from spawnOrchestrator's
return value for the correct tmux target.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 19:32:35 +05:30
prateek 767558fed6
fix: three spawn regressions from PR #88 session-manager refactor (#100)
1. No-issue spawn: use session/{sessionId} branch instead of defaultBranch
   to avoid git worktree conflicts with the main repo's checked-out branch.

2. Plugin resolution: accept importFn parameter in loadBuiltins/loadFromConfig
   so CLI can pass its own import() context for pnpm strict resolution. Added
   all plugin packages as CLI workspace dependencies.

3. Ad-hoc issue IDs: gracefully handle IssueNotFoundError by continuing
   without tracker context instead of throwing. Non-issue errors (auth,
   network) still fail fast.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 17:53:33 +05:30
prateek 59c490a3af
fix: dashboard config discovery + CLI service layer refactoring (#70)
* fix: config discovery, activity detection, and metadata port storage

- findConfigFile() checks AO_CONFIG_PATH env var (resolved to absolute path)
- loadConfig() delegates to findConfigFile() for consistent validation
- Pure Node.js readLastJsonlEntry (no external tail binary), safe for
  multi-byte UTF-8 at chunk boundaries
- Added "ready" activity state to agent plugins
- Store dashboardPort, terminalWsPort, directTerminalWsPort in session
  metadata so ao stop targets the correct processes
- Zod schema port default aligned with TypeScript interface

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

* fix: dashboard config discovery + CLI service layer refactoring

- Config discovery via AO_CONFIG_PATH env var
- Auto port detection with PortManager
- Activity detection with ready state, pure Node.js readLastLine
- 5 CLI services: ConfigService, PortManager, DashboardManager, MetadataService, ProcessManager
- Store all service ports in metadata for ao stop
- Set NEXT_PUBLIC_ env vars for frontend terminal components
- Multi-byte UTF-8 safe readLastJsonlEntry
- Tests for all new services and utils

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

* fix: address bugbot review comments (port fallback + systemPrompt)

1. Align port fallback to 3000 everywhere (matching Zod schema default):
   - start.ts: config.port ?? 3000
   - dashboard.ts: config.port ?? 3000
   - types.ts JSDoc: "defaults to 3000"
   - orchestrator-prompt.ts: already correct at 3000

2. Add --append-system-prompt to Claude Code plugin's getLaunchCommand
   so orchestrator context is actually passed to the Claude agent.
   Previously systemPrompt was generated but silently dropped.

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

* fix: remove dead ConfigService mock from status test

The vi.mock for ConfigService.js referenced a deleted module.
Config mocking is already handled by the @composio/ao-core mock.

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

* fix: extract shared buildDashboardEnv to eliminate duplication

Dashboard env construction (AO_CONFIG_PATH, PORT, NEXT_PUBLIC_*) was
duplicated between start.ts and dashboard.ts. Extracted into
buildDashboardEnv() in web-dir.ts (already shared by both commands).

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 17:08:48 +05:30
prateek 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>
2026-02-18 14:30:17 +05:30
prateek adc17c8ae0
feat: overhaul orchestrator prompt with comprehensive CLI reference (#90)
* feat: overhaul orchestrator prompt with comprehensive CLI reference

The generated CLAUDE.orchestrator.md now teaches the orchestrator agent
everything it needs out of the box: identity/role, complete CLI reference
for every `ao` command with flags/options, behavioral guidelines (do/don't),
session lifecycle, workflows, and anti-patterns.

- Rewrite generateOrchestratorPrompt() with detailed CLI docs
- Add "Never Do" section (no legacy scripts, no raw tmux, no coding)
- Add session lifecycle ASCII diagram adapted to project config
- Document ao send flags (--no-wait, --timeout, -f) and mechanics
- Update static CLAUDE.orchestrator.md to use ao CLI exclusively
- Add 35 unit tests for orchestrator prompt generation

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

* refactor: move orchestrator prompt injection into Agent plugin interface

The orchestrator prompt was being injected by directly writing
CLAUDE.local.md and CLAUDE.orchestrator.md in start.ts — Claude Code-
specific logic that doesn't work for other agents (Codex, Aider, OpenCode).

- Add `injectSystemPrompt()` to the Agent interface in types.ts
- Implement in agent-claude-code: writes CLAUDE.{name}.md + @import
- Implement in agent-codex/opencode: writes AGENTS.md
- Implement in agent-aider: writes .aider.conventions.md
- Remove ensureOrchestratorPrompt/ensureOrchestratorImport from start.ts
- start.ts now calls agent.injectSystemPrompt() (agent-agnostic)

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

* refactor: pass orchestrator prompt via CLI flags instead of file injection

Replace the file-based injectSystemPrompt() approach with native agent CLI
flags. Each agent plugin now handles systemPrompt in getLaunchCommand():
- Claude Code: --append-system-prompt
- Codex: --system-prompt
- Aider: --system-prompt
- OpenCode: no flag yet (ignored)

This removes the need to write CLAUDE.orchestrator.md / CLAUDE.local.md /
AGENTS.md files, making the implementation truly agent-agnostic. Also removes
the unused --regenerate flag from `ao start`.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 11:32:33 +05:30
prateek 73957182f7
fix: activity detection — fix path encoding bug, add ready state (#71)
* fix: activity detection — fix path encoding, use tail -1 for JSONL

Two tightly coupled infrastructure fixes:

- Fix toClaudeProjectPath(): leading `/` becomes `-` (not stripped),
  matching Claude Code's actual project directory naming convention.
- Replace manual 4KB buffer read in readLastJsonlEntry() with
  `tail -1` + JSON.parse — handles any file size, any line length,
  and eliminates the truncated-line edge case entirely.

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

* feat: add "ready" state, return null when unknown, remove dead code

Behavioral changes to activity detection:

- Add "ready" to ActivityState — separates "alive at prompt" from
  "idle/stale". Configurable via readyThresholdMs (default 5 min).
- Agent plugins return null when they can't determine activity
  (no workspace, no JSONL, no per-session tracking). Session manager
  preserves existing activity instead of overwriting with a guess.
- Remove isProcessing() from Agent interface — zero callers in
  production code, fully superseded by getActivityState().
- Remove extractLastMessageType() from claude-code — the field it
  populated (lastMessageType) was only consumed by the old inline
  CLI mapping, which is now replaced by plugin delegation.
- CLI status delegates to agent.getActivityState() (single source
  of truth) with metadata fallback when plugin returns null.

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

* test: comprehensive activity detection coverage

- activity-detection.test.ts: 42+ tests covering path encoding,
  getActivityState edge cases (exited/null/fallback), real Claude Code
  JSONL types, agent interface spec types, staleness thresholds,
  JSONL file selection, and realistic session sequences.
- status.test.ts: plugin delegation tests — verifies CLI uses
  agent.getActivityState() as single source of truth, passes
  readyThresholdMs from config, falls back to metadata on null/throw.
- Integration tests: updated type expectations for null returns from
  codex, opencode, and aider; added "ready" to valid state lists.

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

* fix: flaky Linear integration test + missing ready label in SessionDetail

Linear API has eventual consistency — updateIssue state changes don't
propagate instantly. Poll with retries instead of asserting immediately.

Also adds "ready" entry to SessionDetail activityLabel map (was missing,
causing fallback to dim/unstyled rendering).

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

* fix: pure Node.js readLastJsonlEntry, use pollUntilEqual for Linear test

Replace `tail -1` with pure Node.js implementation that reads backwards
from end of file in 4KB chunks. No external binary dependency — works
on any platform.

Fix flaky Linear integration test by using the existing pollUntilEqual
helper instead of an inline retry loop. Linear API has eventual
consistency; pollUntilEqual retries for up to 5s with 500ms intervals.

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

* fix: use tail -1 for readLastJsonlEntry, add real-data integration test

Replace over-engineered pure Node.js backward-reading implementation with
simple `tail -1` via execFile. The codebase already shells out to tmux,
git, and ps everywhere — tail is no different.

Add integration test that validates toClaudeProjectPath() and
readLastJsonlEntry() against real ~/.claude/projects/ data on disk.
No API key needed — just requires Claude to have been run once.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 03:48:19 +05:30
prateek 9c5927a7f6
fix: clean up hash-based test directories in afterEach (#86)
Fixes bugbot issue: "Tests leak directories under home directory without cleanup"

The problem:
- Tests call the real getSessionsDir() which creates ~/.agent-orchestrator/{hash}/
  directories based on hashing the tmpDir path
- Each test run uses a new random tmpDir, creating a unique hash
- afterEach only cleaned up tmpDir, leaving orphaned directories in ~/.agent-orchestrator/
- These directories accumulated indefinitely

The fix:
- Added cleanup for hash-based directories in afterEach for all affected tests
- Uses getProjectBaseDir() to calculate the directory path
- Removes the hash-based directory before cleaning tmpDir
- All 61 tests pass (13 CLI + 48 core)

Files fixed:
- packages/cli/__tests__/commands/session.test.ts
- packages/core/src/__tests__/session-manager.test.ts
- packages/core/src/__tests__/lifecycle-manager.test.ts

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-18 01:38:48 +05:30
prateek 599710296d
fix: migrate to hash-based project isolation architecture
Complete migration to hash-based directory structure for project isolation. All bugbot issues resolved.
2026-02-18 00:19:55 +05:30
prateek eaea131af9
feat: seamless onboarding with enhanced documentation (#66)
* feat: implement seamless onboarding with enhanced documentation

- Add comprehensive README.md (18KB) with quick start, core concepts, and FAQ
- Add detailed SETUP.md (16.5KB) with prerequisites, integration guides, and troubleshooting
- Add examples/ directory with 5 ready-to-use config templates:
  - simple-github.yaml: Minimal GitHub setup
  - linear-team.yaml: Linear integration
  - multi-project.yaml: Multiple repos
  - auto-merge.yaml: Aggressive automation
  - codex-integration.yaml: Using Codex agent

- Add environment detection (git repo, remote, branch, auth status)
- Auto-fill prompts with smart defaults from detected environment
- Add prerequisite validation (git, tmux, gh CLI)
- Show actionable next steps and warnings
- Parse owner/repo from git remote automatically
- Detect LINEAR_API_KEY and SLACK_WEBHOOK_URL in environment
- Prompt for Linear team ID when Linear tracker selected

- Format all files with Prettier for consistency

Reduces onboarding time from 30+ minutes to ~5 minutes:
1. Install CLI: `npm install -g @composio/ao-cli`
2. Run init: `ao init` (auto-detects everything)
3. Spawn agent: `ao spawn my-project ISSUE-123`

Users no longer need to:
- Manually parse git remote URLs
- Look up current branch names
- Remember YAML syntax
- Search for Linear team IDs
- Debug missing prerequisites

-  pnpm build - All packages compile
-  pnpm typecheck - No TypeScript errors
-  pnpm lint - No new linting issues
-  pnpm format - All files formatted

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

* docs: update installation instructions to reflect npm not yet published

Package is not published to npm yet, so users must build from source.
Updated README.md and SETUP.md to:
- Make 'build from source' the primary installation method
- Add note that npm publishing is coming soon
- Include pnpm as a prerequisite

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

* feat: add ao init --auto --smart for zero-config setup

Implements intelligent config generation with project type detection.

## What's New

### ao init --auto
- Zero prompts - auto-generates config with smart defaults
- Detects: git repo, remote, branch, languages, frameworks, tools
- Generates project-specific agentRules based on detected tech stack

### Project Detection
- Languages: TypeScript, JavaScript, Python, Go, Rust
- Frameworks: React, Next.js, Vue, Express, FastAPI, Django, Flask
- Tools: pnpm workspaces, test frameworks
- Package managers: pnpm, yarn, npm

### Rule Templates
Created templates for:
- base.md - Universal best practices
- typescript.md - TS strict mode, ESM, type imports
- javascript.md - Modern ES6+ patterns
- react.md - Hooks, composition, best practices
- nextjs.md - App Router, Server Components
- python.md - Type hints, PEP 8
- go.md - Error handling, defer patterns
- pnpm-workspaces.md - Monorepo commands

### Example Output

```bash
ao init --auto

# Detects:
# ✓ TypeScript + pnpm workspaces
# ✓ React + Next.js
# ✓ Vitest

# Generates:
agentRules: |
  Always run tests before pushing.
  Use TypeScript strict mode.
  Use ESM modules with .js extensions.
  Use React best practices (hooks, composition).
  Before pushing: pnpm build && pnpm typecheck && pnpm lint && pnpm test
```

## Benefits

- **5 seconds** instead of 5 minutes
- **Zero config knowledge** required
- **Context-aware rules** tailored to your stack
- **Still customizable** - edit the generated config

## Future: --smart (AI-powered)

Flag added but not yet implemented. Will use Claude Code to:
- Analyze CLAUDE.md, CONTRIBUTING.md
- Read CI/CD config
- Generate custom rules based on project patterns

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

* fix: detect repo default branch instead of current branch

Fixes Bugbot issue: "Current branch wrongly suggested as default base branch"

## Problem

detectEnvironment was using `git branch --show-current` to suggest
defaultBranch in the config. If a user ran `ao init` while on a feature
branch like `feat/my-work`, the wizard would suggest that feature branch
as the default, causing agents to branch from the wrong base.

## Solution

Added detectDefaultBranch() function with 3 fallback methods:
1. git symbolic-ref refs/remotes/origin/HEAD (most reliable)
2. GitHub API via gh CLI (if ownerRepo known)
3. Check common branch names: main, master, next, develop

Now EnvironmentInfo tracks both:
- currentBranch: The checked-out branch (for display only)
- defaultBranch: The repo's base branch (for config)

## Testing

Tested on feat/seamless-onboarding branch:
- Current branch: feat/seamless-onboarding (displayed)
- Default branch: main (correctly detected for config)

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

* fix: prevent duplicate framework detection in Python projects

Fixes Bugbot issue: "Duplicate frameworks when multiple Python config files exist"

## Problem

When both requirements.txt and pyproject.toml exist and mention the same
framework (e.g., FastAPI), the detection loop added it to the frameworks
array twice, causing duplicate rules in the generated config.

## Solution

Added addFramework() helper that checks if framework already exists before
adding to the array. Also prevents pytest from being set multiple times as
testFramework.

## Testing

Verified with test repo containing both files with FastAPI:
- Before: Would add 'fastapi' twice
- After: Only adds 'fastapi' once ✓

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

* fix: address Bugbot review comments

- Remove redundant conditional in --smart flag (both branches were identical)
- Include templates directory in npm package files

* fix: add existence check for base.md template file

Add existsSync guard before reading base.md to handle missing templates gracefully, consistent with other template file reads.

* fix: use direct tool invocation instead of which command

Replace 'which' with direct tool invocation (tmux -V, gh --version)
for better portability on minimal Linux systems where 'which' may
not be installed.

* fix: address Bugbot review comments

- Simplify gh auth status check to rely on exit code instead of output string
- Remove async from synchronous functions (detectProjectType, generateRulesFromTemplates)

* feat: add setup script for one-command installation

Add scripts/setup.sh that:
- Installs pnpm if not present
- Installs dependencies
- Builds all packages
- Links CLI globally

Updated README with simplified setup instructions using the script.

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

* fix: correct npm link command in setup script

Remove incorrect -g flag from npm link command. The correct syntax is to cd into the package directory and run npm link without flags.

* fix: address Bugbot review comments on init command

- Validate --smart flag requires --auto (prevents silent ignore)
- Fix path validation to check user-specified path (not CWD)

These fixes address medium and low severity issues found by Cursor Bugbot
in PR #66 review.

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

* docs: add DirectTerminal troubleshooting and fix setup script

- Add TROUBLESHOOTING.md documenting node-pty posix_spawnp error
- Update setup.sh to rebuild node-pty from source (fixes DirectTerminal)
- Ensures seamless onboarding with working terminal out-of-the-box

Resolves DirectTerminal WebSocket failures from incompatible prebuilt binaries.

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

* fix: resolve variable scope issue in init command validation

- Move path variable outside if block to fix TypeScript scope error
- Only validate path existence if projectId is provided
- Use inline tilde expansion instead of missing expandHome import

Fixes build error that prevented setup.sh from completing.

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

* fix: automate node-pty rebuild to eliminate terminal issues

- Add postinstall hook to automatically rebuild node-pty after pnpm install
- Create scripts/rebuild-node-pty.js for automatic rebuild with error handling
- Remove manual node-pty rebuild from setup.sh (now automatic)

This ensures DirectTerminal works correctly on every installation without
manual intervention. Fixes posix_spawnp errors from incompatible prebuilt
binaries across different systems and installations.

Resolves issue where users would encounter blank terminals after setup.

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

* docs: update TROUBLESHOOTING with automatic node-pty rebuild

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

* docs: add comprehensive README with quick start guide

- 3-line magical setup: clone → setup → init → start
- Architecture overview with plugin slots table
- Usage examples and auto-reaction configuration
- Links to detailed docs (SETUP.md, TROUBLESHOOTING.md, examples/)
- Philosophy: push not pull, amplify judgment

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

* fix: resolve ESLint errors in rebuild-node-pty script

- Add scripts directory configuration to eslint.config.js
- Configure Node.js globals (console, process) for scripts
- Remove unused error variable from catch block

Fixes lint CI failure.

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

* fix: warn when auto mode uses placeholder repo value

- Detect when 'owner/repo' placeholder is used in --auto mode
- Show warning: 'Could not detect GitHub repository'
- Update next steps to emphasize editing config when placeholder used
- Prevents silent failures when spawning agents with invalid repo

Addresses Bugbot review comment about silent placeholder values.

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

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 22:22:13 +05:30
prateek dbeb8d9fb2
fix: increase delays in spawn prompt submission to ensure Enter is processed (#62)
Fixes issue where spawned sessions would send the prompt text but the Enter
keystroke would not be submitted, requiring manual Enter press.

Changes:
- Increase delay after paste-buffer from 300ms to 1000ms in tmux.ts sendKeys()
  to ensure tmux processes the paste before receiving Enter keystroke
- Add 100ms delay after Escape key to ensure it's processed before pasting
- Increase spawn wait time from 1s to 2s before sending initial prompt to
  allow Claude to fully initialize (permission prompts, startup, etc.)

These longer delays account for:
1. tmux paste buffer processing time
2. Claude permission prompt interactions
3. Agent initialization and readiness

Closes: FIX-SPAWN-PROMPT-SUBMIT
2026-02-16 20:31:07 +05:30
Prateek a6866458ec fix: unset CLAUDECODE in spawned sessions to prevent nested session errors
The spawn command was creating tmux sessions without calling agent.getEnvironment(),
causing spawned sessions to inherit CLAUDECODE from the parent orchestrator session.
This caused Claude to refuse to start with "cannot launch inside another Claude Code
session" error.

Now properly calls agent.getEnvironment() and merges those env vars (including
CLAUDECODE="") into the tmux session creation.

Fixes sessions ao-22 through ao-25 which were spawned but never had Claude running.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 05:51:05 +05:30
prateek 77323cb309
feat: implement ao start command for unified orchestrator startup (#42)
* feat: implement ao start command for unified orchestrator startup

Adds `ao start` and `ao stop` commands to unify orchestrator and
dashboard startup. Key features:

- Generates CLAUDE.orchestrator.md with project-specific context
- Auto-imports orchestrator prompt via CLAUDE.local.md
- Creates orchestrator tmux session with agent
- Starts Next.js dashboard server
- Supports --no-dashboard, --no-orchestrator, --regenerate flags
- Idempotent operation (safe to run multiple times)
- Computes orchestrator ID from config (not session search)
- Dashboard button always visible for orchestrator terminal

Components:
- packages/core/src/orchestrator-prompt.ts: Prompt generator
- packages/cli/src/commands/start.ts: Start/stop commands
- Modified exports and dashboard UI for orchestrator support

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

* feat: add automatic metadata updates via Claude Code hooks

CRITICAL: This makes the dashboard work by auto-updating metadata when
agents run git/gh commands. Without this, PRs created by agents never
appear on the dashboard.

Changes:
- packages/core/src/claude-hooks.ts: Setup Claude hooks (settings.json + metadata-updater.sh)
- ao start: Automatically configures Claude hooks in project directory
- ao spawn: Sets AO_SESSION and AO_DATA_DIR env vars for hook script
- metadata-updater.sh: Detects gh pr create, git checkout -b, gh pr merge

How it works:
1. PostToolUse hook fires after every Bash command
2. metadata-updater.sh receives JSON with command and output
3. Pattern matches git/gh commands and updates flat metadata files
4. Dashboard reads metadata files to show PR/branch/status

The .claude directory is symlinked from main repo to worktrees so all
sessions share the same hook config.

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

* refactor: move hook setup to Agent plugin interface

ARCHITECTURE: Hooks setup must go through the Agent plugin interface,
not be hardcoded for Claude Code. This allows other agents (Codex,
Aider, OpenCode) to implement their own metadata update mechanisms.

Changes:
- Added Agent.setupWorkspaceHooks() method to types.ts
- Added WorkspaceHooksConfig interface
- Implemented setupWorkspaceHooks() in Claude Code plugin
- ao start: calls agent.setupWorkspaceHooks() instead of direct setup
- ao spawn: calls agent.setupWorkspaceHooks() for new worktrees
- Uses $CLAUDE_PROJECT_DIR variable for hook path (works with symlinked .claude)

Each agent plugin now implements its own hook mechanism:
- Claude Code: .claude/settings.json with PostToolUse hook
- Future: Codex, Aider, OpenCode with their own config formats

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

* fix: address Cursor Bugbot review comments

Fixes 3 issues identified by Cursor Bugbot:

1. HIGH: ao start is now truly idempotent - if orchestrator session exists,
   it skips creating the session but still proceeds with dashboard startup
   and hook configuration. This allows `ao start` to recover from dashboard
   crashes without failing.

2. MEDIUM: Dashboard orchestrator button now finds the actual running
   orchestrator session instead of always using the first project. Fallback
   to first project ID if no orchestrator is running.

3. LOW: Deduplicated findWebDir() function by moving it to shared utility
   lib/web-dir.ts. Now used by both dashboard.ts and start.ts.

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

* fix: resolve ESLint errors

Fixes 4 ESLint errors identified in CI:
- Added { cause: err } to Error constructors (preserve-caught-error rule)
- Prefixed unused parameter 'config' with underscore in setupWorkspaceHooks
- Prefixed unused variable 'projectId' with underscore in stop command

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

* fix: address bugbot review comments - markdown escaping and unused module

- Fix markdown code fence escaping in orchestrator-prompt.ts (change
  `\\\`` to `\`` for proper markdown rendering)
- Remove unused claude-hooks.ts module (functionality moved to plugin)
- Clean up exports from core/src/index.ts

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

* fix: address remaining bugbot issues - metadata path, duplication, summary

HIGH severity - Fix metadata path mismatch for worker sessions:
- Add AO_PROJECT_ID environment variable in spawn.ts
- Update metadata-updater hook script to construct correct path:
  * Worker sessions: $AO_DATA_DIR/${AO_PROJECT_ID}-sessions/$AO_SESSION
  * Orchestrator: $AO_DATA_DIR/$AO_SESSION (no project ID)
- Fixes silent hook failures where PRs/branches never appeared on dashboard

LOW severity - Remove code duplication in hook setup:
- Extract setupHookInWorkspace() helper function (90 lines)
- Refactor setupWorkspaceHooks() to use helper (from 80 lines to 4)
- Refactor postLaunchSetup() to use helper (from 82 lines to 6)
- Eliminates risk of methods drifting out of sync

LOW severity - Fix misleading summary output:
- Change "Orchestrator started" to "Startup complete" when components skipped
- Only show dashboard URL when --no-dashboard NOT used
- Only show session info when --no-orchestrator NOT used
- Show "already running" status when orchestrator exists

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

* fix: address new bugbot issues - hook quoting, orchestrator link, pkill scope

HIGH severity - Fix Claude hook command quoting:
- Remove embedded double quotes from $CLAUDE_PROJECT_DIR path
- Change from '"$CLAUDE_PROJECT_DIR"/.claude/...' to '$CLAUDE_PROJECT_DIR/.claude/...'
- Prevents hook execution failures if runner treats command as literal path

MEDIUM severity - Fix orchestrator link pointing to nowhere:
- Only show "orchestrator terminal" link when session actually exists
- Remove fallback to computed/hardcoded "ao-orchestrator" ID
- Prevents 404s when user clicks link before starting orchestrator

MEDIUM severity - Fix stop command killing unrelated processes:
- Replace broad `pkill -f "next dev -p ${port}"` with targeted approach
- Use `lsof -ti :${port}` to find exact PID listening on port
- Only kill the specific process, not any process mentioning "next dev"
- Prevents accidentally killing unrelated Next.js dev servers

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

* fix: orchestrator metadata path and multi-pid dashboard stop

HIGH severity - Fix orchestrator AO_PROJECT_ID pollution:
- Orchestrator intentionally omits AO_PROJECT_ID (uses flat metadata path)
- agent.getEnvironment() adds AO_PROJECT_ID=project.name
- Object.assign() merged this in, breaking metadata hook path lookup
- Fix: delete environment.AO_PROJECT_ID after merge
- Ensures orchestrator metadata updates work correctly

MEDIUM severity - Fix stopDashboard with multiple PIDs:
- lsof -ti :PORT returns multiple PIDs (one per line) for parent+children
- Passing entire multi-line string to kill fails (can't parse newlines)
- Fix: split stdout by newlines, filter empty, pass PIDs as separate args
- Now correctly stops dashboard even when multiple Node processes exist

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

* fix: remove AO_PROJECT_ID from agent plugins to fix metadata path mismatch

The agent.getEnvironment() method was setting AO_PROJECT_ID to
config.projectConfig.name, but different callers have different metadata
path schemes:
- spawn.ts writes to project-specific directories (dataDir/{projectId}-sessions/)
- start.ts writes to flat directories for orchestrator (dataDir/)
- session-manager writes to flat directories (dataDir/)

Setting AO_PROJECT_ID in getEnvironment() caused the metadata updater hook
to look for files in the wrong location for orchestrator and session-manager
flows, breaking automatic metadata updates.

Fix: Remove AO_PROJECT_ID from all agent plugins' getEnvironment() methods
and make it the caller's responsibility to set when using project-specific
directories. Only spawn.ts sets it now.

Changes:
- Remove AO_PROJECT_ID assignment from getEnvironment() in all 4 agent plugins
  (claude-code, aider, codex, opencode)
- Update corresponding tests to expect AO_PROJECT_ID to be undefined
- Remove delete environment.AO_PROJECT_ID statement from start.ts (no longer needed)
- Add comments explaining the metadata path scheme contract

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

* fix: only run orchestrator setup when actually starting orchestrator

The orchestrator-specific setup steps (generating CLAUDE.orchestrator.md,
configuring CLAUDE.local.md, and setting up agent hooks) were executing
unconditionally, even when --no-orchestrator was passed. This caused
`ao start --no-dashboard` to fail if hook setup had errors, because the
hook setup was fatal and blocked the dashboard from starting.

Fix: Move all orchestrator setup steps inside the `if (opts?.orchestrator !== false)`
guard, and specifically inside the `else` branch (when session doesn't already exist).
Now these steps only run when we're actually creating a new orchestrator session.

This allows:
- `ao start --no-orchestrator` to start only the dashboard
- Skipping setup when orchestrator session already exists
- Setup to be non-blocking for dashboard-only mode

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

* fix: eliminate redundant getAgent call by hoisting agent declaration

The agent instance was being created twice in the orchestrator setup block:
- Once at line 237 inside the hook setup try block
- Again at line 253 for getting the launch command

This creates duplicate agent instances unnecessarily. Fixed by declaring
the agent variable before the hook setup try block, allowing it to be
reused for both hook setup and launch command generation.

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

* fix: address resource leaks and undefined env variable

Fixed 4 Bugbot issues:

1. HIGH: Undefined $CLAUDE_PROJECT_DIR in hook script path
   - Changed setupWorkspaceHooks to use absolute path instead of undefined
     env variable
   - Matches approach used in postLaunchSetup for consistency

2. HIGH: Orchestrator session leaks when metadata write fails
   - Added try-catch around tmux session launch and metadata write
   - Kills tmux session if metadata write or agent launch fails
   - Prevents orphaned sessions from consuming resources

3. MEDIUM: Dashboard process leaks when orchestrator setup fails
   - Wrapped orchestrator setup in try-catch block
   - Kills dashboard child process if orchestrator setup fails
   - Prevents orphaned Next.js server from blocking future starts

4. LOW: Inconsistent indentation (fixed as side effect of restructuring)
   - Refactored error handling simplified indentation structure

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

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 04:58:39 +05:30
prateek 1cd7c519af
feat: validate tracker issues on spawn with fail-fast behavior (#44)
* feat: validate tracker issues on spawn with fail-fast behavior

Implement issue validation in spawn flow to fail fast when issues don't exist,
preventing creation of sessions with broken issue references.

**Key Changes:**
- Add isIssueNotFoundError() helper to detect "not found" errors
- Validate issues BEFORE creating resources (workspace, runtime, session ID)
- Fail fast with clear error messages when issues don't exist
- Categorize batch spawn failures (not_found, auth_failed, other)
- Add comprehensive tests (4 new test cases, all 25 tests pass)
- Update documentation with new spawn flow

**Behavior:**
- Issue exists → spawn proceeds, reports success
- Issue not found → fail with "does not exist in tracker" message
- Auth/network error → fail with error details
- No issue provided → spawn ad-hoc session without issue tracking

**Benefits:**
- No wasted resources on invalid issues
- Clear, actionable error messages for orchestrators
- Maintains backwards compatibility (ad-hoc sessions still work)
- Separation of concerns (spawn validates, orchestrator creates issues)

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

* fix: address bugbot review comments - improve error detection and add CLI validation

**Issue 1: CLI validation was missing**
- CLI's spawnSession bypassed core session manager validation
- Added tracker plugin support to CLI (getTracker function)
- Validate issues in CLI before creating worktrees/tmux sessions
- Error categorization now matches actual tracker errors, not git/tmux errors

**Issue 2: Overly broad "not found" matching**
- Previous: matched any "not found" (including "API key not found", "Team not found")
- Fixed: Check for issue-specific patterns AND exclude infrastructure errors
- Now matches: "issue" + "not found", "no issue found", "could not find issue"
- Excludes: "api key", "team", "configuration", "workspace", "organization", "endpoint"

**Changes:**
- packages/core/src/types.ts: More specific isIssueNotFoundError logic
- packages/cli/src/commands/spawn.ts: Add validation before workspace creation
- packages/cli/src/lib/plugins.ts: Add tracker plugin support + getTracker
- packages/cli/package.json: Add tracker plugin dependencies

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

* fix: resolve CI lint and test failures

**Lint fixes:**
- Combine duplicate imports in session-manager.ts and spawn.ts
- Add error cause preservation in CLI error throws

**Test fixes:**
- Mock getIssue response in plugin-integration test for spawn validation
- Test now provides proper GitHub issue response for validation to pass

**Changes:**
- packages/core/src/session-manager.ts: Combine imports using inline type syntax
- packages/cli/src/commands/spawn.ts: Merge duplicate plugin imports, add error cause
- packages/core/src/__tests__/plugin-integration.test.ts: Add mockGh for issue validation

All lint checks pass (0 errors, 11 warnings - existing)
All tests pass (128 tests)

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

* fix: address bugbot review comments - error categorization and CLI test mocking

- Fix case-sensitive auth check in batch-spawn (now catches 'Authentication' errors)
- Fix workspace exclusion in isIssueNotFoundError to not shadow valid issue errors
- Add missing tracker mock to CLI tests (fixes all 5 test failures)

* fix: address remaining bugbot comments - remove duplication and redundancy

- Remove redundant sessionId reassignment after reservation loop
- Remove duplicate suggestion message in CLI error handler
- Remove duplicate issue validation from CLI to avoid DRY violation
  (validation remains in core SessionManager.spawn for programmatic use)

* fix: restore sessionId assignment needed for TypeScript flow analysis

The assignment after the loop is not logically redundant but is required
for TypeScript's definite assignment analysis. Without it, TS cannot
guarantee sessionId is assigned after the loop.

* fix: remove unused tracker plugin code from CLI

- Remove getTracker function (no longer used after removing CLI validation)
- Remove tracker plugin imports and dependencies
- Remove mockGetTracker from tests
- Reduces bundle size and dependency coupling

* chore: update pnpm lockfile after removing tracker dependencies

* fix: remove console output from core library

Core library should not have console.log/console.warn calls as it's
used by CLI, web dashboard, and tests. Console output couples the
library to a specific output mechanism and produces noise in non-CLI
contexts. Callers can handle output presentation as needed.

* fix: remove unused catch parameter to fix lint error

* fix: remove redundant success messages in CLI spawn command

spawnSession already outputs spinner.succeed/fail with details, so
additional success/failure messages in command handlers are redundant.
Keep batch-spawn error categorization for summary purposes.

* fix: remove unused catch parameter

* fix: simplify batch-spawn error reporting

Remove misleading error categorization from batch-spawn. Since spawn no
longer categorizes errors as "not_found" or "auth_failed", the batch-spawn
summary section was filtering on error types that were never set.

Simplified to just list all failed issues with their error messages.

Also fixed single spawn error handling to log the error message before
exiting.

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

* fix: remove dead code from isIssueNotFoundError

The exclusion guard for infrastructure errors (lines 863-876) was
completely redundant. Every return pattern already requires "issue" to
be present in the message, so a message without "issue" would naturally
return false from the main return statement. The guard added no value
and created confusion for maintainers.

Simplified by removing the redundant guard entirely.

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

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 04:45:38 +05:30
prateek 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>
2026-02-15 04:28:57 +05:30