Commit Graph

108 Commits

Author SHA1 Message Date
Ashish Huddar f3ce113c4c
Add multi-project storage, resolution, and project settings support (#1343)
* feat: add content-addressed project storage keys

* Add per-project resolution and hardened project routing

* Fix storage-key test isolation

* feat(web): redesign Add Project modal with Finder-native layout

* feat: multi-project support with project sidebar, settings, and improved routing

Add per-project configuration in global-config, project-aware CLI commands
(start/spawn/open/session), workspace-worktree project resolution, redesigned
ProjectSidebar with settings modal, repair flow for degraded projects, project
detail page with loading state, reload API endpoint, and comprehensive tests.

* Fix legacy config storage keys and duplicate project flow

* Fix multi-project storage migration and collision handling

* Fix merge regressions in startup and config handling

* Externalize yaml and zod from the web server bundle

* Fix session prefix matching for hashed tmux names

* Fix multi-project migration regressions

* Ignore generated worktree files in ESLint

* Fallback reload config for local-only projects

* Speed up session refresh and redirect after kill

* Use fresh session lists for dashboard polling

* fix(web): remove unused direct terminal child state

* test(web): mock router in merge conflict actions coverage

* docs: call out filesystem browse rollout requirement

* Add portfolio tests and remove unused decomposer export
2026-04-21 17:45:55 +05:30
Chirag Arora f09cc72dc8
feat(cli): hide terminal sessions in `ao session ls` by default (#1319)
* feat(cli): hide terminal session rows by default in session ls

- Filter with TERMINAL_STATUSES unless --include-terminated
- Hint when completed sessions are hidden; docs/CLI.md updated

Made-with: Cursor

* fix(cli): keep session ls JSON inventory stable

Address review concerns by preserving terminal sessions in `ao session ls --json` output while keeping terminal rows hidden by default in text output. Use the core terminal-session predicate, improve hidden-row messaging, and add regression tests for mixed/hinted and JSON behavior.

Made-with: Cursor

* fix(cli): resolve rebase conflicts with latest session ls behavior

Align session command and CLI docs with the current mainline JSON contract (`data` + `meta.hiddenTerminatedCount`) while preserving terminal-filter defaults and updated regression coverage.

Made-with: Cursor
2026-04-21 11:35:54 +05:30
Harshit Singh Bhandari e45f34e1dd
Merge pull request #1308 from harshitsinghbhandari/fix/ao-start-orchestrator-reuse-race
fix: restore dead orchestrators on ao start
2026-04-20 14:53:20 +05:30
Dhruv Sharma f0d4faf2a4
fix: fail ao send when killed session delivery is not confirmed (#1236)
* fix: fail send when killed session delivery is not confirmed

* fix(core): drop requireConfirmation throw that re-introduced duplicate-message bug

The PR's sendWithConfirmation added a throw when confirmation heuristics
did not flip within SEND_CONFIRMATION_ATTEMPTS on a restored session.
But runtimePlugin.sendMessage had already fired, so the throw bubbled up
to the lifecycle manager's catch-all, leaving lastCIFailureDispatchHash
(and its merge-conflict twin) unset. Next poll re-dispatched the same
message — exactly the duplicate-message bug that commit 77685a5 removed.

Real fix for #1074 is preserved: restoreForDelivery still throws when
waitForRestoredSession returns false, which happens before sendMessage
fires. Killed sessions that cannot be revived are still reported as
failures, with no duplicate-send risk.

- sendWithConfirmation no longer takes requireConfirmation; unconfirmed
  delivery always returns (soft success).
- prepareSession returns Session (the tuple only existed to drive the
  removed throw).
- send's retry predicate reverts to prepared.restoredAt === undefined
  && isRestorable(prepared).
- Adds regression test: restored session + sendMessage fires +
  confirmation never flips → send() resolves.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 13:56:31 +05:30
harshitsinghbhandari b2abcb5c3c test: fix restore mock setup in start tests 2026-04-20 13:33:34 +05:30
harshitsinghbhandari 55ebb6395a fix: tighten startup lock cleanup 2026-04-20 13:30:07 +05:30
Ashish Huddar f330a1ea69
feat(cli): filter terminated sessions from ao session ls / ao status by default (#1340)
* feat(cli): filter terminated sessions from ao session ls / ao status by default

Closes #1310. Terminated sessions (killed/terminated/done/merged/errored/cleanup,
plus lifecycle-driven terminal states) are now hidden from `ao session ls` and
`ao status` by default. A dim footer reports how many were hidden and how to
surface them. Pass `--include-terminated` to restore the full list.

JSON output wraps into `{ data: [...], meta: { hiddenTerminatedCount } }` on
both commands so text and machine-readable views tell the same story. This is a
breaking change for script consumers of `--json`; `--include-terminated` is the
escape hatch.

Orthogonal to `-a, --all` (orchestrator visibility, unchanged). Restore of
terminated sessions by id is unaffected — that path goes through `sm.get`, not
`sm.list`.

Docs (`SETUP.md`, `docs/CLI.md`) updated to match. Tests cover both the legacy
status branch and the canonical lifecycle branch of `isTerminalSession`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cli): drop unused `lc` param in lifecycle-alive test case

ESLint's no-unused-vars rejects unprefixed unused args. The "alive — should
remain visible" branch of the new lifecycle-driven filter test in
`session.test.ts` took `lc` but never touched it. Switch to `()`. Matches the
equivalent case in `status.test.ts`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(core): preserve pr.state=merged when legacy metadata lacks pr= URL

Review blocker on PR #1340: a metadata file with `status=merged` but no `pr=`
URL was still showing as active in `ao session ls` / `ao status` by default.

Root cause: `synthesizePRState()` in lifecycle-state.ts short-circuited to
`{ state: "none" }` whenever no PR URL was present, ignoring the fact that the
legacy `status` column already encodes terminal truth. Once lifecycle was
synthesized as `session.state="idle"` + `pr.state="none"`, `deriveLegacyStatus`
returned `"idle"` and `isTerminalSession()` (lifecycle branch) returned false.
The new CLI filter then let the session through.

Fix: when legacy `status === "merged"` and no URL is available, synthesize
`pr.state="merged", reason="merged"` with `number: null, url: null`. The
terminal signal survives the flat-metadata → canonical-lifecycle round trip.

Also:
- Export `sessionFromMetadata` from the core barrel. CLI tests and external
  consumers need it to round-trip metadata through the canonical lifecycle.
- Update CLI `buildSessionsFromDir` helpers to route through `sessionFromMetadata`
  so mocked `sm.list()` reflects production reconstruction (the old shortcut
  bypassed synthesis entirely and was the reason the bug slipped past the
  original test suite).
- Add regression tests: one at the core level (`parseCanonicalLifecycle` for
  merged-without-URL) and one integration-style test per CLI command asserting
  the reviewer's exact repro produces the expected filtered output.
- One pre-existing test expectation updated: when metadata has `status=working`
  and `pr=<url>`, the reconstructed status is `pr_open`, not `working`. That's
  what production `sm.list()` has always returned; the test was previously
  hiding behind the reconstruction shortcut.

Changeset bumped to include ao-core (patch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(cli): route review-check helper through sessionFromMetadata

Last remaining test-fidelity shortcut flagged by codex on PR #1340. The
`buildSessionsFromDir` helper in review-check.test.ts fabricated Session
objects by hand, bypassing the canonical lifecycle reconstruction that
production `sm.list()` runs. Doesn't affect review-check's actual behavior
(which reads `session.metadata["pr"]` directly), but aligns this test with
the equivalent helpers in session.test.ts and status.test.ts so future
lifecycle changes don't silently skip this surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 13:05:34 +05:30
harshitsinghbhandari bb5dcf0af7 fix: address #1306 review follow-ups 2026-04-19 19:59:16 +05:30
harshitsinghbhandari aede68e8da test: remove duplicate restore mock 2026-04-19 19:59:16 +05:30
harshitsinghbhandari c6129de1c9 fix: restore dead orchestrators on start (#1306) 2026-04-19 19:59:15 +05:30
Harsh Batheja 81489079b2
fix(cli,core): reuse orchestrator sessions across ao start; fix dashboard id mismatch (#1075)
Fixes #1048. ao start used to allocate a fresh `{prefix}-orchestrator-N`
on every invocation instead of reattaching to the previous session, and
the dashboard's orchestrator link pointed at a different id than the
CLI just printed.

Changes:

runStartup (packages/cli/src/commands/start.ts):
  - On startup, list existing orchestrators for the project, partition
    them into live (runtime still running) and restorable (terminal but
    sm.restore()-able) buckets, pick the most-recently-active from the
    chosen bucket, and reuse/restore that id instead of spawning a new
    one. Only spawn fresh when both buckets are empty.
  - Live is preferred UNCONDITIONALLY over restorable — a newer killed
    record can never beat an older-but-running one. Without this, a
    cross-bucket sort could resurrect a killed record via sm.restore()
    while the live orchestrator kept running, leaving two alive.
  - Restored sessions get an explicit "(restored)" marker in the CLI
    summary so the resurfaced id isn't a surprise.
  - The phantom `${prefix}-orchestrator` id constant is removed from
    every URL print, browser-open target, and summary line. Everything
    now uses the real selected id.

registerStop (same file):
  - ao stop now resolves the real orchestrator via sm.list(projectId)
    + isOrchestratorSession filter + most-recently-active sort, then
    calls sm.kill on that id. The old phantom `${prefix}-orchestrator`
    target never matched a real numbered record, so ao stop was a
    silent no-op and the orchestrator kept running on disk between
    start cycles. sm.list-failure warning no longer duplicates with the
    generic "no orchestrator found" message.

isOrchestratorSession (packages/core/src/types.ts):
  - Tightened: legacy bare-id records (`{projectId}-orchestrator` with
    no role metadata) are no longer recognized as orchestrators by the
    public predicate. This was the source of the dashboard/CLI id
    divergence — stale bare records with a different prefix than the
    numbered form were leaking into the dashboard's orchestrator list.

session-manager repair (packages/core/src/session-manager.ts):
  - Split `isOrchestratorSessionRecord` (permissive, used by cleanup
    protection) from a new `isRepairableOrchestratorRecord` (stricter,
    used only by repairSingleSessionMetadataOnRead and
    repairSessionMetadataOnRead).
  - The strict repair predicate accepts role-stamped records, the bare
    `{sessionPrefix}-orchestrator` correct-prefix legacy shape, and the
    numbered `{sessionPrefix}-orchestrator-N` worktree shape. It
    rejects foreign bare names like `{projectId}-orchestrator`, so
    those records never get `role: orchestrator` backfilled on read
    and therefore can no longer pass `isOrchestratorSession()` in real
    `sm.list()` output via the role-metadata branch.

Tests added (~12):
  - runStartup: live reuse, restore-on-killed, ignore-stale-bare
    legacy records, live-beats-restorable regression, multi-live
    reuse, URL fallback when --no-orchestrator.
  - ao stop: kills the actual numbered id (not the phantom), handles
    multiple orchestrators, tolerates sm.list throwing.
  - isOrchestratorSession: rejects stale bare ids without role
    metadata; accepts bare ids with role metadata stamped.
  - listDashboardOrchestrators: stale bare excluded, numbered live
    included, role-stamped legacy included.
  - session-manager repair: does not backfill role onto foreign bare-id
    records (issue #1048 regression guard).

Unblocks: review comments from cursor[bot] (dead else-if branch,
double messaging, redundant isTerminalSession check) and illegalcall
(cross-bucket sort, repair-backfill bypass of predicate tightening) —
all addressed in-place with the multi-orchestrator model preserved.

Verified: core 606/606, cli 450/450, typecheck clean across core/cli/web.
2026-04-19 13:12:28 +05:30
harshitsinghbhandari bcdda4b9ba fix: address PR #1300 requested changes 2026-04-18 11:13:53 +05:30
Adil Shaikh ba77929c93
Merge pull request #1158 from ComposioHQ/feat/issue-1154
fix(cli): stop writing broken yaml when `ao start` runs in a new folder
2026-04-15 15:10:10 +05:30
adil 101a725530 fix(test): use vi.spyOn for isHumanCaller after restoreAllMocks
afterEach calls vi.restoreAllMocks() which restores the top-level mock
to the real function. Use vi.spyOn on the module namespace instead of
vi.mocked on the destructured import so the mock survives restoration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 03:46:27 +05:30
adil e685db9d88 fix(test): mock isHumanCaller as false in autoCreateConfig test
The test has no ownerRepo detected, which triggers the interactive
repo prompt. Mock isHumanCaller to false so the prompt is skipped
in the non-interactive test environment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 03:43:03 +05:30
Adil Shaikh 2bb09498d4
Merge pull request #1159 from ComposioHQ/feat/issue-1150
fix(cli): prevent duplicate YAML entry on already-running `ao start`
2026-04-15 03:38:20 +05:30
adil 05a34913e3 fix(cli): address PR review feedback
- Move ensureGit() after path dedup check so already-registered paths
  return early without requiring git
- Use realpathSync for canonical path comparison (resolves symlinks,
  case variants, trailing slashes)
- Bail out with error when both SIGTERM and SIGKILL fail to stop AO
  instead of unconditionally unregistering a live instance
- Rewrite path-dedup test to exercise the path-arg branch via
  AO_CONFIG_PATH, covering addProjectToConfig's dedup lines

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 03:10:23 +05:30
adil d63231a6ea fix(test): update autoCreateConfig test to use isGitRepo: true
The test was mocking detectEnvironment with isGitRepo: false, which now
correctly triggers the fail-fast error for non-git directories. Updated
to isGitRepo: true since the test is verifying config generation
defaults, not non-git behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 03:07:12 +05:30
adil 80932f367a fix(cli): address review — use exit handler, verify PIDs, filter kills (#645)
1. Replace SIGINT/SIGTERM handlers with a process `exit` handler to avoid
   conflicting with the shutdown handler that flushes lifecycle state and
   exits with the correct code (130 for SIGINT).

2. Expand dashboard process pattern to match dev mode (next dev, ao-web)
   in addition to production (next-server, start-all.js).

3. Only kill dashboard-matching PIDs from lsof output, leaving unrelated
   co-listeners (sidecars, SO_REUSEPORT) untouched.

4. Use killDashboardOnPort for the first port attempt too, preventing
   blind kills on the configured port when running.json is stale.

5. Add test for mixed-PID filtering on a single port.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 03:01:41 +05:30
adil e666c88098 fix(cli): address review — verify dashboard process before kill, restore exit on SIGINT
1. Port scan now uses killDashboardOnPort() which checks `ps -p <pid> -o args=`
   for next-server/start-all.js before killing, avoiding collateral damage to
   unrelated services on nearby ports.

2. SIGINT/SIGTERM handlers now call process.exit() after killing the child,
   restoring default exit behavior so Ctrl+C doesn't leave the parent hung
   if the child is unresponsive.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 22:12:58 +05:30
adil 4958512d9e test(cli): add coverage for port scan fallback and c8 ignore for signal handlers
Adds test for stopDashboard finding an orphaned dashboard on a reassigned
port via the port-range scan fallback. Marks signal handler body with
c8 ignore since it only fires on process termination.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 22:12:57 +05:30
adil e620a182c9 fix(cli): kill dashboard child on SIGINT and scan ports in stopDashboard (#645)
Two bugs caused `ao stop` to fail when the dashboard port was auto-reassigned:

1. Ctrl+C did not propagate to the dashboard child process because Node.js
   doesn't guarantee signal forwarding. Added SIGINT/SIGTERM/exit handlers
   in runStartup() to explicitly kill the dashboard child.

2. stopDashboard() only checked the configured port, missing orphaned
   dashboards on reassigned ports. Refactored into killOnPort() helper
   and added a port-range scan fallback (up to MAX_PORT_SCAN ports).

Closes #645

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 22:12:56 +05:30
adil 24a29194eb fix(cli): fix flaky multi-project test by setting non-interactive caller
The "errors when multiple projects and no arg" test expects the error
path (not the prompt path) in resolveProject. Set mockIsHumanCaller
to false so the test reliably hits the non-interactive error branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 17:42:47 +05:30
adil f5818c8b88 test(cli): add tests for already-running detection and path-based dedup
Cover the moved isAlreadyRunning() check (non-TTY exit, quit, open,
restart, new orchestrator) and the path-based dedup guard in
addProjectToConfig(). Uses hoisted mocks for isAlreadyRunning,
isHumanCaller, promptSelect, unregister, and waitForExit to ensure
proper test isolation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 17:42:47 +05:30
Harsh Batheja 95fbecc379
refactor(cli): remove lifecycle-worker subprocess, poll in-process (#1186)
* refactor(cli): remove lifecycle-worker subprocess model, run polling in-process

Replaces the per-project `ao lifecycle-worker` subprocess with an in-process
polling loop managed inside `lifecycle-service`. All registered projects are
now polled from the single long-lived `ao start` process.

- Drops the `lifecycle-worker` command and its registration.
- Rewrites `lifecycle-service` around a `Map<projectId, ActiveLoop>`,
  with SIGINT/SIGTERM/beforeExit graceful shutdown.
- Removes PID-file coordination (PID file, log file, status, etc.) —
  these only existed to track subprocess state.
- Per-project error isolation is preserved: the core lifecycle manager's
  `pollAll()` already catches per-cycle errors, and stop failures in one
  project can't prevent others from stopping.
- Updates `start`/`spawn` callers and tests to drop the PID/logFile shape.
- Adds `lifecycle-service.test.ts` covering idempotency, unknown projects,
  error isolation across projects, and graceful stop-all.

Closes #1185

* fix(cli): address bugbot review on lifecycle-service in-process refactor

- `ao spawn` / `ao batch-spawn` no longer call `ensureLifecycleWorker`.
  That call used to start `setInterval` polling in the one-shot spawn
  process, which (a) kept the CLI alive forever after the session spawned
  and (b) duplicated polling already running in `ao start`. Replaced with
  a `warnIfAONotRunning()` helper that checks `running.json` and prints a
  hint if the orchestrator isn't up.
- `ao stop` no longer calls `stopLifecycleWorker`. That call always ran
  against a fresh in-memory map (stop is a separate process from start),
  so it always returned false and printed "Lifecycle worker not running"
  misleadingly. SIGTERM to the `ao start` PID already triggers the shared
  shutdown handler in `lifecycle-service`, which stops every loop.
- Drop duplicated shutdown closure inside `registerSignalsOnce` — signal
  handlers now reference `stopAllLifecycleWorkers` directly.
- Update tests accordingly: spawn.test.ts mocks `running-state.getRunning`,
  start.test.ts drops `stopLifecycleWorker` expectations.

* fix(cli): drop SIGINT/SIGTERM listeners from lifecycle-service

Installing listeners for those signals removes Node.js's default
"exit on signal" behavior (per Node docs: "its default behavior will
be removed — Node.js will no longer exit"). Since the registered
listener doesn't call process.exit(), the `ao start` process would
hang on SIGTERM with the setInterval timer keeping the event loop
alive forever — effectively breaking `ao stop`.

Default signal handling terminates the process cleanly; the OS
reclaims the interval timer and dashboard child. `stopAllLifecycleWorkers`
stays exported for callers that want explicit cleanup before exit.

* fix(cli): project-scoped spawn warning + flush lifecycle health on exit

Addresses reviewer feedback on PR #1186:

- `warnIfAONotRunning` now takes a projectId and warns not only when no
  AO is running, but also when the running instance isn't polling the
  target project (e.g. `ao start A` then `ao spawn` in B left users
  silent about the fact that B wasn't being polled).
- `running.json` now records only the project this `ao start` actually
  polls, not every project in config. Previously this list was a lie —
  `ensureLifecycleWorker` is called for the selected project only.
- `ao start` installs SIGINT/SIGTERM handlers that call
  `stopAllLifecycleWorkers()` (flushing per-project "stopped" health
  state) and then `process.exit()`. Installing the handler safely
  requires an explicit exit because SIGINT/SIGTERM listeners remove
  Node's default exit behavior.

* fix(cli): remove dead stopLifecycleWorker, add missing test mock

Addresses bugbot comments on PR #1186:

- Delete `stopLifecycleWorker` from `lifecycle-service.ts`. It was only
  called from `ao stop` in the old subprocess model; in the in-process
  model, SIGTERM to the `ao start` pid + the shutdown handler in
  `start.ts` covers cleanup. No production caller remains.
- Add `stopAllLifecycleWorkers: vi.fn()` to `start.test.ts`'s
  `lifecycle-service.js` mock. Without it, `vi.mock` replaced the module
  and the named import resolved to undefined; the shutdown handler's
  try/catch would silently swallow the resulting TypeError, hiding any
  regression in how shutdown is wired.
- Update `lifecycle-service.test.ts` to drop references to the removed
  export (one test removed, one repurposed as a no-op smoke test for
  `stopAllLifecycleWorkers` against an empty active map).
2026-04-14 17:23:33 +05:30
Ashish Huddar 07f01ef6a5
feat(cli): install-aware ao update, startup notifier, doctor version check (#1156)
* feat(cli): install-aware update command, startup notifier, and doctor version check

Make `ao update` detect whether AO was installed via npm or git source and
route accordingly: git installs use the existing shell script, npm installs
prompt to run `npm install -g @aoagents/ao@latest`, and unknown installs
print guidance. Add `--check` flag for JSON version info output.

Add a startup version notifier that reads a cached update check (no network
on startup) and prints a one-liner to stderr when outdated. Background
cache refresh runs after command completion via unref'd timer.

Add a version freshness check to `ao doctor` using cached data only (no
network dependency in diagnostics).

Closes #1136

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(cli): high coverage for update service, command routing, and notifier

Add comprehensive tests per maintainer request for full coverage of all
update paths:

update-check.ts (57 tests):
- isVersionOutdated: major/minor/patch comparisons, equal, newer, missing parts
- detectInstallMethod: all 4 scenarios (git, npm-global, partial, unknown)
- readCachedUpdateInfo: fresh, expired, 23h boundary, version mismatch,
  invalid JSON, missing fields, empty file
- fetchLatestVersion: success, 404, 500, network error, timeout, non-JSON,
  missing version, non-string version, AbortSignal presence
- checkForUpdate: cache hit, cache bypass with force, stale cache fetch,
  cache write on success, no cache write on failure, version match,
  unreachable registry, result shape
- maybeShowUpdateNotice: positive print case, not outdated, no cache,
  non-TTY, AO_NO_UPDATE_NOTIFIER, CI, AGENT_ORCHESTRATOR_CI,
  each skipArg (update/doctor/--version/-V/--help/-h)
- scheduleBackgroundRefresh: does not throw

update.ts (17 tests):
- conflicting flags rejection
- --check: valid JSON output, forces fresh fetch, no side effects
- git: default args, --skip-smoke, --smoke-only, cache invalidation
- npm-global: no script-runner, already up-to-date, registry unreachable
  exits non-zero, --skip-smoke warning, forces fresh fetch
- unknown: help message, shows latest version, registry unreachable,
  suggests npm command

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): address all review feedback — coverage gaps, code fixes, Codex findings

Code fixes:
- Extract classifyInstallPath() for testable npm-global detection
- Convert IS_TTY from module-level constant to isTTY() function call
  (testable without import-time mocking)
- Fix non-TTY npm update silently exiting 0 → now exits 1 so scripts
  detect that no upgrade happened (Codex P2)
- Handle pre-release version tags in isVersionOutdated() — strips
  suffixes before comparing, handles NaN safely
- Export getCacheDir() and writeCache() for test coverage

Test coverage (69 + 24 + 9 + 3 = 105 tests):
- classifyInstallPath: npm-global (Unix + Windows paths), git, partial, unknown
- isVersionOutdated: pre-release tags, NaN handling, same-with-prerelease
- getCacheDir: XDG_CACHE_HOME override, fallback to ~/.cache
- writeCache: success, EACCES mkdir failure, ENOSPC write failure
- handleNpmUpdate full flow: confirm → spawn success, spawn failure with
  sudo suggestion, spawn error (ENOENT), non-TTY exit, flag warning,
  user decline, registry unreachable
- --check with unreachable registry still outputs valid JSON
- maybeShowUpdateNotice: happy path print, AGENT_ORCHESTRATOR_CI guard,
  each skipArg individually
- scheduleBackgroundRefresh: no-throw, swallows errors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): move background refresh earlier, distinguish local vs global node_modules

- Move scheduleBackgroundRefresh() before parseAsync() so the fetch runs
  in parallel with command execution. Short-lived commands now have a
  better chance of seeding the cache before exit. (Codex P2)
- Distinguish global npm installs (lib/node_modules, .pnpm store) from
  local project node_modules (npx, linked installs). Local installs now
  classify as "unknown" instead of "npm-global" to avoid suggesting
  npm install -g to npx users. (Codex P2)
- Add tests: pnpm global store path, nvm global path, npx local path

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(lint): remove forbidden import() type annotations in test mocks

Replace `vi.importActual<typeof import("node:fs")>(...)` with untyped
`vi.importActual(...)` to satisfy @typescript-eslint/consistent-type-imports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): don't misclassify local pnpm installs as npm-global

Local pnpm projects have node_modules/.pnpm/ which matched the /.pnpm/
heuristic. Replace with /pnpm/global/ check which only matches pnpm's
actual global store path (~/.local/share/pnpm/global/...).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): non-TTY exits 0, remove misleading sudo suggestion

- Non-TTY ao update now exits 0 after printing the command. Exit 1
  implied error but the user just needs to run the command manually.
- Remove blanket sudo suggestion on npm failure. npm can exit 1 for
  many reasons (network, version conflict, engine mismatch). Just
  print the exit code and let the user diagnose.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): pnpm global support, defer background refresh after parseAsync

- Detect pnpm global installs separately from npm global. pnpm users
  now see `pnpm add -g @aoagents/ao@latest` instead of the npm command.
  New InstallMethod value: "pnpm-global".
- Move scheduleBackgroundRefresh() to .then() after parseAsync() completes.
  Prevents in-flight fetch from holding the event loop open during
  short-lived commands when the registry is slow/offline. (Codex P2)
- Use info.recommendedCommand instead of hardcoded npm string in
  handleNpmUpdate, so the correct package manager command is always used.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): use actual command name in error message, not hardcoded "npm"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): handle signaled npm update exits

* fix(cli): detect prereleases behind stable releases

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:46:26 +05:30
ChiragArora31 98c5e1518c Merge upstream/main into feat/session-ls-json 2026-04-10 06:43:25 +05:30
Dhruv Sharma c9c41ad6dc
Merge pull request #967 from ChiragArora31/fix/notifier-alias-lifecycle-routing
fix(core): resolve notifier aliases in lifecycle routing
2026-04-10 03:02:36 +05:30
ChiragArora31 ea3da99c51 Merge upstream/main into feat/session-ls-json 2026-04-09 22:24:37 +05:30
ChiragArora31 f9edbee5d2 fix(cli): classify metadata orchestrators in session ls json 2026-04-09 22:16:49 +05:30
ChiragArora31 8ea1c38532 Merge upstream/main into fix/notifier-alias-lifecycle-routing 2026-04-09 21:55:05 +05:30
Prateek 967e864f5a chore: rename @composio scope to @aoagents across all packages
Renames all npm package scopes from @composio/* to @aoagents/* and
updates GitHub repo references from ComposioHQ/agent-orchestrator
to aoagents/ao throughout the codebase.

- All package.json names and dependencies
- README badges, links, and install instructions
- Documentation references
- Changeset config
- Source code imports and test files
2026-04-09 15:59:33 +00:00
ChiragArora31 49df74172b fix(core): preserve alias-specific notifier instances 2026-04-09 19:47:26 +05:30
adil 98547a9f74 fix(cli): clear spinner mock calls between tests (#947)
Add mockClear() before mockReturnThis() in spawn test beforeEach to
prevent call history accumulating across tests. Fixes Bugbot review
feedback about stale mock.calls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 03:40:06 +05:30
adil 48a6f94f50 fix(cli): use ao session attach fallback when --no-dashboard (#947)
When dashboard is disabled, print `ao session attach <id>` instead of
an unreachable dashboard URL. Fixes Bugbot review feedback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 03:40:06 +05:30
adil 24ecfc2926 fix(cli): replace all tmux attach output with dashboard URLs (#947)
Replace raw `tmux attach -t` commands in CLI output with web dashboard
URLs (`http://localhost:{port}/sessions/{sessionId}`) across all four
CLI commands: spawn, session restore, open, and start.

Add `stripHashPrefix` helper in session-utils to extract AO session IDs
from hash-prefixed tmux session names. Remove unused `tmuxTarget`
variable from start command.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 03:40:06 +05:30
adil 5322220f6e fix(cli): consolidate spawn output into single message, add dashboard caveat
Address review feedback:
- Remove duplicate created/spawned messages — use single spinner.succeed
- Add "(if running)" qualifier for dashboard mention since ao spawn
  does not start the dashboard itself
- Update test mock to expose spinner for output assertions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 03:40:06 +05:30
adil 5ad370e8d1 fix(cli): simplify spawn success message, use ao session attach (#947)
Replace verbose multi-line spawn output (worktree, branch, raw tmux
attach) with a single-line message that directs users to the dashboard
or `ao session attach <id>` instead of exposing internal tmux session
hashes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 03:40:06 +05:30
adil fe6fb4f7a2 fix(cli): print session URL instead of tmux attach in ao start (#947)
When dashboard is enabled, `ao start` now prints the session detail page
URL (e.g. http://localhost:3000/sessions/<id>) instead of the tmux attach
command. Falls back to tmux attach when --no-dashboard is used.

Closes #947

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 03:40:06 +05:30
Ashish Huddar 48d655d932
fix: reduce dashboard JS bundle from 1.7MB to 170KB (#792) (#928)
* fix: reduce dashboard JS bundle from 1.7MB to 170KB (gzipped) (#792)

Switch `ao start` default from `next dev` (7.6MB uncompressed) to optimized
production builds (128KB per route). Add `--dev` flag for HMR when editing
dashboard UI. Add bundle analyzer, server-only guards, and lazy-load
DirectTerminal via next/dynamic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: skip dashboard rebuild when assets exist, fix CI timeout

Skip the production build step when .next/BUILD_ID and dist-server/
already exist (e.g. after pnpm build in CI). Add c8 ignore for
untestable process-spawning startup code to fix diff coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: adopt PR #903 patterns — centralize rebuild logic and add preflight web artifact checks

Extract rebuildDashboardProductionArtifacts into dashboard-rebuild.ts, add
isInstalledUnderNodeModules/assertDashboardRebuildSupported guards, remove
findProcessWebDir, and verify .next/BUILD_ID + dist-server/start-all.js in
preflight. Dashboard command now always uses production server.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: skip production preflight in --dev mode, add coverage for rebuild helpers

- Skip preflight.checkBuilt() when --dev is passed (dev mode uses HMR,
  doesn't need .next/BUILD_ID or dist-server/start-all.js)
- Add c8 ignore to dashboard.ts process-spawning code (matches start.ts pattern)
- Add tests for rebuildDashboardProductionArtifacts (success, failure, npm guard)
- Add preflight tests for npm-install web artifact hint paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align preflight skip with actual dev-server condition

Only skip production artifact preflight when both --dev is passed AND
we're in the monorepo (where dev mode actually works). For npm global
installs, --dev is silently ignored and production server runs, so
preflight must still validate .next/BUILD_ID and dist-server/start-all.js.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: match @next/bundle-analyzer version to next@^15.1.0

The @next/* packages follow the Next.js release train. Pin the bundle
analyzer to ^15.1.0 to match the project's next dependency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: correct recovery command for npm installs and remove redundant guard

- Change stale-build recovery suggestion from "ao update" (which only
  works in source checkouts) to "npm install -g @composio/ao@latest"
  for npm global installs
- Remove redundant assertDashboardRebuildSupported call in dashboard.ts
  since rebuildDashboardProductionArtifacts already calls it internally

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 16:12:51 +05:30
Ashish Huddar d8b8645bc7
fix(cli): ao start navigates to session page instead of orchestrator selection (#958)
* fix(cli): ao start navigates to session page instead of orchestrator selection

When ao start finds existing orchestrators, it now auto-selects the most
recently active one and opens /sessions/{id} directly, rather than the
/orchestrators selection page. This matches the behavior users expect:
landing on the agent terminal immediately on startup.

Closes #954

* fix(cli): navigate to session page for single orchestrator, selection page for multiple

When ao start finds existing orchestrators:
- 1 orchestrator: navigate directly to /sessions/{id} (fix for #954)
- 2+ orchestrators: keep /orchestrators?project={id} selection page so users
  can choose among sessions or spawn a new one — the main dashboard only
  links one orchestrator per project, making the selection page the only
  startup path for multi-orchestrator projects.

Addresses review feedback on #958.
2026-04-07 09:48:41 +05:30
Ashish Huddar 30ee327daf
fix(notifier): remove desktop notifications from default configs (#962)
* fix(notifier): remove desktop notifications from all default configs (#960)

Desktop notifications via osascript open blank Finder windows on macOS
without providing useful information. Remove 'desktop' from all default
notifier lists so new users are not affected. Existing configs that
explicitly list 'desktop' continue to work unchanged.

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

* test(cli): add coverage for autoCreateConfig default notifiers

Line 585 in start.ts (`notifiers: []`) was not covered by the diff
coverage check. Added a test that exercises `autoCreateConfig` via
`createConfigOnly()`, verifying that the generated config does not
include 'desktop' in `defaults.notifiers`.

Required infrastructure additions:
- Mock `node:process` so `import { cwd } from "node:process"` in
  start.ts can be overridden per-test via `mockProcessCwd`
- Set `detectProjectType` mock to return `{ languages: [], frameworks: [] }`
  instead of null to avoid crash in autoCreateConfig

Closes #960

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

* fix(config): disable all notifications by default — fully opt-in

Change DefaultPluginsSchema.notifiers default from ["composio"] to []
and notificationRouting defaults to all empty arrays, so no notifications
are sent unless the user explicitly configures them. Addresses reviewer
feedback that composio notifier should not be active out of the box.

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

* fix(config): use empty object as notificationRouting default

Empty array entries like { urgent: [] } block the ?? fallback in
notifyHuman (lifecycle-manager.ts:1169) because [] is not null/undefined.
Using {} lets unlisted priorities fall back to defaults.notifiers, so
explicit per-user notifier config is respected.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 08:59:51 +05:30
ChiragArora31 f19f46b30e feat(cli): add json output to session ls 2026-04-07 08:41:10 +05:30
ChiragArora31 4d22931180 fix(core): resolve notifier aliases in lifecycle routing 2026-04-07 08:23:03 +05:30
Harsh Batheja 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 003eb78adb.

* fix: resolve post-revert type errors and broken test references

- Fix dangling import { in session-manager.ts (leftover from removing globalPause imports)
- Fix duplicate registerStop body in start.ts (leftover parent-version code after catch block)
- Remove --keep-session flag from stop command (was added by #367)
- Fix PullRequestsPage.tsx useSessionEvents call removing dropped initialGlobalPause arg
- Remove globalPause test cases from spawn.test.ts, communication.test.ts, api-routes.test.ts
- Remove unused updateMetadata imports from test files

* fix: remove unused allSessions variable after globalPause removal
2026-04-06 14:58:41 +05:30
harshitsinghbhandari 25b3f31a1c test: update start command tests for auto-select behavior
Update existing test to verify the new --no-dashboard auto-select
behavior and add a separate test for dashboard-enabled selection
message.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-04 22:10:11 +05:30
harshitsinghbhandari 2a1b513e7d fix tests 2026-04-04 21:52:50 +05:30
harshitsinghbhandari 71bdf75f53 add missing mock list 2026-04-04 19:32:01 +05:30
ChiragArora31 4d33d25126 fix: detect nested project directories in CLI auto-resolution 2026-04-01 18:28:57 +05:30
Dhruv Sharma 2febded969
Merge pull request #836 from ChiragArora31/feat/status-watch-mode
feat(cli): add watch mode to ao status
2026-04-01 12:28:32 +05:30