agent-orchestrator/packages/cli/__tests__/lib
Harshit Singh Bhandari cd6d0292b6
refactor(cli): collapse running/not-running fork via ensureDaemon (PR B.2) (#1626)
* refactor(cli): collapse running/not-running fork via ensureDaemon (PR B.2)

Replaces the three branches that handled "AO is already running" cases in
start.ts (§3.2 URL/path-while-running, §3.3 project-id-while-running,
§3.5 non-human info dump) with a single attach pipeline that runs after
resolveOrCreateProject. The fork between attach and spawn is now a single
post-resolve decision point.

New module packages/cli/src/lib/daemon.ts owns the daemon side of that
fork:

  - attachToDaemon(running) -> AttachedDaemon { port, pid,
    notifyProjectChange() } — pure handle plus a typed
    {ok}|{ok:false,reason} cache-invalidation result, replacing the
    open-coded fetch + try/catch that lived in two places.
  - killExistingDaemon(running) — SIGTERM -> waitForExit -> SIGKILL ->
    unregister, used by the "Restart everything" menu option. Replaces
    the inline restart code in §3.4.

resolve-project.ts gains an opt:

  - { targetGlobalRegistry?: boolean } — when true, fromUrl and fromPath
    register against the global config (the daemon's source of truth)
    rather than into a cwd-local one. fromUrl's "register globally"
    branch is a near-verbatim move of the §3.2 inline clone+register
    block, including the global-registry dedup and the flat-local-config
    write that §3.2 deliberately preferred over fromUrl's wrapped yaml.
    fromCwdOrId short-circuits straight to the global registry for
    project-id args while running.

The new dispatch in start.ts:

  - Running + non-human + (no arg | URL | path) -> info dump, exit 0
    (preserved §3.5 behavior; project-id args still fall through to
    attach+spawn so automation can `ao start <id>` against a live
    daemon).
  - Running + human + no arg -> menu (preserved §3.4: open / quit /
    add / new / restart). "restart" now routes through
    killExistingDaemon and falls through to the spawn path; "new" sets
    startNewOrchestrator and falls through to the attach path.
  - resolveOrCreateProject(arg, deps, { targetGlobalRegistry: !!running })
  - Running -> attachAndSpawnOrchestrator helper (§3.2 short-circuit
    preserved: URL/path arg whose project is already in
    running.projects skips the orchestrator-spawn and just opens the
    dashboard).
  - Not running -> existing runStartup + register + handlers.

attachAndSpawnOrchestrator unifies the §3.2/§3.3 messaging behind a
single justCreated discriminator:

  - justCreated=true (URL clone or path register): "Spawning
    orchestrator session..." -> "Project '...' registered in the global
    config." -> "Orchestrator session ready: ..."
  - justCreated=false (project id or already-registered path):
    "Attaching to running AO instance..." -> "Orchestrator session
    ready: ..." -> "Project '...' reattached to running daemon (PID
    ...)"

Both flows then notifyProjectChange (warns on failure, never throws —
the dashboard might be down), print the lifecycle-attach notice when
the project isn't yet supervised, and either openUrl (human) or print
the URL (non-human).

Subsumes the B.1 follow-up (migrate §3.2 inline clone+register to share
fromUrl): the inline block is deleted outright by the fork collapse and
fromUrl now owns both the not-running and the global-registry cases.

start.ts: -1126 / +131 lines. New daemon.ts: 105 lines. resolve-project.ts:
+167 lines (the global-registry branch + a moved-from-start.ts
detectClonedRepoDefaultBranch helper).

No behavior change. Test count and failure set are identical to
upstream/main; the 8 stop-command failures pre-exist on fad75b63.
8 new daemon.test.ts tests cover attachToDaemon (port/pid wiring,
notifyProjectChange success / non-2xx / fetch-throws) and
killExistingDaemon (SIGTERM happy path, SIGKILL escalation, throw on
both-fail, ESRCH-on-already-dead).

Step 2 of PR B in ao-118's start.ts refactor plan
(~/.ao/agent-orchestrator/ao-118/plan.md). startNewOrchestrator and the
§3.4 menu options remain intact — those land in PR B.3.

* fix(cli): canonicalize paths and guard reload in fromPath global branch

Two review fixes on resolve-project.ts:

1. Restore realpathSync canonicalization in fromPath's global-registry
   branch. The original §3.2 inline block in start.ts canonicalized both
   sides before comparing, so an `ao start /tmp/foo` against a daemon
   whose global config stored /private/tmp/foo (macOS symlink) would
   dedupe correctly. The B.2 collapse used plain resolve() and would
   miss the match, calling addProjectToConfig and double-registering
   the project. New canonicalize() helper mirrors the elsewhere-used
   try-realpathSync-fallback pattern.

2. Add the missing null guard on reloaded.projects[addedId] in the same
   branch, matching fromUrlIntoGlobal's existing guard. addProjectToConfig
   could persist nothing on a write-permission error that doesn't throw,
   in which case the undefined project would propagate into
   generateOrchestratorPrompt with a useless stack trace; an explicit
   "Failed to register" error is what the URL branch already raises.

* fix(cli): scope daemon test spy and correct add-menu comment

Two review fixes:

1. Move the process.kill spy in daemon.test.ts inside beforeEach +
   afterEach (vi.restoreAllMocks). The previous module-scope spy could
   leak into sibling test files when Vitest reuses worker threads,
   silently mocking process.kill in unrelated suites and producing
   confusing failures.

2. Rewrite the misleading comment on the "add" menu branch in start.ts.
   The previous wording claimed the path "intentionally does not register
   globally", but loadConfig() walks up from cwd and returns the global
   config as a canonical fallback — so addProjectToConfig may register
   globally in that common case. The new comment honestly describes the
   canonical-aware behavior and the intentional skip of orchestrator
   spawn (the "add" choice is distinct from "new").
2026-05-04 14:28:07 +05:30
..
bun-tmp-janitor.test.ts fix: reduce opencode session list churn (#1478) 2026-04-29 01:24:55 +05:30
daemon.test.ts refactor(cli): collapse running/not-running fork via ensureDaemon (PR B.2) (#1626) 2026-05-04 14:28:07 +05:30
dashboard-rebuild.test.ts fix: clear stale Next.js cache on version upgrade (#1022) 2026-05-01 12:23:51 +05:30
detect-env.test.ts refactor(cli): extract shared repo helpers and support GitLab subgroups 2026-04-15 12:30:32 +05:30
format.test.ts feat: implement CLI with all commands (init, status, spawn, session, send, review-check, dashboard, open) (#6) 2026-02-14 16:14:27 +05:30
lifecycle-service.test.ts fix(cli): supervise lifecycle workers for active projects (#1600) 2026-05-01 15:45:06 +05:30
openclaw-probe.test.ts enhance openclaw-probe with installation detection 2026-03-31 04:02:33 +05:30
plugins.test.ts feat(plugin): implement kimicode agent plugin (#1390) 2026-05-01 14:11:30 +05:30
preflight.test.ts refactor(spawn): plugin-owned preflight + collapse project resolution (#1622) 2026-05-04 14:03:26 +05:30
prevent-sleep.test.ts fix(power): address PR review feedback for sleep prevention 2026-04-11 21:06:32 +05:30
project-resolution.test.ts fix: detect nested project directories in CLI auto-resolution 2026-04-01 18:28:57 +05:30
project-supervisor.test.ts fix(cli): supervise lifecycle workers for active projects (#1600) 2026-05-01 15:45:06 +05:30
repo-validation.test.ts refactor(cli): extract shared repo helpers and support GitLab subgroups 2026-04-15 12:30:32 +05:30
running-state.test.ts fix: tighten startup lock cleanup 2026-04-20 13:30:07 +05:30
script-runner.test.ts fix(cli): avoid missing repo scripts on global installs (#1252) (#1277) 2026-04-26 16:47:49 +05:30
session-utils.test.ts Add multi-project storage, resolution, and project settings support (#1343) 2026-04-21 17:45:55 +05:30
shell.test.ts feat: seamless onboarding with enhanced documentation (#66) 2026-02-16 22:22:13 +05:30
update-check.test.ts chore: align workspace package.json versions with npm registry (#1587) 2026-05-01 15:31:04 +05:30