@chorus-aidlc/chorus 0.14.3 → 0.14.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-build-manifest.json +161 -161
  3. package/.next/standalone/.next/app-path-routes-manifest.json +34 -34
  4. package/.next/standalone/.next/build-manifest.json +2 -2
  5. package/.next/standalone/.next/prerender-manifest.json +42 -42
  6. package/.next/standalone/.next/server/app/(dashboard)/project-groups/[uuid]/page_client-reference-manifest.js +1 -1
  7. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/activity/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/dashboard/[ideaUuid]/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/dashboard/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/documents/[documentUuid]/page.js +1 -1
  11. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/documents/[documentUuid]/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/documents/page.js +1 -1
  13. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/documents/page_client-reference-manifest.js +1 -1
  14. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/graph/page_client-reference-manifest.js +1 -1
  15. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/[proposalUuid]/page.js +1 -1
  16. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/[proposalUuid]/page_client-reference-manifest.js +1 -1
  17. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/new/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/page.js +1 -1
  19. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/tasks/[taskUuid]/page_client-reference-manifest.js +1 -1
  21. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/tasks/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/.next/server/app/(dashboard)/projects/page_client-reference-manifest.js +1 -1
  23. package/.next/standalone/.next/server/app/(dashboard)/settings/page_client-reference-manifest.js +1 -1
  24. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  25. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  26. package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  27. package/.next/standalone/.next/server/app/admin/companies/[uuid]/page_client-reference-manifest.js +1 -1
  28. package/.next/standalone/.next/server/app/admin/companies/new/page_client-reference-manifest.js +1 -1
  29. package/.next/standalone/.next/server/app/admin/companies/new.html +2 -2
  30. package/.next/standalone/.next/server/app/admin/companies/new.rsc +1 -1
  31. package/.next/standalone/.next/server/app/admin/companies/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/admin/companies.html +2 -2
  33. package/.next/standalone/.next/server/app/admin/companies.rsc +1 -1
  34. package/.next/standalone/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  35. package/.next/standalone/.next/server/app/admin.html +2 -2
  36. package/.next/standalone/.next/server/app/admin.rsc +1 -1
  37. package/.next/standalone/.next/server/app/api/admin/companies/[uuid]/route_client-reference-manifest.js +1 -1
  38. package/.next/standalone/.next/server/app/api/admin/companies/route_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app/api/admin/login/route_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/api/admin/session/route_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/api/agent-connections/route_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/api/agents/[uuid]/route_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/api/agents/[uuid]/sessions/route_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/api/agents/route_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/api/api-keys/[uuid]/route_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/api/api-keys/route_client-reference-manifest.js +1 -1
  47. package/.next/standalone/.next/server/app/api/auth/callback/route_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/app/api/auth/check-default/route_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/api/auth/company-oidc/route_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/api/auth/default-login/route_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/api/auth/identify/route_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/api/auth/me/route_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/api/auth/sync-token/route_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/api/comments/route_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/app/api/daemon/connection-heartbeat/route_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/app/api/daemon/control/route_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/api/daemon/execution-state/route_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/app/api/daemon/executions/route_client-reference-manifest.js +1 -1
  60. package/.next/standalone/.next/server/app/api/daemon/pending-turns/route_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/app/api/daemon/report-interrupt/route_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/api/daemon/resume/route_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/api/daemon/transcript/route_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/app/api/daemon/turn-advance/route_client-reference-manifest.js +1 -1
  65. package/.next/standalone/.next/server/app/api/daemon-sessions/[sessionUuid]/instruction/route_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/app/api/daemon-sessions/[sessionUuid]/repoint/route_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/api/daemon-sessions/[sessionUuid]/route_client-reference-manifest.js +1 -1
  68. package/.next/standalone/.next/server/app/api/daemon-sessions/ad-hoc/route_client-reference-manifest.js +1 -1
  69. package/.next/standalone/.next/server/app/api/daemon-sessions/route_client-reference-manifest.js +1 -1
  70. package/.next/standalone/.next/server/app/api/documents/[uuid]/route_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/api/entities/[type]/[uuid]/root-idea/route_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/api/events/notifications/route_client-reference-manifest.js +1 -1
  73. package/.next/standalone/.next/server/app/api/events/route_client-reference-manifest.js +1 -1
  74. package/.next/standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  75. package/.next/standalone/.next/server/app/api/ideas/[uuid]/claim/route_client-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/app/api/ideas/[uuid]/move/preview/route_client-reference-manifest.js +1 -1
  77. package/.next/standalone/.next/server/app/api/ideas/[uuid]/move/route_client-reference-manifest.js +1 -1
  78. package/.next/standalone/.next/server/app/api/ideas/[uuid]/parent/route_client-reference-manifest.js +1 -1
  79. package/.next/standalone/.next/server/app/api/ideas/[uuid]/release/route_client-reference-manifest.js +1 -1
  80. package/.next/standalone/.next/server/app/api/ideas/[uuid]/route_client-reference-manifest.js +1 -1
  81. package/.next/standalone/.next/server/app/api/ideas/[uuid]/wake-preview/route_client-reference-manifest.js +1 -1
  82. package/.next/standalone/.next/server/app/api/ideas/conversational/route_client-reference-manifest.js +1 -1
  83. package/.next/standalone/.next/server/app/api/mcp/route_client-reference-manifest.js +1 -1
  84. package/.next/standalone/.next/server/app/api/me/assignments/route_client-reference-manifest.js +1 -1
  85. package/.next/standalone/.next/server/app/api/mentionables/route_client-reference-manifest.js +1 -1
  86. package/.next/standalone/.next/server/app/api/notifications/[uuid]/archive/route_client-reference-manifest.js +1 -1
  87. package/.next/standalone/.next/server/app/api/notifications/[uuid]/read/route_client-reference-manifest.js +1 -1
  88. package/.next/standalone/.next/server/app/api/notifications/preferences/route_client-reference-manifest.js +1 -1
  89. package/.next/standalone/.next/server/app/api/notifications/read-all/route_client-reference-manifest.js +1 -1
  90. package/.next/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  91. package/.next/standalone/.next/server/app/api/notifications/unread-count/route_client-reference-manifest.js +1 -1
  92. package/.next/standalone/.next/server/app/api/project-groups/[uuid]/dashboard/route_client-reference-manifest.js +1 -1
  93. package/.next/standalone/.next/server/app/api/project-groups/[uuid]/route_client-reference-manifest.js +1 -1
  94. package/.next/standalone/.next/server/app/api/project-groups/route_client-reference-manifest.js +1 -1
  95. package/.next/standalone/.next/server/app/api/project-visits/pin/route_client-reference-manifest.js +1 -1
  96. package/.next/standalone/.next/server/app/api/project-visits/route_client-reference-manifest.js +1 -1
  97. package/.next/standalone/.next/server/app/api/project-visits/visit/route_client-reference-manifest.js +1 -1
  98. package/.next/standalone/.next/server/app/api/projects/[uuid]/activity/route_client-reference-manifest.js +1 -1
  99. package/.next/standalone/.next/server/app/api/projects/[uuid]/available/route_client-reference-manifest.js +1 -1
  100. package/.next/standalone/.next/server/app/api/projects/[uuid]/documents/route_client-reference-manifest.js +1 -1
  101. package/.next/standalone/.next/server/app/api/projects/[uuid]/group/route_client-reference-manifest.js +1 -1
  102. package/.next/standalone/.next/server/app/api/projects/[uuid]/ideas/route_client-reference-manifest.js +1 -1
  103. package/.next/standalone/.next/server/app/api/projects/[uuid]/ideas/tracker/route_client-reference-manifest.js +1 -1
  104. package/.next/standalone/.next/server/app/api/projects/[uuid]/proposals/[proposalUuid]/validate/route_client-reference-manifest.js +1 -1
  105. package/.next/standalone/.next/server/app/api/projects/[uuid]/proposals/route_client-reference-manifest.js +1 -1
  106. package/.next/standalone/.next/server/app/api/projects/[uuid]/proposals/summary/route_client-reference-manifest.js +1 -1
  107. package/.next/standalone/.next/server/app/api/projects/[uuid]/resource-graph/route_client-reference-manifest.js +1 -1
  108. package/.next/standalone/.next/server/app/api/projects/[uuid]/route_client-reference-manifest.js +1 -1
  109. package/.next/standalone/.next/server/app/api/projects/[uuid]/stats/route_client-reference-manifest.js +1 -1
  110. package/.next/standalone/.next/server/app/api/projects/[uuid]/tasks/dependencies/route_client-reference-manifest.js +1 -1
  111. package/.next/standalone/.next/server/app/api/projects/[uuid]/tasks/route_client-reference-manifest.js +1 -1
  112. package/.next/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  113. package/.next/standalone/.next/server/app/api/proposals/[uuid]/approve/route_client-reference-manifest.js +1 -1
  114. package/.next/standalone/.next/server/app/api/proposals/[uuid]/close/route_client-reference-manifest.js +1 -1
  115. package/.next/standalone/.next/server/app/api/proposals/[uuid]/reject/route_client-reference-manifest.js +1 -1
  116. package/.next/standalone/.next/server/app/api/proposals/[uuid]/revoke/route_client-reference-manifest.js +1 -1
  117. package/.next/standalone/.next/server/app/api/proposals/[uuid]/route_client-reference-manifest.js +1 -1
  118. package/.next/standalone/.next/server/app/api/references/[uuid]/route_client-reference-manifest.js +1 -1
  119. package/.next/standalone/.next/server/app/api/references/route_client-reference-manifest.js +1 -1
  120. package/.next/standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  121. package/.next/standalone/.next/server/app/api/session/route_client-reference-manifest.js +1 -1
  122. package/.next/standalone/.next/server/app/api/sessions/[uuid]/route_client-reference-manifest.js +1 -1
  123. package/.next/standalone/.next/server/app/api/tasks/[uuid]/claim/route_client-reference-manifest.js +1 -1
  124. package/.next/standalone/.next/server/app/api/tasks/[uuid]/dependencies/[dependsOnUuid]/route_client-reference-manifest.js +1 -1
  125. package/.next/standalone/.next/server/app/api/tasks/[uuid]/dependencies/route_client-reference-manifest.js +1 -1
  126. package/.next/standalone/.next/server/app/api/tasks/[uuid]/release/route_client-reference-manifest.js +1 -1
  127. package/.next/standalone/.next/server/app/api/tasks/[uuid]/route_client-reference-manifest.js +1 -1
  128. package/.next/standalone/.next/server/app/api/tasks/[uuid]/sessions/route_client-reference-manifest.js +1 -1
  129. package/.next/standalone/.next/server/app/index.html +2 -2
  130. package/.next/standalone/.next/server/app/index.rsc +1 -1
  131. package/.next/standalone/.next/server/app/login/admin/page_client-reference-manifest.js +1 -1
  132. package/.next/standalone/.next/server/app/login/admin.html +2 -2
  133. package/.next/standalone/.next/server/app/login/admin.rsc +1 -1
  134. package/.next/standalone/.next/server/app/login/callback/page_client-reference-manifest.js +1 -1
  135. package/.next/standalone/.next/server/app/login/callback.html +2 -2
  136. package/.next/standalone/.next/server/app/login/callback.rsc +1 -1
  137. package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  138. package/.next/standalone/.next/server/app/login/pick-workspace/page_client-reference-manifest.js +1 -1
  139. package/.next/standalone/.next/server/app/login/pick-workspace.html +2 -2
  140. package/.next/standalone/.next/server/app/login/pick-workspace.rsc +1 -1
  141. package/.next/standalone/.next/server/app/login.html +2 -2
  142. package/.next/standalone/.next/server/app/login.rsc +1 -1
  143. package/.next/standalone/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  144. package/.next/standalone/.next/server/app/onboarding.html +2 -2
  145. package/.next/standalone/.next/server/app/onboarding.rsc +1 -1
  146. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  147. package/.next/standalone/.next/server/app/projects.html +2 -2
  148. package/.next/standalone/.next/server/app/projects.rsc +1 -1
  149. package/.next/standalone/.next/server/app/settings.html +2 -2
  150. package/.next/standalone/.next/server/app/settings.rsc +1 -1
  151. package/.next/standalone/.next/server/app-paths-manifest.json +34 -34
  152. package/.next/standalone/.next/server/chunks/6515.js +2 -2
  153. package/.next/standalone/.next/server/chunks/8454.js +1 -1
  154. package/.next/standalone/.next/server/middleware-manifest.json +1 -1
  155. package/.next/standalone/.next/server/pages/404.html +2 -2
  156. package/.next/standalone/.next/server/pages/500.html +1 -1
  157. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  158. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  159. package/.next/standalone/package.json +1 -1
  160. package/README.md +2 -0
  161. package/cli/client-args.mjs +15 -8
  162. package/cli/daemon-agent.mjs +56 -6
  163. package/cli/daemon-install-config.mjs +129 -0
  164. package/cli/daemon.mjs +23 -1
  165. package/package.json +1 -1
  166. /package/.next/standalone/.next/static/{3R5zOQKwCZOVs9-BjIVqk → WVmgQzimrMNvGtHx6Cqm1}/_buildManifest.js +0 -0
  167. /package/.next/standalone/.next/static/{3R5zOQKwCZOVs9-BjIVqk → WVmgQzimrMNvGtHx6Cqm1}/_ssgManifest.js +0 -0
@@ -3,7 +3,18 @@
3
3
  // `claude-code` (the default), `codex`, and `kiro` are all implemented backends
4
4
  // (add-daemon-codex-backend cashed in the reserved `codex` slot;
5
5
  // add-daemon-kiro-backend adds `kiro`). Resolving an unknown value is a hard
6
- // error (no silent fallback). Zero dependencies.
6
+ // error (no silent fallback).
7
+ //
8
+ // Resolution is layered like resolveSigintTimeoutMs / resolveDaemonCwds in
9
+ // daemon-config.mjs — first defined source wins:
10
+ // --agent flag > CHORUS_AGENT env > ~/.chorus/daemon.json `agent` > claude-code.
11
+ // The config-file layer lets an operator persist a default backend (e.g.
12
+ // `"agent": "codex"`) once instead of re-passing --agent / re-exporting the env
13
+ // on every start. IO (file read) is injectable so this stays unit-testable
14
+ // without real disk.
15
+
16
+ import { readFileSync } from "node:fs";
17
+ import { loginFilePath } from "./credentials.mjs";
7
18
 
8
19
  /** The agent backends the daemon recognizes. All are implemented (claude-code
9
20
  * via ClaudeSpawner, codex via CodexSpawner, kiro via KiroSpawner — see
@@ -42,19 +53,58 @@ export function backendClientType(agentType) {
42
53
  }
43
54
 
44
55
  /**
45
- * Resolve the agent type from flag env default, and validate it.
46
- * Precedence: explicit `--agent` flag > CHORUS_AGENT env > DEFAULT_AGENT.
56
+ * Read a JSON file, returning `null` on any error (missing / unreadable /
57
+ * malformed). Never throws mirrors daemon-config.mjs readJsonSafe.
58
+ * @param {string} path
59
+ * @returns {Record<string, unknown> | null}
60
+ */
61
+ function readJsonSafe(path) {
62
+ try {
63
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
64
+ return parsed && typeof parsed === "object" ? parsed : null;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ /** A non-empty trimmed string, or undefined. */
71
+ function nonEmpty(value) {
72
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
73
+ }
74
+
75
+ /**
76
+ * Resolve the agent type from flag → env → config file → default, and validate it.
77
+ * Precedence (first defined source wins, mirroring resolveSigintTimeoutMs /
78
+ * resolveDaemonCwds): explicit `--agent` flag > CHORUS_AGENT env >
79
+ * ~/.chorus/daemon.json `agent` > DEFAULT_AGENT.
80
+ *
81
+ * An unknown value from ANY source (flag, env, or file) is a hard error — no
82
+ * silent fallback. The config file lets an operator persist a default backend
83
+ * without re-passing --agent on every start.
47
84
  *
48
85
  * @param {{ agent?: string }} flags
49
86
  * @param {Record<string, string|undefined>} env
87
+ * @param {{
88
+ * readJson?: (path: string) => (Record<string, unknown>|null),
89
+ * loginPath?: string,
90
+ * }} [deps]
50
91
  * @returns {{ ok: true, agent: string } | { ok: false, value: string, error: string }}
51
92
  * `ok:false` carries the offending value and a human-actionable error naming
52
93
  * the accepted types — the caller exits non-zero and prints `error`.
53
94
  */
54
- export function resolveAgentType(flags, env) {
95
+ export function resolveAgentType(flags, env, deps = {}) {
96
+ const readJson = deps.readJson ?? readJsonSafe;
97
+ const loginPath = deps.loginPath ?? loginFilePath();
98
+
99
+ const fromFile = () => {
100
+ const file = readJson(loginPath);
101
+ return file ? nonEmpty(file.agent) : undefined;
102
+ };
103
+
55
104
  const raw =
56
- (typeof flags.agent === "string" && flags.agent.trim()) ||
57
- (typeof env.CHORUS_AGENT === "string" && env.CHORUS_AGENT.trim()) ||
105
+ nonEmpty(flags.agent) ||
106
+ nonEmpty(env.CHORUS_AGENT) ||
107
+ fromFile() ||
58
108
  DEFAULT_AGENT;
59
109
  if (!KNOWN_AGENTS.includes(raw)) {
60
110
  return {
@@ -26,6 +26,11 @@ import { resolveCredentials, loginFilePath } from "./credentials.mjs";
26
26
  import { updateDaemonConfig, prompt as defaultPrompt } from "./login.mjs";
27
27
  import { validateAndFetchIdentity } from "./chorus-client.mjs";
28
28
  import { normalizeCwd, cleanCwdList } from "./daemon-config.mjs";
29
+ import { KNOWN_AGENTS, DEFAULT_AGENT } from "./daemon-agent.mjs";
30
+ import { agentNotFoundWarningLine } from "./daemon-banner.mjs";
31
+ import { resolveClaudePath } from "./claude-spawner.mjs";
32
+ import { resolveCodexPath } from "./codex-spawner.mjs";
33
+ import { resolveKiroPath } from "./kiro-spawner.mjs";
29
34
  import { readFileSync } from "node:fs";
30
35
 
31
36
  /** A non-empty trimmed string, or undefined. */
@@ -211,3 +216,127 @@ export async function resolveInstallCwds(flags = {}, opts = {}) {
211
216
  if (finalCwds.length > 0) writeConfig({ cwds: finalCwds });
212
217
  return { cwds: finalCwds };
213
218
  }
219
+
220
+ /**
221
+ * The interactive agent-backend menu. Order mirrors KNOWN_AGENTS with claude-code
222
+ * first (it is the default). Kept beside the resolver so the numbered prompt and
223
+ * the accepted values never drift.
224
+ */
225
+ const AGENT_MENU = [
226
+ { value: "claude-code", label: "Claude Code (default)" },
227
+ { value: "codex", label: "Codex CLI" },
228
+ { value: "kiro", label: "Kiro CLI" },
229
+ ];
230
+
231
+ /** Probe the selected backend's CLI on PATH. Returns the resolved path or null.
232
+ * Injectable per backend so the check is testable without a real PATH. */
233
+ function probeAgentCli(agent, probes = {}) {
234
+ const findClaude = probes.resolveClaudePath ?? resolveClaudePath;
235
+ const findCodex = probes.resolveCodexPath ?? resolveCodexPath;
236
+ const findKiro = probes.resolveKiroPath ?? resolveKiroPath;
237
+ if (agent === "codex") return findCodex();
238
+ if (agent === "kiro") return findKiro();
239
+ return findClaude();
240
+ }
241
+
242
+ /**
243
+ * Determine which local agent backend the installed daemon wakes and persist it
244
+ * to ~/.chorus/daemon.json `agent` (the single source of truth — the unit carries
245
+ * no --agent, exactly like `cwds`). AFTER resolving, probe the selected backend's
246
+ * CLI on PATH and warn (non-fatal) when it is missing, so the operator learns at
247
+ * install time rather than when the first wake fails.
248
+ *
249
+ * Resolution (first defined source wins):
250
+ * 1. --agent flag — the operator's explicit choice.
251
+ * 2. CHORUS_AGENT env — matches the daemon's own resolution precedence.
252
+ * 3. existing daemon.json `agent` — a prior valid choice is kept (re-install is
253
+ * idempotent and does not re-prompt); an invalid stored value is ignored.
254
+ * 4. TTY + !skip — interactive numbered menu (Enter = claude-code default).
255
+ * 5. non-TTY / skip — DEFAULT_AGENT (claude-code), persisted so the config
256
+ * is explicit about the backend the boot service runs.
257
+ *
258
+ * Never fails: --agent / CHORUS_AGENT are already validated upstream by
259
+ * resolveAgentType (runDaemon rejects an unknown value before dispatch), the menu
260
+ * is constrained to KNOWN_AGENTS, and default/stored-invalid fall back to
261
+ * claude-code. Returns the resolved `{ agent, cliPath, cliFound }`. Never throws.
262
+ *
263
+ * @param {{ agent?: string }} flags
264
+ * @param {Record<string, string|undefined>} [env]
265
+ * @param {{
266
+ * isTTY?: boolean, skip?: boolean,
267
+ * readJson?: (path: string) => (Record<string, unknown>|null),
268
+ * loginPath?: string,
269
+ * writeConfig?: (partial: Record<string, unknown>) => string,
270
+ * prompt?: typeof defaultPrompt,
271
+ * probes?: { resolveClaudePath?: Function, resolveCodexPath?: Function, resolveKiroPath?: Function },
272
+ * log?: (m: string) => void,
273
+ * errLog?: (m: string) => void,
274
+ * }} [opts]
275
+ * @returns {Promise<{ agent: string, cliPath: string|null, cliFound: boolean }>}
276
+ */
277
+ export async function resolveInstallAgent(flags = {}, env = {}, opts = {}) {
278
+ const isTTY = opts.isTTY ?? false;
279
+ const skip = opts.skip ?? false;
280
+ const readJson = opts.readJson ?? readJsonSafe;
281
+ const loginPath = opts.loginPath ?? loginFilePath();
282
+ const writeConfig = opts.writeConfig ?? updateDaemonConfig;
283
+ const ask = opts.prompt ?? defaultPrompt;
284
+ const log = opts.log ?? (() => {});
285
+ const errLog = opts.errLog ?? (() => {});
286
+
287
+ // Only a KNOWN backend is ever accepted; anything else is treated as absent.
288
+ const known = (v) => (v && KNOWN_AGENTS.includes(v) ? v : undefined);
289
+
290
+ let agent;
291
+ let persist = true; // write unless we reuse an already-stored value (no-op)
292
+
293
+ // 1/2. Explicit --agent flag or CHORUS_AGENT env (both pre-validated upstream).
294
+ agent = known(nonEmpty(flags.agent)) ?? known(nonEmpty(env.CHORUS_AGENT));
295
+
296
+ // 3. Existing daemon.json `agent` — a prior valid choice is authoritative; keep
297
+ // it and do not re-prompt (re-install stays idempotent). Skip the re-write.
298
+ if (!agent) {
299
+ const file = readJson(loginPath);
300
+ const stored = known(file ? nonEmpty(file.agent) : undefined);
301
+ if (stored) {
302
+ agent = stored;
303
+ persist = false; // already on disk — no-op write avoided
304
+ }
305
+ }
306
+
307
+ // 4. Interactive numbered menu on a TTY (Enter accepts the claude-code default).
308
+ if (!agent && isTTY && !skip) {
309
+ log("[Chorus] Which local agent backend should this daemon wake?");
310
+ for (let i = 0; i < AGENT_MENU.length; i += 1) {
311
+ log(`[Chorus] ${i + 1}) ${AGENT_MENU[i].label}`);
312
+ }
313
+ const answer = nonEmpty(await ask(`Select [1-${AGENT_MENU.length}] (Enter for 1): `));
314
+ if (!answer) {
315
+ agent = DEFAULT_AGENT;
316
+ } else {
317
+ const n = Number(answer);
318
+ const byNumber = Number.isInteger(n) && n >= 1 && n <= AGENT_MENU.length ? AGENT_MENU[n - 1].value : undefined;
319
+ // Also accept the value typed by name (e.g. "codex") for muscle memory.
320
+ agent = byNumber ?? known(answer) ?? DEFAULT_AGENT;
321
+ }
322
+ }
323
+
324
+ // 5. Non-TTY / skip with nothing configured → the default, persisted so the boot
325
+ // service config is explicit about the backend it runs.
326
+ if (!agent) agent = DEFAULT_AGENT;
327
+
328
+ if (persist) writeConfig({ agent });
329
+
330
+ // Probe the selected backend's CLI (non-fatal). A missing binary warns loudly so
331
+ // the operator fixes PATH before the first wake, but never blocks the install.
332
+ const cliPath = probeAgentCli(agent, opts.probes);
333
+ const cliFound = cliPath !== null;
334
+ if (cliFound) {
335
+ log(`[Chorus] agent backend: ${agent} (found CLI at ${cliPath}).`);
336
+ } else {
337
+ log(`[Chorus] agent backend: ${agent}.`);
338
+ errLog(`[Chorus] ${agentNotFoundWarningLine(agent)}`);
339
+ }
340
+
341
+ return { agent, cliPath, cliFound };
342
+ }
package/cli/daemon.mjs CHANGED
@@ -17,6 +17,7 @@ import { prompt, writeLoginFile, updateDaemonConfig } from "./login.mjs";
17
17
  import {
18
18
  resolveInstallCredentials,
19
19
  resolveInstallCwds,
20
+ resolveInstallAgent,
20
21
  } from "./daemon-install-config.mjs";
21
22
  import {
22
23
  resolvePermissionMode,
@@ -777,6 +778,7 @@ export async function handleLifecycleAction(action, { log, errLog, lifecycle, se
777
778
  const installCfg = svc.installConfig ?? {
778
779
  resolveInstallCredentials,
779
780
  resolveInstallCwds,
781
+ resolveInstallAgent,
780
782
  };
781
783
 
782
784
  const credResult = await installCfg.resolveInstallCredentials(flags, env, {
@@ -810,10 +812,30 @@ export async function handleLifecycleAction(action, { log, errLog, lifecycle, se
810
812
  log,
811
813
  });
812
814
 
815
+ // Configure + persist the agent backend to daemon.json `agent` (single source
816
+ // of truth; the unit carries no --agent, exactly like --cwd). Prompts an
817
+ // interactive menu on a TTY (Enter = claude-code); reuses a --agent flag /
818
+ // CHORUS_AGENT env / a prior stored choice; probes the selected CLI and warns
819
+ // (non-fatal) when it is missing. An unknown --agent was already rejected by
820
+ // resolveAgentType above, so this phase never fails.
821
+ await installCfg.resolveInstallAgent(flags, env, {
822
+ isTTY,
823
+ skip,
824
+ writeConfig: pfDeps?.writeConfig ?? updateDaemonConfig,
825
+ readJson: pfDeps?.readJson,
826
+ loginPath: pfDeps?.loginPath,
827
+ prompt: pfDeps?.askPrompt,
828
+ log,
829
+ errLog,
830
+ });
831
+
813
832
  const spec = {
814
833
  ...svc.resolveServicePaths(),
815
834
  cwds: flags.cwd ?? [],
816
- agent: flags.agent,
835
+ // NOTE: agent is deliberately NOT passed into the unit. The selected backend
836
+ // is persisted to daemon.json `agent` (single source of truth) and read back
837
+ // by the daemon via resolveAgentType — mirroring how --cwd is handled. Baking
838
+ // --agent into ExecStart too would let the two sources drift.
817
839
  chorusOnly: flags.chorusOnly === true,
818
840
  workingDir: process.cwd(),
819
841
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chorus-aidlc/chorus",
3
- "version": "0.14.3",
3
+ "version": "0.14.4",
4
4
  "description": "The Agent Harness for AI-Human Collaboration — session lifecycle, task state, sub-agent orchestration, observability, and failure recovery",
5
5
  "homepage": "https://github.com/Chorus-AIDLC/Chorus",
6
6
  "repository": {