@cohortapp/agent-sdk 2.17.0 → 2.18.5

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 (531) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +18 -5
  3. package/README.md +1 -0
  4. package/bin/maestro.mjs +62 -0
  5. package/docs/guides/billing-console-keys.md +60 -0
  6. package/docs/guides/front-door-session.md +54 -9
  7. package/docs/guides/mac-mini.md +20 -25
  8. package/docs/guides/setup-wizard.md +1 -1
  9. package/docs/runbooks/fleet-rollout.md +156 -0
  10. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  11. package/lib/action-executor.js +19 -3
  12. package/lib/budget-guard.mjs +279 -3
  13. package/lib/channels/base-adapter.mjs +3 -1
  14. package/lib/channels/contract.mjs +2 -1
  15. package/lib/channels/inbox-item.mjs +8 -0
  16. package/lib/claude-bin.mjs +5 -6
  17. package/lib/cli/doctor-checks.mjs +141 -10
  18. package/lib/cli/global-setup-extras.mjs +5 -1
  19. package/lib/cli/inbox.mjs +100 -15
  20. package/lib/cli/seat-auth.mjs +463 -0
  21. package/lib/cli/session.mjs +80 -12
  22. package/lib/collective/capture-slots.mjs +234 -0
  23. package/lib/collective/capture.mjs +8 -6
  24. package/lib/collective/config.mjs +2 -0
  25. package/lib/collective/global-config.mjs +63 -1
  26. package/lib/collective/loop-guard.mjs +155 -0
  27. package/lib/collective/presence.mjs +142 -5
  28. package/lib/comms/send-gate.mjs +559 -1
  29. package/lib/diagnostics/alerts.mjs +49 -0
  30. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  31. package/lib/engine/agents/definitions.mjs +343 -0
  32. package/lib/engine/agents/persist.mjs +275 -0
  33. package/lib/engine/agents/runtime.mjs +748 -0
  34. package/lib/engine/agents/usage.mjs +95 -0
  35. package/lib/engine/auth-status.mjs +139 -0
  36. package/lib/engine/budget.mjs +194 -0
  37. package/lib/engine/cli.mjs +1204 -0
  38. package/lib/engine/commands/index.mjs +269 -0
  39. package/lib/engine/context/budget.mjs +219 -0
  40. package/lib/engine/context/cache.mjs +125 -0
  41. package/lib/engine/context/child-env.mjs +215 -0
  42. package/lib/engine/context/compaction.mjs +342 -0
  43. package/lib/engine/context/images.mjs +90 -0
  44. package/lib/engine/context/instructions.mjs +327 -0
  45. package/lib/engine/context/lazy-instructions.mjs +169 -0
  46. package/lib/engine/context/manager.mjs +182 -0
  47. package/lib/engine/context/real-path.mjs +91 -0
  48. package/lib/engine/context/secret-values.mjs +163 -0
  49. package/lib/engine/context/settings.mjs +274 -0
  50. package/lib/engine/context/stream-input.mjs +159 -0
  51. package/lib/engine/guard.mjs +152 -0
  52. package/lib/engine/hooks.mjs +713 -0
  53. package/lib/engine/loop.mjs +560 -0
  54. package/lib/engine/mcp/client.mjs +254 -0
  55. package/lib/engine/mcp/config.mjs +301 -0
  56. package/lib/engine/mcp/http.mjs +201 -0
  57. package/lib/engine/mcp/index.mjs +146 -0
  58. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  59. package/lib/engine/mcp/naming.mjs +66 -0
  60. package/lib/engine/mcp/resources.mjs +89 -0
  61. package/lib/engine/mcp/results.mjs +133 -0
  62. package/lib/engine/mcp/stdio.mjs +137 -0
  63. package/lib/engine/mcp/supervisor.mjs +116 -0
  64. package/lib/engine/messages.mjs +104 -0
  65. package/lib/engine/output/json.mjs +164 -0
  66. package/lib/engine/output/stream-json.mjs +266 -0
  67. package/lib/engine/permissions.mjs +845 -0
  68. package/lib/engine/process-identity.mjs +164 -0
  69. package/lib/engine/process-tree.mjs +551 -0
  70. package/lib/engine/prompt.mjs +60 -0
  71. package/lib/engine/session/store.mjs +299 -0
  72. package/lib/engine/session-runtime/args.mjs +97 -0
  73. package/lib/engine/session-runtime/host.mjs +143 -0
  74. package/lib/engine/session-runtime/inbox.mjs +122 -0
  75. package/lib/engine/session-runtime/notifications.mjs +129 -0
  76. package/lib/engine/session-runtime/registry.mjs +328 -0
  77. package/lib/engine/session-runtime/runner.mjs +344 -0
  78. package/lib/engine/session-runtime/socket.mjs +212 -0
  79. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  80. package/lib/engine/skills/index.mjs +321 -0
  81. package/lib/engine/tools/bash-background.mjs +533 -0
  82. package/lib/engine/tools/bash.mjs +216 -0
  83. package/lib/engine/tools/edit.mjs +97 -0
  84. package/lib/engine/tools/glob.mjs +81 -0
  85. package/lib/engine/tools/grep.mjs +224 -0
  86. package/lib/engine/tools/index.mjs +84 -0
  87. package/lib/engine/tools/list-agents.mjs +32 -0
  88. package/lib/engine/tools/ls.mjs +127 -0
  89. package/lib/engine/tools/monitor.mjs +82 -0
  90. package/lib/engine/tools/notebook-edit.mjs +218 -0
  91. package/lib/engine/tools/read.mjs +103 -0
  92. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  93. package/lib/engine/tools/schema.mjs +144 -0
  94. package/lib/engine/tools/send-message.mjs +77 -0
  95. package/lib/engine/tools/session.mjs +70 -0
  96. package/lib/engine/tools/todo.mjs +144 -0
  97. package/lib/engine/tools/toolsearch.mjs +217 -0
  98. package/lib/engine/tools/walk.mjs +193 -0
  99. package/lib/engine/tools/web-switch.mjs +31 -0
  100. package/lib/engine/tools/webfetch-html.mjs +387 -0
  101. package/lib/engine/tools/webfetch-net.mjs +340 -0
  102. package/lib/engine/tools/webfetch.mjs +198 -0
  103. package/lib/engine/tools/websearch.mjs +91 -0
  104. package/lib/engine/tools/workflow.mjs +95 -0
  105. package/lib/engine/tools/write.mjs +76 -0
  106. package/lib/engine/tui/line-editor.mjs +137 -0
  107. package/lib/engine/tui/render.mjs +86 -0
  108. package/lib/engine/tui/tui.mjs +274 -0
  109. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  110. package/lib/engine/wire/effort.mjs +36 -0
  111. package/lib/engine/wire/errors.mjs +496 -0
  112. package/lib/engine/wire/http.mjs +441 -0
  113. package/lib/engine/wire/index.mjs +76 -0
  114. package/lib/engine/wire/openai-chat.mjs +332 -0
  115. package/lib/engine/wire/prompt-cache.mjs +79 -0
  116. package/lib/engine/wire/search.mjs +140 -0
  117. package/lib/engine/wire/sse.mjs +114 -0
  118. package/lib/engine/wire/stall.mjs +349 -0
  119. package/lib/engine/wire/token-provider.mjs +175 -0
  120. package/lib/engine/wire/usage.mjs +192 -0
  121. package/lib/engine/workflow/host.mjs +524 -0
  122. package/lib/engine/workflow/journal.mjs +188 -0
  123. package/lib/engine/workflow/json-schema.mjs +171 -0
  124. package/lib/engine/workflow/meta.mjs +329 -0
  125. package/lib/engine/workflow/notifications.mjs +52 -0
  126. package/lib/engine/workflow/runtime.mjs +447 -0
  127. package/lib/engine/workflow/sandbox.mjs +534 -0
  128. package/lib/engine/workflow/worker.mjs +141 -0
  129. package/lib/engine/workflow/worktree.mjs +74 -0
  130. package/lib/execution/disposition.mjs +1 -1
  131. package/lib/execution/intake.mjs +10 -0
  132. package/lib/execution/surface-policy.mjs +15 -0
  133. package/lib/learning/curator.mjs +8 -6
  134. package/lib/learning/reflect.mjs +8 -6
  135. package/lib/model-router/catalog/cohort.yaml +137 -0
  136. package/lib/model-router/catalog.mjs +118 -1
  137. package/lib/model-router/failover.mjs +67 -16
  138. package/lib/model-router/llm-task.mjs +39 -3
  139. package/lib/model-router/resolve.mjs +89 -3
  140. package/lib/model-router/spawn.mjs +46 -47
  141. package/lib/model-router/taxonomy.mjs +126 -4
  142. package/lib/org/cost-sync.mjs +141 -11
  143. package/lib/org/inbound/broadcast.mjs +289 -0
  144. package/lib/org/inbound/collective.mjs +375 -0
  145. package/lib/org/inbound/directedness.mjs +96 -8
  146. package/lib/org/inbound/facts.mjs +78 -2
  147. package/lib/org/inbound/project.mjs +22 -0
  148. package/lib/org/inbound/surfaces.mjs +14 -0
  149. package/lib/org/llm-token.mjs +879 -0
  150. package/lib/org/mesh.mjs +61 -0
  151. package/lib/org/messaging.mjs +3 -1
  152. package/lib/org/protocol.checksum +1 -1
  153. package/lib/org/protocol.mjs +15 -0
  154. package/lib/org/quota.mjs +520 -0
  155. package/lib/org/tool-surface.mjs +104 -16
  156. package/lib/org/ui-parity.mjs +16 -1
  157. package/lib/org/work-ledger.mjs +37 -6
  158. package/lib/rate-guard.mjs +114 -1
  159. package/lib/resource-governor.mjs +41 -6
  160. package/lib/runtime/adapter.mjs +833 -0
  161. package/lib/runtime/child-env.mjs +191 -0
  162. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  163. package/lib/runtime/seat-engine.mjs +162 -0
  164. package/lib/session/ask-ledger.mjs +271 -0
  165. package/lib/session/current-work.mjs +676 -0
  166. package/lib/session/feed-core.mjs +40 -3
  167. package/lib/session/launch-args.mjs +56 -4
  168. package/lib/session/status-summary.mjs +26 -9
  169. package/lib/session/upgrade-notice.mjs +42 -0
  170. package/lib/setup/claude-probe.mjs +117 -13
  171. package/lib/setup/enrich.mjs +13 -10
  172. package/lib/setup/sections/model.mjs +39 -13
  173. package/lib/telemetry/collect.mjs +229 -11
  174. package/lib/upgrade/ignored-drift.mjs +105 -0
  175. package/lib/voice/post-call-brief.mjs +30 -17
  176. package/package.json +13 -3
  177. package/plugins/maestro-skills/skills/board-work.md +5 -0
  178. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  179. package/plugins/maestro-skills/skills/main-session.md +18 -7
  180. package/scaffold/config/collective.yaml +7 -0
  181. package/scripts/ci/check-durable-write-seam.mjs +3 -1
  182. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  183. package/scripts/ci/run-tests.mjs +47 -19
  184. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  185. package/scripts/collective/hook-runner.mjs +142 -19
  186. package/scripts/continuous-monitor.sh +13 -0
  187. package/scripts/cost/track-claude-usage.mjs +15 -0
  188. package/scripts/daemon/agent-daemon.mjs +408 -20
  189. package/scripts/daemon/assurance.mjs +48 -12
  190. package/scripts/daemon/cadence-consumer.mjs +218 -68
  191. package/scripts/daemon/cadence-handlers.mjs +73 -4
  192. package/scripts/daemon/classifier.mjs +75 -26
  193. package/scripts/daemon/context-compiler.mjs +51 -37
  194. package/scripts/daemon/deliver.mjs +30 -1
  195. package/scripts/daemon/dispatcher.mjs +595 -149
  196. package/scripts/daemon/health.mjs +14 -1
  197. package/scripts/daemon/maestro-daemon.mjs +11 -0
  198. package/scripts/daemon/prompt-builder.mjs +24 -0
  199. package/scripts/daemon/responder.mjs +246 -79
  200. package/scripts/daemon/sdk-version.mjs +98 -16
  201. package/scripts/eval/probe-gateway.mjs +635 -0
  202. package/scripts/eval/replay/extract.mjs +270 -0
  203. package/scripts/eval/replay/grade.mjs +260 -0
  204. package/scripts/eval/replay/lib/config.mjs +50 -0
  205. package/scripts/eval/replay/lib/effects.mjs +65 -0
  206. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  207. package/scripts/eval/replay/lib/judge.mjs +72 -0
  208. package/scripts/eval/replay/lib/redact.mjs +136 -0
  209. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  210. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  211. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  212. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  213. package/scripts/eval/replay/report.mjs +185 -0
  214. package/scripts/eval/replay/run.mjs +404 -0
  215. package/scripts/fleet/rollout.mjs +1151 -0
  216. package/scripts/hooks/pre-send-audit.sh +36 -245
  217. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  218. package/scripts/hooks/validate-state-yaml.sh +190 -0
  219. package/scripts/huddle/huddle-llm.mjs +361 -0
  220. package/scripts/huddle/huddle-server.mjs +46 -121
  221. package/scripts/local-triggers/autoupdate.sh +465 -81
  222. package/scripts/local-triggers/run-trigger.sh +13 -0
  223. package/scripts/maintenance/pin-integrity.mjs +364 -0
  224. package/scripts/poll-slack-events.sh +41 -9
  225. package/scripts/poller/slack-socket-mode.mjs +28 -3
  226. package/scripts/session/supervisor.mjs +80 -13
  227. package/scripts/spawn-session.sh +13 -0
  228. package/bin/maestro.test.mjs +0 -1574
  229. package/lib/action-executor.test.mjs +0 -871
  230. package/lib/archetype.test.mjs +0 -132
  231. package/lib/assurance/plan-note.test.mjs +0 -234
  232. package/lib/assurance/room-budget.test.mjs +0 -486
  233. package/lib/assurance/tier.test.mjs +0 -174
  234. package/lib/autonomy.test.mjs +0 -66
  235. package/lib/backlog.test.mjs +0 -302
  236. package/lib/backup/policy.test.mjs +0 -305
  237. package/lib/budget-escalate.test.mjs +0 -232
  238. package/lib/budget-guard.envelope.test.mjs +0 -476
  239. package/lib/budget-guard.test.mjs +0 -427
  240. package/lib/cadence-bus-requeue.test.mjs +0 -83
  241. package/lib/cadence-bus-schedule.test.mjs +0 -194
  242. package/lib/cadence-bus.test.mjs +0 -720
  243. package/lib/cadences.test.mjs +0 -230
  244. package/lib/capability/inventory.test.mjs +0 -232
  245. package/lib/capability.test.mjs +0 -78
  246. package/lib/channels/base-adapter.test.mjs +0 -590
  247. package/lib/channels/channels.test.mjs +0 -371
  248. package/lib/channels/contract.test.mjs +0 -162
  249. package/lib/channels/inbox-item.test.mjs +0 -368
  250. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  251. package/lib/channels/pairing.test.mjs +0 -270
  252. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  253. package/lib/channels/slack-adapter.test.mjs +0 -212
  254. package/lib/channels/telegram-adapter.test.mjs +0 -306
  255. package/lib/channels/voice/adapter.test.mjs +0 -278
  256. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  257. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  258. package/lib/charter.test.mjs +0 -89
  259. package/lib/claude-bin.test.mjs +0 -131
  260. package/lib/cli/board.test.mjs +0 -227
  261. package/lib/cli/design.test.mjs +0 -270
  262. package/lib/cli/doctor-checks.test.mjs +0 -336
  263. package/lib/cli/global-setup-extras.test.mjs +0 -462
  264. package/lib/cli/inbox.test.mjs +0 -230
  265. package/lib/cli/session-ack.test.mjs +0 -63
  266. package/lib/cli/session.test.mjs +0 -613
  267. package/lib/collective/capture.test.mjs +0 -121
  268. package/lib/collective/cards.test.mjs +0 -114
  269. package/lib/collective/config.test.mjs +0 -123
  270. package/lib/collective/global-config.test.mjs +0 -220
  271. package/lib/collective/global-skills.test.mjs +0 -126
  272. package/lib/collective/presence.test.mjs +0 -95
  273. package/lib/collective/recall.test.mjs +0 -116
  274. package/lib/collective/vendor-skills.test.mjs +0 -306
  275. package/lib/comms/send-gate.test.mjs +0 -770
  276. package/lib/comms.test.mjs +0 -41
  277. package/lib/context/budget.test.mjs +0 -252
  278. package/lib/context/history-scope.test.mjs +0 -79
  279. package/lib/cost/ledger-row.test.mjs +0 -183
  280. package/lib/design/design-md.test.mjs +0 -318
  281. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  282. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  283. package/lib/design/fixtures/foundation.json +0 -133
  284. package/lib/design/refresh-gate.test.mjs +0 -144
  285. package/lib/design/write.test.mjs +0 -241
  286. package/lib/diagnostics/alerts.test.mjs +0 -318
  287. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  288. package/lib/diagnostics/counters.test.mjs +0 -206
  289. package/lib/diagnostics/events.test.mjs +0 -290
  290. package/lib/diagnostics/otel.test.mjs +0 -196
  291. package/lib/diagnostics/trace.test.mjs +0 -251
  292. package/lib/env-compat.test.mjs +0 -104
  293. package/lib/execution/disposition.test.mjs +0 -553
  294. package/lib/execution/drive.test.mjs +0 -270
  295. package/lib/execution/effects.test.mjs +0 -344
  296. package/lib/execution/intake.test.mjs +0 -389
  297. package/lib/execution/journal.test.mjs +0 -261
  298. package/lib/execution/match.test.mjs +0 -235
  299. package/lib/execution/pipeline.test.mjs +0 -392
  300. package/lib/execution/route.test.mjs +0 -186
  301. package/lib/execution/surface-policy.test.mjs +0 -162
  302. package/lib/fs-atomic.test.mjs +0 -72
  303. package/lib/fs-ownership.test.mjs +0 -158
  304. package/lib/goals/admission.test.mjs +0 -164
  305. package/lib/goals/classify.test.mjs +0 -167
  306. package/lib/goals/collaborate.test.mjs +0 -336
  307. package/lib/goals/gaps.test.mjs +0 -284
  308. package/lib/goals/loop.test.mjs +0 -845
  309. package/lib/hooks/bus.test.mjs +0 -387
  310. package/lib/identity/persona.test.mjs +0 -142
  311. package/lib/kpi-sensors.test.mjs +0 -278
  312. package/lib/kpi.test.mjs +0 -244
  313. package/lib/learning/config.test.mjs +0 -75
  314. package/lib/learning/counters.test.mjs +0 -69
  315. package/lib/learning/curator-consolidate.test.mjs +0 -238
  316. package/lib/learning/curator.test.mjs +0 -106
  317. package/lib/learning/reflect.test.mjs +0 -0
  318. package/lib/learning/session-index.test.mjs +0 -125
  319. package/lib/learning/skill-writer.test.mjs +0 -210
  320. package/lib/mandate/audit.test.mjs +0 -195
  321. package/lib/mandate/contract.test.mjs +0 -185
  322. package/lib/mandate/derive.test.mjs +0 -274
  323. package/lib/mandate/model.test.mjs +0 -164
  324. package/lib/mandate/refresh.test.mjs +0 -389
  325. package/lib/mcp/server.test.mjs +0 -426
  326. package/lib/model-router/auth-profiles.test.mjs +0 -580
  327. package/lib/model-router/catalog.test.mjs +0 -385
  328. package/lib/model-router/economics.test.mjs +0 -438
  329. package/lib/model-router/failover.test.mjs +0 -439
  330. package/lib/model-router/health.test.mjs +0 -338
  331. package/lib/model-router/integration-coverage.test.mjs +0 -831
  332. package/lib/model-router/integration.test.mjs +0 -564
  333. package/lib/model-router/ledger.test.mjs +0 -415
  334. package/lib/model-router/llm-task.test.mjs +0 -392
  335. package/lib/model-router/org-credentials.test.mjs +0 -265
  336. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  337. package/lib/model-router/reconcile.test.mjs +0 -316
  338. package/lib/model-router/repair.test.mjs +0 -180
  339. package/lib/model-router/spawn.test.mjs +0 -446
  340. package/lib/model-router/taxonomy.test.mjs +0 -410
  341. package/lib/model-router.test.mjs +0 -1207
  342. package/lib/org/activity.test.mjs +0 -134
  343. package/lib/org/approvals.test.mjs +0 -216
  344. package/lib/org/awareness.test.mjs +0 -159
  345. package/lib/org/board-mine-cache.test.mjs +0 -53
  346. package/lib/org/board.test.mjs +0 -187
  347. package/lib/org/bootstrap-context.test.mjs +0 -153
  348. package/lib/org/client.test.mjs +0 -1206
  349. package/lib/org/cohort-client.test.mjs +0 -126
  350. package/lib/org/cost-sync.test.mjs +0 -153
  351. package/lib/org/doctor.test.mjs +0 -346
  352. package/lib/org/engagement-ledger.test.mjs +0 -112
  353. package/lib/org/engagement.test.mjs +0 -739
  354. package/lib/org/handoff.test.mjs +0 -269
  355. package/lib/org/inbound/directedness.test.mjs +0 -668
  356. package/lib/org/inbound/facts.test.mjs +0 -471
  357. package/lib/org/inbound/hydrate.test.mjs +0 -908
  358. package/lib/org/inbound/index.test.mjs +0 -429
  359. package/lib/org/inbound/project.test.mjs +0 -287
  360. package/lib/org/integration-tools.test.mjs +0 -160
  361. package/lib/org/keys.test.mjs +0 -92
  362. package/lib/org/knowledge.test.mjs +0 -326
  363. package/lib/org/leases.test.mjs +0 -235
  364. package/lib/org/mesh-directives.test.mjs +0 -110
  365. package/lib/org/mesh-integration.test.mjs +0 -127
  366. package/lib/org/mesh.test.mjs +0 -400
  367. package/lib/org/messaging.test.mjs +0 -471
  368. package/lib/org/param-contract.test.mjs +0 -477
  369. package/lib/org/policy.test.mjs +0 -237
  370. package/lib/org/protocol.checksum.test.mjs +0 -90
  371. package/lib/org/protocol.test.mjs +0 -323
  372. package/lib/org/push.test.mjs +0 -792
  373. package/lib/org/registry.test.mjs +0 -100
  374. package/lib/org/resource-tools.test.mjs +0 -361
  375. package/lib/org/tool-access.test.mjs +0 -144
  376. package/lib/org/tool-surface-integration.test.mjs +0 -120
  377. package/lib/org/tool-surface.test.mjs +0 -1268
  378. package/lib/org/typing.test.mjs +0 -291
  379. package/lib/org/ui-parity.test.mjs +0 -560
  380. package/lib/org/verify.test.mjs +0 -194
  381. package/lib/org/work-ledger.test.mjs +0 -273
  382. package/lib/plan/adoption-e2e.test.mjs +0 -366
  383. package/lib/plan/budget-enforcement.test.mjs +0 -400
  384. package/lib/plan/compile.test.mjs +0 -382
  385. package/lib/plan/emit.test.mjs +0 -269
  386. package/lib/plan/explain.test.mjs +0 -188
  387. package/lib/prompts/parallelism.test.mjs +0 -177
  388. package/lib/rag/rag.test.mjs +0 -505
  389. package/lib/rate-guard.test.mjs +0 -272
  390. package/lib/reactive-gate.test.mjs +0 -57
  391. package/lib/render.test.mjs +0 -68
  392. package/lib/resource-governor.test.mjs +0 -488
  393. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  394. package/lib/scheduling/jitter.test.mjs +0 -140
  395. package/lib/secrets/broker.test.mjs +0 -280
  396. package/lib/secrets/providers.test.mjs +0 -274
  397. package/lib/security/audit-engine.test.mjs +0 -424
  398. package/lib/security/coerce-args.test.mjs +0 -281
  399. package/lib/security/dangerous-tools.test.mjs +0 -68
  400. package/lib/security/external-content.test.mjs +0 -84
  401. package/lib/security/redact.test.mjs +0 -441
  402. package/lib/security/secret-equal.test.mjs +0 -55
  403. package/lib/session/config.test.mjs +0 -92
  404. package/lib/session/feed-core.test.mjs +0 -198
  405. package/lib/session/first-run.test.mjs +0 -121
  406. package/lib/session/frontdoor.test.mjs +0 -205
  407. package/lib/session/handoffs.test.mjs +0 -183
  408. package/lib/session/identity.test.mjs +0 -180
  409. package/lib/session/inbox-claims.test.mjs +0 -286
  410. package/lib/session/launch-args.test.mjs +0 -157
  411. package/lib/session/liveness.test.mjs +0 -100
  412. package/lib/session/status-summary.test.mjs +0 -118
  413. package/lib/session-permissions.test.mjs +0 -120
  414. package/lib/setup/claude-probe.test.mjs +0 -187
  415. package/lib/setup/completeness.test.mjs +0 -110
  416. package/lib/setup/context-pack.test.mjs +0 -89
  417. package/lib/setup/enrich.test.mjs +0 -115
  418. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  419. package/lib/setup/integration.test.mjs +0 -162
  420. package/lib/setup/io.test.mjs +0 -77
  421. package/lib/setup/runner.test.mjs +0 -132
  422. package/lib/setup/sections/identity.test.mjs +0 -234
  423. package/lib/setup/sections/inventory.test.mjs +0 -198
  424. package/lib/setup/sections/learning.test.mjs +0 -81
  425. package/lib/setup/sections/mandate.test.mjs +0 -388
  426. package/lib/setup/sections/messaging.test.mjs +0 -127
  427. package/lib/setup/sections/model.test.mjs +0 -240
  428. package/lib/setup/sections/org.test.mjs +0 -346
  429. package/lib/setup/sections/orgmail.test.mjs +0 -118
  430. package/lib/setup/sections/recovery.test.mjs +0 -98
  431. package/lib/setup/sections/subagents.test.mjs +0 -429
  432. package/lib/setup/sections/verify.test.mjs +0 -175
  433. package/lib/setup/sot.test.mjs +0 -81
  434. package/lib/setup/state.test.mjs +0 -115
  435. package/lib/singleton.test.mjs +0 -151
  436. package/lib/subagents/cli.test.mjs +0 -389
  437. package/lib/subagents/client.test.mjs +0 -309
  438. package/lib/subagents/gap.test.mjs +0 -234
  439. package/lib/subagents/lock.test.mjs +0 -248
  440. package/lib/subagents/manifest.test.mjs +0 -175
  441. package/lib/subagents/refs.test.mjs +0 -204
  442. package/lib/subagents/resolve.test.mjs +0 -422
  443. package/lib/subagents/schema.test.mjs +0 -328
  444. package/lib/telemetry/alerts.test.mjs +0 -109
  445. package/lib/telemetry/collect.test.mjs +0 -1274
  446. package/lib/tool-definitions-integration.test.mjs +0 -83
  447. package/lib/tool-definitions.test.mjs +0 -437
  448. package/lib/upgrade/global-refresh.test.mjs +0 -65
  449. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  450. package/lib/upgrade/post-steps.test.mjs +0 -200
  451. package/lib/upgrade/verify.test.mjs +0 -164
  452. package/lib/util/fetch-timeout.test.mjs +0 -202
  453. package/lib/util/reconnect.test.mjs +0 -369
  454. package/lib/util/unhandled.test.mjs +0 -216
  455. package/lib/voice/outbound.test.mjs +0 -69
  456. package/lib/voice/session-rotation.test.mjs +0 -114
  457. package/lib/voice/stt.test.mjs +0 -226
  458. package/lib/voice/voice.test.mjs +0 -990
  459. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  460. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  461. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  462. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  463. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  464. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  465. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  466. package/scripts/ci/check.test.mjs +0 -194
  467. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  468. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  469. package/scripts/collective/hook-runner.test.mjs +0 -173
  470. package/scripts/cost/fleet-digest.test.mjs +0 -207
  471. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  472. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  473. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  474. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  475. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  476. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  477. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  478. package/scripts/daemon/assurance.test.mjs +0 -1791
  479. package/scripts/daemon/board-mirror.test.mjs +0 -165
  480. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  481. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  482. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  483. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  484. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  485. package/scripts/daemon/classifier.test.mjs +0 -266
  486. package/scripts/daemon/classify-kind.test.mjs +0 -40
  487. package/scripts/daemon/context-compiler.test.mjs +0 -406
  488. package/scripts/daemon/deliver.test.mjs +0 -564
  489. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  490. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  491. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  492. package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
  493. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  494. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  495. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  496. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  497. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  498. package/scripts/daemon/integration.test.mjs +0 -149
  499. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  500. package/scripts/daemon/lib/session-router.test.mjs +0 -554
  501. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  502. package/scripts/daemon/prompt-builder.test.mjs +0 -556
  503. package/scripts/daemon/responder-cost.test.mjs +0 -68
  504. package/scripts/daemon/responder-history.test.mjs +0 -221
  505. package/scripts/daemon/sdk-version.test.mjs +0 -31
  506. package/scripts/daemon/session-lock.test.mjs +0 -252
  507. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  508. package/scripts/daemon/typing-registry.test.mjs +0 -102
  509. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  510. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  511. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  512. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  513. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  514. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  515. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  516. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  517. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  518. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  519. package/scripts/poller-launchd/install.test.mjs +0 -243
  520. package/scripts/restore-from-backup.test.mjs +0 -181
  521. package/scripts/session/feed.test.mjs +0 -196
  522. package/scripts/session/supervisor-sh.test.mjs +0 -218
  523. package/scripts/session/supervisor.test.mjs +0 -482
  524. package/scripts/setup/configure-macos.test.mjs +0 -306
  525. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  526. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  527. package/scripts/setup/generate-capability.test.mjs +0 -134
  528. package/scripts/setup/init-agent.test.mjs +0 -370
  529. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  530. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  531. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -0,0 +1,833 @@
1
+ /**
2
+ * lib/runtime/adapter.mjs — the runtime adapter seam.
3
+ *
4
+ * The ONE owner of "which agent runtime, which binary, which argv, which env"
5
+ * for every JavaScript site that starts an agent runtime. A site describes what
6
+ * it wants (`lane`, `model`, `prompt`, …); `buildSpawn` answers with the exact
7
+ * `{bin, argv, env}` to hand to `spawn`/`execFile`/a multiplexer. Before this
8
+ * seam each of the 17 sites below built its own argv inline and five of them
9
+ * hand-built their own Claude auth env, so swapping the runtime meant editing
10
+ * ~20 files and re-deriving each one's credential rules (A1 §11).
11
+ *
12
+ * Engines
13
+ * -------
14
+ * claude the Claude Code CLI. Implemented; the default for every seat.
15
+ * cohort the Cohort Engine (lib/engine/cli.mjs `cohort run`). buildSpawn
16
+ * maps the lane's claude `--print` argv onto engine flags
17
+ * (`translateClaudeArgv`), runs it under this node, and hands it the
18
+ * gateway URL + a seat token with every Anthropic/Claude credential
19
+ * scrubbed. A flag the engine does not support yet is a returned
20
+ * `engine_flag_unsupported` naming the flag — never silently dropped
21
+ * (dropping `--allowedTools` would WIDEN a session's tools). No seat
22
+ * runs it by default (design §8.4: flip per seat behind parity gates).
23
+ * Outbound sends (CF-20): the engine screens and counts every
24
+ * `mcp__*__{messaging_send,…}` call with its in-process send gate,
25
+ * so this adapter mounts NO pre-send-audit hook for an engine spawn
26
+ * (it passes no hook-bearing --settings at all). The seat's global
27
+ * ~/.claude/settings.json still carries that hook for the claude
28
+ * binary; the engine's hook runner skips it for the gated tools
29
+ * (lib/engine/hooks.mjs `supersededBySendGate`), so a send is
30
+ * counted once.
31
+ *
32
+ * cohort-llm admission
33
+ * --------------------
34
+ * A session routed to cohort-llm (engine cohort, or the claude binary
35
+ * retargeted to the cohort provider) must not start when the seat's quota
36
+ * already refuses it: stock Claude Code retries a 429 up to ten times and
37
+ * ignores the gateway's quota headers (design C6), so the gate BEFORE the spawn
38
+ * is the barrier. `admitSpawn` (async) / `admitSpawnCached` (sync, from the
39
+ * quota cache) answer with a typed deferral the call site turns into a requeue.
40
+ *
41
+ * Lanes (the JS spawn sites, A1 §3)
42
+ * ---------------------------------
43
+ * dispatcher scripts/daemon/dispatcher.mjs spawnSession --print json, prompt argv, retargetable
44
+ * resume scripts/daemon/dispatcher.mjs buildResumeSpawn same shape as dispatcher
45
+ * cadence scripts/daemon/cadence-consumer.mjs --print json, prompt argv, retargetable
46
+ * responder scripts/daemon/responder.mjs runClaudeCLI --print json, prompt stdin
47
+ * classifier scripts/daemon/classifier.mjs runClaudeCLI --print, prompt stdin
48
+ * ack scripts/daemon/assurance.mjs runAckModel --print, prompt stdin
49
+ * lookup lib/action-executor.js executeLookup --print -p <prompt>
50
+ * capture lib/collective/capture.mjs defaultDistiller --print, prompt stdin
51
+ * reflect lib/learning/reflect.mjs defaultReviewSpawn --print, prompt stdin
52
+ * curator lib/learning/curator.mjs defaultConsolidateSpawn --print, prompt stdin
53
+ * enrich lib/setup/enrich.mjs defaultSpawn --print, prompt argv
54
+ * auth-status lib/setup/claude-probe.mjs probeSubscription `auth status --json`
55
+ * mcp-register lib/cli/global-setup-extras.mjs `mcp add-json … --scope user`
56
+ * main-session scripts/session/supervisor.mjs (launch-args) interactive, --name/--session-id|--resume
57
+ * peer lib/cli/session.mjs `session spawn` interactive, --name
58
+ * voice-brief lib/voice/post-call-brief.mjs briefSpawnSpec --print, prompt argv
59
+ * huddle scripts/huddle/huddle-llm.mjs huddleCliSpawn --print -p <prompt>
60
+ *
61
+ * Both voice lanes, and the lookup lane their tools reach, read the seat's
62
+ * engine per call (seat-engine resolveSeatSpawn, row 36). The huddle's direct API lane (HUDDLE_USE_API=1) is not a spawn: on
63
+ * a claude seat it is the Anthropic SDK, on an engine-cohort seat the engine
64
+ * wire to cohort-llm with the seat token (scripts/huddle/huddle-llm.mjs
65
+ * runHuddleApiTurn).
66
+ *
67
+ * Legacy shell lanes — NOT behind this seam
68
+ * -----------------------------------------
69
+ * Three shell scripts still run `claude` directly. They are legacy, manual or
70
+ * one-shot tools (no daemon lane depends on them). W4-E1 RETIRED them for
71
+ * engine-cohort seats: each calls lib/runtime/legacy-shell-guard.mjs first,
72
+ * which refuses (exit 3) when the seat's runtime.engine is cohort, and doctor
73
+ * warns on such seats. On a claude seat they run as before:
74
+ * scripts/spawn-session.sh:115-124 `claude -p "$SESSION_PROMPT" --output-format text`
75
+ * scripts/continuous-monitor.sh:48-57 `claude -p "<observe prompt>" --output-format text`
76
+ * scripts/local-triggers/run-trigger.sh:59 `"$CLAUDE_BIN" --print --dangerously-skip-permissions "$PROMPT"`
77
+ * See {@link SHELL_LEGACY_LANES}.
78
+ *
79
+ * Env postures
80
+ * ------------
81
+ * Every lane's env is composed here from ONE credential builder,
82
+ * `lib/runtime/child-env.mjs#buildRuntimeEnv`, for the retargetable lanes, and
83
+ * from one of a small set of named postures for the rest. The postures are the
84
+ * five hand-built envs the sites used to carry, byte-for-byte:
85
+ * stock-oauth {...env, PATH, ANTHROPIC_API_KEY:"", ANTHROPIC_AUTH_TOKEN:""}
86
+ * — strip stale API keys so the CLI rides the seat's
87
+ * subscription credential (dispatcher/resume/responder/classifier/ack).
88
+ * An API-key seat (no prefer flag, no OAuth token) keeps its
89
+ * ANTHROPIC_API_KEY: stripping it would leave no credential.
90
+ * cadence {...env, AGENT_ROOT, AGENT_DIR, PATH}, ANTHROPIC_API_KEY deleted
91
+ * when subscription auth is preferred or the key is a placeholder
92
+ * path {...env, PATH} (capture/reflect/curator)
93
+ * lookup {...env, TERM:"dumb", PATH} (lookup)
94
+ * huddle {...env, TERM:"dumb"} (huddle)
95
+ * agent-root {...env, AGENT_ROOT, AGENT_DIR} (voice-brief)
96
+ * inherit {...env} (enrich/auth-status/mcp-register/main-session/peer)
97
+ * A retarget (router envForSpawn carrying ANTHROPIC_BASE_URL) is accepted only
98
+ * on the three retargetable lanes and always goes through buildRuntimeEnv, which
99
+ * scrubs the OAuth token, forces the attribution header off and refuses a
100
+ * retarget with no third-party token.
101
+ *
102
+ * Results, not throws: an unavailable engine, an unsupported retarget or a
103
+ * refused retarget is a returned `{ok:false, error}`. An unknown lane is a
104
+ * programming defect and throws.
105
+ *
106
+ * @module lib/runtime/adapter
107
+ */
108
+
109
+ "use strict";
110
+
111
+ import { resolveClaudeBin, daemonClaudeArgs, augmentedPath } from "../claude-bin.mjs";
112
+ import { join, dirname } from "node:path";
113
+ import { readFileSync } from "node:fs";
114
+ import { fileURLToPath } from "node:url";
115
+
116
+ import { buildRuntimeEnv, isRetarget, isCohortHelperRetarget, COHORT_HELPER_KEY } from "./child-env.mjs";
117
+ import { buildClaudeArgs } from "../session/launch-args.mjs";
118
+ import { cohortTierFor } from "../model-router/catalog.mjs";
119
+ import { DEFAULT_LLM_BASE_URL, apiKeyHelperCommand } from "../org/llm-token.mjs";
120
+ import { COHORT_BACKEND } from "../rate-guard.mjs";
121
+ import { COLLECTIVE_CHILD_ENV } from "../collective/loop-guard.mjs";
122
+
123
+ /** Engines a seat can name in `runtime.engine`. */
124
+ export const ENGINES = Object.freeze(["claude", "cohort"]);
125
+ export const DEFAULT_ENGINE = "claude";
126
+
127
+ /** The legacy shell lanes that do not go through this seam (documented, not wired). */
128
+ export const SHELL_LEGACY_LANES = Object.freeze([
129
+ Object.freeze({ path: "scripts/spawn-session.sh", invocation: "claude -p \"$SESSION_PROMPT\" --output-format text" }),
130
+ Object.freeze({ path: "scripts/continuous-monitor.sh", invocation: "claude -p \"<observe prompt>\" --output-format text" }),
131
+ Object.freeze({ path: "scripts/local-triggers/run-trigger.sh", invocation: "\"$CLAUDE_BIN\" --print --dangerously-skip-permissions \"$PROMPT\"" }),
132
+ ]);
133
+
134
+ /** Placeholder API keys the cadence lane has always stripped. */
135
+ const PLACEHOLDER_API_KEY = /^(your-api-key|placeholder|xxx+|sk-ant-xxx)/i;
136
+
137
+ /**
138
+ * Pure: the engine a seat runs, from its agent config. `runtime.engine` must be
139
+ * one of {@link ENGINES}; anything else (absent, blank, unknown) is the default.
140
+ * @param {object} [agentConfig] config/agent.json
141
+ * @returns {"claude"|"cohort"}
142
+ */
143
+ export function resolveEngine(agentConfig) {
144
+ const v = agentConfig && agentConfig.runtime && typeof agentConfig.runtime.engine === "string"
145
+ ? agentConfig.runtime.engine.trim().toLowerCase()
146
+ : "";
147
+ return ENGINES.includes(v) ? v : DEFAULT_ENGINE;
148
+ }
149
+
150
+ /**
151
+ * The engine the seat at `agentRoot` runs, read from its config/agent.json.
152
+ * The edge twin of {@link resolveEngine}: the daemon's pre-spawn gates call it
153
+ * so a seat on engine cohort is metered and gated even when model routing
154
+ * never names the cohort provider.
155
+ *
156
+ * An absent or unparseable config answers the default engine — exactly what
157
+ * `resolveEngine(undefined)` answers, and what every spawn on such a seat runs.
158
+ * @param {string} agentRoot
159
+ * @param {{readFile?:(p:string)=>string}} [deps]
160
+ * @returns {"claude"|"cohort"}
161
+ */
162
+ export function readSeatEngine(agentRoot, deps = {}) {
163
+ const readFile = deps.readFile || ((p) => readFileSync(p, "utf-8"));
164
+ try {
165
+ return resolveEngine(JSON.parse(readFile(join(String(agentRoot || ""), "config", "agent.json"))));
166
+ } catch {
167
+ // No/invalid config/agent.json: the seat runs the default engine, so its
168
+ // gate is the default engine's gate (routing still decides cohort-ness).
169
+ return DEFAULT_ENGINE;
170
+ }
171
+ }
172
+
173
+ /** `--max-turns`/`--effort`/`--agents` from the router's spawn knobs. Order is fixed. */
174
+ function knobArgs(knobs) {
175
+ const out = [];
176
+ if (!knobs || typeof knobs !== "object") return out;
177
+ if (knobs.maxTurns != null && Number.isFinite(Number(knobs.maxTurns))) out.push("--max-turns", String(knobs.maxTurns));
178
+ if (knobs.effort) out.push("--effort", String(knobs.effort));
179
+ if (knobs.agentsJson) out.push("--agents", typeof knobs.agentsJson === "string" ? knobs.agentsJson : JSON.stringify(knobs.agentsJson));
180
+ return out;
181
+ }
182
+
183
+ /** The MCP flags for a lane: none, strict-only, or the daemon's per-source set. */
184
+ function mcpArgs(mcp, deps) {
185
+ if (mcp == null || mcp === false) return [];
186
+ if (mcp === "strict") return ["--strict-mcp-config"];
187
+ if (typeof mcp === "object") return deps.daemonArgs(mcp.agentRoot, mcp.source ? { source: mcp.source } : {});
188
+ return [];
189
+ }
190
+
191
+ const arr = (v) => (Array.isArray(v) ? v : []);
192
+ const nonEmpty = (v) => typeof v === "string" && v.trim() !== "";
193
+
194
+ /** Is this env on Claude subscription auth (prefer flag, or an OAuth token)? */
195
+ export function subscriptionAuth(env) {
196
+ const e = env && typeof env === "object" ? env : {};
197
+ return /^(1|true|yes)$/i.test(String(e.MAESTRO_PREFER_SUBSCRIPTION_AUTH || "").trim()) || nonEmpty(e.CLAUDE_CODE_OAUTH_TOKEN);
198
+ }
199
+
200
+ /**
201
+ * Lane table. `argv(i, x)` gets the normalised input and the computed flag
202
+ * fragments `x = {perm, mcp, knobs}`; `stdin` says whether the prompt is
203
+ * written to the child's stdin instead of argv; `posture` names the env shape;
204
+ * `retarget` says whether a router retarget is legal on the lane.
205
+ * `collectiveChild` marks the collective layer's own model passes: their env
206
+ * carries MAESTRO_COLLECTIVE_CHILD=1 so the global Stop/PreCompact hook does not
207
+ * capture the child's transcript (lib/collective/loop-guard.mjs) — without it,
208
+ * every capture spawns a session whose end spawns another capture.
209
+ */
210
+ const LANES = Object.freeze({
211
+ dispatcher: {
212
+ posture: "stock-oauth", stdin: false, retarget: true,
213
+ argv: (i, x) => ["--print", "--output-format", "json", ...x.settings, ...x.perm, ...x.mcp, ...x.knobs, "--session-id", i.sessionId, "--model", i.model, i.prompt],
214
+ },
215
+ resume: {
216
+ posture: "stock-oauth", stdin: false, retarget: true,
217
+ argv: (i, x) => ["--print", "--output-format", "json", ...x.settings, ...x.perm, ...x.mcp, ...x.knobs, "--session-id", i.sessionId, "--model", i.model, i.prompt],
218
+ },
219
+ cadence: {
220
+ posture: "cadence", stdin: false, retarget: true,
221
+ argv: (i, x) => ["--print", "--output-format", "json", ...x.settings, ...x.perm, ...x.mcp, ...x.knobs, ...(i.model ? ["--model", String(i.model)] : []), i.prompt],
222
+ },
223
+ responder: {
224
+ posture: "stock-oauth", stdin: true, retarget: false,
225
+ argv: (i, x) => ["--print", ...x.perm, ...x.mcp, "--model", i.model, "--append-system-prompt", i.systemPrompt, "--output-format", "json", ...(i.sessionId ? ["--session-id", i.sessionId] : [])],
226
+ },
227
+ classifier: {
228
+ posture: "stock-oauth", stdin: true, retarget: false,
229
+ argv: (i, x) => ["--print", ...x.perm, ...x.mcp, "--model", i.model, "--append-system-prompt", i.systemPrompt],
230
+ },
231
+ ack: {
232
+ posture: "stock-oauth", stdin: true, retarget: false,
233
+ argv: (i, x) => ["--print", ...x.perm, ...x.mcp, "--model", i.model, "--append-system-prompt", i.systemPrompt],
234
+ },
235
+ lookup: {
236
+ posture: "lookup", stdin: false, retarget: false,
237
+ argv: (i) => ["--print", "--model", i.model, "-p", i.prompt],
238
+ },
239
+ capture: {
240
+ posture: "path", stdin: true, retarget: false, collectiveChild: true,
241
+ argv: (i, x) => ["--print", "--model", i.model, ...x.mcp],
242
+ },
243
+ reflect: {
244
+ posture: "path", stdin: true, retarget: false, collectiveChild: true,
245
+ argv: (i, x) => ["--print", "--model", i.model, ...x.mcp],
246
+ },
247
+ curator: {
248
+ posture: "path", stdin: true, retarget: false, collectiveChild: true,
249
+ argv: (i, x) => ["--print", "--model", i.model, ...x.mcp],
250
+ },
251
+ enrich: {
252
+ posture: "inherit", stdin: false, retarget: false,
253
+ argv: (i, x) => ["--print", ...x.mcp, "--model", i.model, "--append-system-prompt", i.systemPrompt, i.prompt],
254
+ },
255
+ "auth-status": {
256
+ posture: "inherit", stdin: false, retarget: false,
257
+ argv: () => ["auth", "status", "--json"],
258
+ },
259
+ "mcp-register": {
260
+ posture: "inherit", stdin: false, retarget: false,
261
+ argv: (i) => {
262
+ const r = i.registration || {};
263
+ return ["mcp", "add-json", r.name, r.config, "--scope", r.scope || "user"];
264
+ },
265
+ },
266
+ "main-session": {
267
+ posture: "inherit", stdin: false, retarget: false,
268
+ // launch-args owns the main-session argv contract (and its defect checks).
269
+ argv: (i, x) => buildClaudeArgs({
270
+ first: i.first,
271
+ sessionId: i.sessionId,
272
+ mode: i.resumeMode,
273
+ permissionArgs: x.perm,
274
+ extraArgs: arr(i.extraArgs),
275
+ prompt: i.prompt,
276
+ }),
277
+ },
278
+ peer: {
279
+ posture: "inherit", stdin: false, retarget: false,
280
+ argv: (i, x) => ["--name", i.name, ...x.perm, i.prompt],
281
+ },
282
+ "voice-brief": {
283
+ posture: "agent-root", stdin: false, retarget: false,
284
+ defaultPermissions: ["--dangerously-skip-permissions"],
285
+ argv: (i, x) => ["--print", ...x.perm, ...x.mcp, "--model", i.model, i.prompt],
286
+ },
287
+ huddle: {
288
+ posture: "huddle", stdin: false, retarget: false,
289
+ argv: (i) => ["--print", "--model", i.model, "-p", i.prompt],
290
+ },
291
+ });
292
+
293
+ /** Every lane name, for tests and docs. */
294
+ export const LANE_NAMES = Object.freeze(Object.keys(LANES));
295
+
296
+ /**
297
+ * Compose a lane's env. Pure over its inputs.
298
+ * @returns {{ok:true, env:object, retargeting:boolean}|{ok:false, error:object}}
299
+ */
300
+ function laneEnv(spec, i, deps) {
301
+ const base = i.env && typeof i.env === "object" ? i.env : {};
302
+ const retargeting = isRetarget(i.retarget);
303
+ const path = () => deps.augmentedPath();
304
+
305
+ if (retargeting) {
306
+ // Only the retargetable lanes reach here (checked by the caller).
307
+ const pre = spec.posture === "cadence"
308
+ ? { ...base, AGENT_ROOT: i.agentRoot, AGENT_DIR: i.agentRoot, PATH: path() }
309
+ : { ...base, PATH: path() };
310
+ const built = buildRuntimeEnv({ baseEnv: pre, retarget: i.retarget });
311
+ if (!built.ok) return built;
312
+ const env = spec.posture === "cadence"
313
+ ? { ...built.env, AGENT_ROOT: i.agentRoot, AGENT_DIR: i.agentRoot, PATH: path() }
314
+ : { ...built.env, PATH: path() };
315
+ return { ok: true, env, retargeting: true };
316
+ }
317
+
318
+ switch (spec.posture) {
319
+ case "stock-oauth": {
320
+ // A non-retarget router set (an Anthropic decision) may still carry
321
+ // harmless keys; they ride on top exactly as the sites always merged them.
322
+ const overlay = i.retarget && typeof i.retarget === "object" ? i.retarget : {};
323
+ const env = { ...base, PATH: path(), ANTHROPIC_API_KEY: "", ANTHROPIC_AUTH_TOKEN: "", ...overlay };
324
+ // A seat on subscription auth (the prefer flag or an OAuth token) strips
325
+ // stale keys so the CLI rides the subscription — the historical posture,
326
+ // unchanged for every such seat. A seat with NEITHER is an API-key seat
327
+ // (the setup wizard's default since W1-E): blanking its key would leave
328
+ // the CLI with no credential at all, so the key is kept.
329
+ if (!subscriptionAuth(base) && nonEmpty(base.ANTHROPIC_API_KEY) && !("ANTHROPIC_API_KEY" in overlay)) {
330
+ env.ANTHROPIC_API_KEY = base.ANTHROPIC_API_KEY;
331
+ }
332
+ return { ok: true, retargeting: false, env };
333
+ }
334
+ case "cadence": {
335
+ const env = { ...base, AGENT_ROOT: i.agentRoot, AGENT_DIR: i.agentRoot, PATH: path() };
336
+ const preferSubscription = base.MAESTRO_PREFER_SUBSCRIPTION_AUTH === "1";
337
+ const apiKey = env.ANTHROPIC_API_KEY || "";
338
+ if (preferSubscription || !String(apiKey).trim() || PLACEHOLDER_API_KEY.test(apiKey)) delete env.ANTHROPIC_API_KEY;
339
+ return { ok: true, retargeting: false, env };
340
+ }
341
+ case "path":
342
+ return { ok: true, retargeting: false, env: { ...base, PATH: path() } };
343
+ case "lookup":
344
+ return { ok: true, retargeting: false, env: { ...base, TERM: "dumb", PATH: path() } };
345
+ case "huddle":
346
+ return { ok: true, retargeting: false, env: { ...base, TERM: "dumb" } };
347
+ case "agent-root":
348
+ return { ok: true, retargeting: false, env: { ...base, AGENT_ROOT: i.agentRoot, AGENT_DIR: i.agentRoot } };
349
+ case "inherit":
350
+ default:
351
+ return { ok: true, retargeting: false, env: { ...base } };
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Build one runtime spawn.
357
+ *
358
+ * @param {object} i
359
+ * @param {string} i.lane one of {@link LANE_NAMES} (unknown → throws: a defect)
360
+ * @param {"claude"|"cohort"} [i.engine="claude"]
361
+ * @param {string} [i.model] the --model value
362
+ * @param {string} [i.prompt] the prompt; argv or stdin per lane
363
+ * @param {string} [i.systemPrompt] --append-system-prompt (responder/classifier/ack/enrich)
364
+ * @param {string} [i.sessionId] --session-id (dispatcher/resume/responder/main-session)
365
+ * @param {null|"strict"|{source?:string, agentRoot?:string}} [i.mcp] MCP flags
366
+ * @param {string[]} [i.permissions] resolved permission args (lib/session-permissions.mjs)
367
+ * @param {object} [i.knobs] {maxTurns, effort, agentsJson} router spawn knobs
368
+ * @param {object} [i.env] the base env (default process.env)
369
+ * @param {object} [i.retarget] the router's envForSpawn (retargetable lanes only)
370
+ * @param {object} [i.extraEnv] plain identifiers set after every merge (never credentials)
371
+ * @param {string[]} [i.extraArgs] main-session only: args before the prompt
372
+ * @param {string} [i.agentRoot] cadence/voice-brief: AGENT_ROOT/AGENT_DIR
373
+ * @param {string} [i.name] peer: the --name value
374
+ * @param {string} [i.first] main-session: the agent's first name
375
+ * @param {"session-id"|"resume"} [i.resumeMode] main-session launch mode
376
+ * @param {{name:string, config:string, scope?:string}} [i.registration] mcp-register
377
+ * @param {string} [i.bin] explicit binary (a caller's injected dep); else resolved
378
+ * @param {number} [i.maxBudgetUsd] engine cohort run lanes only: `--max-budget-usd` (ignored for claude)
379
+ * @param {string} [i.cohortToken] engine cohort: the seat token the spawn starts with
380
+ * @param {object} [deps] {resolveBin, daemonArgs, augmentedPath} — test seams
381
+ * @returns {{ok:true, engine:string, lane:string, bin:string, argv:string[], env:object, stdin:string|null, retargeting:boolean} |
382
+ * {ok:false, error:{code:string, message:string, lane:string, engine:string}}}
383
+ */
384
+ export function buildSpawn(i = {}, deps = {}) {
385
+ const spec = LANES[i.lane];
386
+ if (!spec) throw new TypeError(`buildSpawn: unknown lane ${JSON.stringify(i.lane)} (known: ${LANE_NAMES.join(", ")})`);
387
+ const engine = i.engine == null ? DEFAULT_ENGINE : i.engine;
388
+ const fail = (code, message) => ({ ok: false, error: { code, message, lane: i.lane, engine: String(engine) } });
389
+
390
+ if (engine !== "claude" && engine !== "cohort") {
391
+ return fail("unknown_engine", `unknown runtime engine ${JSON.stringify(engine)} (known: ${ENGINES.join(", ")})`);
392
+ }
393
+ if (isRetarget(i.retarget) && !spec.retarget) {
394
+ return fail("retarget_unsupported", `lane "${i.lane}" cannot be retargeted to another backend`);
395
+ }
396
+
397
+ const d = {
398
+ resolveBin: deps.resolveBin || resolveClaudeBin,
399
+ daemonArgs: deps.daemonArgs || daemonClaudeArgs,
400
+ augmentedPath: deps.augmentedPath || augmentedPath,
401
+ };
402
+ // A collective lane's marker rides extraEnv so both engines set it after
403
+ // every merge; a caller cannot drop it by passing its own extraEnv.
404
+ const extraEnv = spec.collectiveChild
405
+ ? { ...(i.extraEnv && typeof i.extraEnv === "object" ? i.extraEnv : {}), [COLLECTIVE_CHILD_ENV]: "1" }
406
+ : i.extraEnv;
407
+ const input = { ...i, env: i.env === undefined ? process.env : i.env, extraEnv };
408
+
409
+ if (engine === "cohort") return buildCohortEngineSpawn(spec, input, d, deps, fail);
410
+
411
+ const envResult = laneEnv(spec, input, d);
412
+ if (!envResult.ok) return fail(envResult.error.code, envResult.error.message);
413
+
414
+ const perm = Array.isArray(i.permissions) ? i.permissions : (spec.defaultPermissions || []);
415
+ // Path B: a cohort-llm retarget authenticates through the seat-token helper,
416
+ // which Claude Code only reads from settings (child-env COHORT_HELPER_KEY).
417
+ const settings = envResult.retargeting && isCohortHelperRetarget(i.retarget)
418
+ ? ["--settings", JSON.stringify({ apiKeyHelper: i.retarget[COHORT_HELPER_KEY] })]
419
+ : [];
420
+ const x = { perm, mcp: mcpArgs(i.mcp, d), knobs: knobArgs(i.knobs), settings };
421
+ const argv = spec.argv(input, x);
422
+
423
+ const env = envResult.env;
424
+ if (input.extraEnv && typeof input.extraEnv === "object") {
425
+ for (const [k, v] of Object.entries(input.extraEnv)) if (v !== undefined && v !== null) env[k] = String(v);
426
+ }
427
+
428
+ return {
429
+ ok: true,
430
+ engine,
431
+ lane: i.lane,
432
+ bin: i.bin || d.resolveBin(),
433
+ argv,
434
+ env,
435
+ stdin: spec.stdin ? (i.prompt == null ? "" : String(i.prompt)) : null,
436
+ retargeting: envResult.retargeting,
437
+ };
438
+ }
439
+
440
+ // ---------------------------------------------------------------------------
441
+ // Engine: cohort
442
+ // ---------------------------------------------------------------------------
443
+
444
+ /** The engine's headless entry point. */
445
+ export const ENGINE_CLI_PATH = join(dirname(fileURLToPath(import.meta.url)), "..", "engine", "cli.mjs");
446
+
447
+ /**
448
+ * The flags `cohort run` accepts TODAY (lib/engine/cli.mjs `parseRunArgs`).
449
+ * adapter-cohort.test.mjs pins this list against the real parser in both
450
+ * directions, so when the engine grows a flag (W2-D: MCP, permissions) the
451
+ * test fails until the flag is listed here — and only then does the adapter
452
+ * pass it through.
453
+ */
454
+ export const ENGINE_RUN_FLAGS = Object.freeze([
455
+ "-p", "--print", "--output-format", "--model", "--session-id", "--resume", "--max-turns", "--append-system-prompt",
456
+ // W2-D: MCP, permissions, settings.
457
+ "--mcp-config", "--strict-mcp-config", "--permission-mode", "--dangerously-skip-permissions",
458
+ "--allowedTools", "--disallowedTools", "--settings",
459
+ // W3-G (CF-45): the router knobs and the daemon's bare mode.
460
+ "--add-dir", "--plugin-dir", "--effort", "--agents", "--bare",
461
+ // W4-E1 (row 24): the per-session spend cap.
462
+ "--max-budget-usd",
463
+ ]);
464
+
465
+ /**
466
+ * The claude `--print` subset maestro's lanes emit, and the engine flag each
467
+ * becomes. Anything not in this table is unsupported by construction.
468
+ */
469
+ const CLAUDE_TO_ENGINE = Object.freeze({
470
+ "-p": { flag: "-p", value: false },
471
+ "--print": { flag: "-p", value: false },
472
+ "--output-format": { flag: "--output-format", value: true, values: ["json", "text"] },
473
+ "--model": { flag: "--model", value: true, model: true },
474
+ "--session-id": { flag: "--session-id", value: true },
475
+ "--resume": { flag: "--resume", value: true },
476
+ "--append-system-prompt": { flag: "--append-system-prompt", value: true },
477
+ "--max-turns": { flag: "--max-turns", value: true },
478
+ "--mcp-config": { flag: "--mcp-config", value: true },
479
+ "--strict-mcp-config": { flag: "--strict-mcp-config", value: false },
480
+ "--dangerously-skip-permissions": { flag: "--dangerously-skip-permissions", value: false },
481
+ "--permission-mode": { flag: "--permission-mode", value: true },
482
+ "--allowedTools": { flag: "--allowedTools", value: true },
483
+ "--disallowedTools": { flag: "--disallowedTools", value: true },
484
+ "--settings": { flag: "--settings", value: true },
485
+ "--add-dir": { flag: "--add-dir", value: true },
486
+ "--plugin-dir": { flag: "--plugin-dir", value: true },
487
+ "--effort": { flag: "--effort", value: true },
488
+ "--agents": { flag: "--agents", value: true },
489
+ "--bare": { flag: "--bare", value: false },
490
+ "--max-budget-usd": { flag: "--max-budget-usd", value: true },
491
+ });
492
+
493
+ /**
494
+ * The Anthropic-slot → Cohort-tier map the engine lane uses when the caller
495
+ * does not pass one. Pinned equal to lib/model-router/catalog/cohort.yaml
496
+ * `tier_map` by adapter-cohort.test.mjs.
497
+ */
498
+ export const DEFAULT_COHORT_TIER_MAP = Object.freeze({ fable: "cohort-reason", opus: "cohort-agentic", sonnet: "cohort-agentic", haiku: "cohort-fast" });
499
+
500
+ /** Lanes whose shape the engine cannot serve (claude CLI subcommands). */
501
+ const ENGINE_UNSUPPORTED_LANES = new Set(["auth-status", "mcp-register"]);
502
+
503
+ /**
504
+ * W4-A1: the interactive lanes, served by `cohort session` (lib/engine/cli.mjs
505
+ * `session`, session-runtime/runner.mjs). Not enabled for any seat: a seat's
506
+ * engine stays `claude` until the §8.4 gate.
507
+ */
508
+ export const ENGINE_INTERACTIVE_LANES = Object.freeze(["main-session", "peer"]);
509
+
510
+ /**
511
+ * The flags `cohort session` accepts that the interactive lanes may emit
512
+ * (lib/engine/session-runtime/args.mjs `SESSION_FLAGS`). adapter-session.test.mjs
513
+ * pins this list against the real session parser.
514
+ */
515
+ export const ENGINE_SESSION_FLAGS = Object.freeze([
516
+ "--name", "--session-id", "--resume", "--model", "--max-turns", "--append-system-prompt",
517
+ "--mcp-config", "--strict-mcp-config", "--permission-mode", "--dangerously-skip-permissions",
518
+ "--allowedTools", "--disallowedTools", "--settings", "--add-dir", "--plugin-dir", "--effort", "--agents", "--bare",
519
+ ]);
520
+
521
+ /**
522
+ * Map an interactive lane's claude argv (`--name …, --session-id|--resume …,
523
+ * permission/extra flags, prompt`) onto `cohort session` argv. The last element
524
+ * is always the prompt. PURE.
525
+ * @param {string[]} argv
526
+ * @param {{tierMap?:Record<string,string>, engineFlags?:readonly string[]}} [o]
527
+ * @returns {{ok:true, argv:string[]}|{ok:false, error:{code:string, message:string, flag?:string}}}
528
+ */
529
+ export function translateInteractiveArgv(argv, o = {}) {
530
+ const engineFlags = o.engineFlags || ENGINE_SESSION_FLAGS;
531
+ const tierMap = o.tierMap || DEFAULT_COHORT_TIER_MAP;
532
+ const args = [...(Array.isArray(argv) ? argv : [])];
533
+ const bad = (code, message, flag) => ({ ok: false, error: { code, message, ...(flag ? { flag } : {}) } });
534
+ if (args.length === 0) return bad("engine_argument_unsupported", "an interactive session needs a prompt");
535
+ const prompt = String(args.pop());
536
+ const out = ["session"];
537
+ for (let k = 0; k < args.length; k++) {
538
+ const a = String(args[k]);
539
+ const m = a === "--name" ? { flag: "--name", value: true } : CLAUDE_TO_ENGINE[a];
540
+ if (!m || m.flag === "-p" || m.flag === "--output-format") {
541
+ if (a.startsWith("-")) return bad("engine_flag_unsupported", `cohort session does not support ${a}`, a);
542
+ return bad("engine_argument_unsupported", `unexpected positional argument for cohort session: ${JSON.stringify(a.slice(0, 40))}`);
543
+ }
544
+ if (!engineFlags.includes(m.flag)) return bad("engine_flag_unsupported", `cohort session does not support ${a} yet`, a);
545
+ if (!m.value) { out.push(m.flag); continue; }
546
+ let v = args[++k];
547
+ if (v === undefined) return bad("engine_argument_unsupported", `${a} needs a value`, a);
548
+ v = String(v);
549
+ if (m.model) {
550
+ const tier = cohortTierFor(v, tierMap);
551
+ if (!tier) return bad("engine_model_unmapped", `--model ${v} has no Cohort tier (expected cohort-* or an opus/sonnet/haiku/fable slot)`, a);
552
+ v = tier;
553
+ }
554
+ out.push(m.flag, v);
555
+ }
556
+ out.push("--", prompt);
557
+ return { ok: true, argv: out };
558
+ }
559
+
560
+ /**
561
+ * Map a lane's claude argv onto `cohort run` argv. PURE.
562
+ * @param {string[]} argv the claude argv the lane table built
563
+ * @param {object} o
564
+ * @param {string|null} [o.prompt] the prompt, when it rides argv (last element)
565
+ * @param {boolean} [o.stdin] the prompt rides stdin
566
+ * @param {Record<string,string>} [o.tierMap]
567
+ * @param {readonly string[]} [o.engineFlags]
568
+ * @returns {{ok:true, argv:string[]}|{ok:false, error:{code:string, message:string, flag?:string}}}
569
+ */
570
+ export function translateClaudeArgv(argv, o = {}) {
571
+ const engineFlags = o.engineFlags || ENGINE_RUN_FLAGS;
572
+ const tierMap = o.tierMap || DEFAULT_COHORT_TIER_MAP;
573
+ const args = [...(Array.isArray(argv) ? argv : [])];
574
+ let promptArg = null;
575
+ if (!o.stdin && o.prompt != null && args.length && args[args.length - 1] === o.prompt) promptArg = args.pop();
576
+ const out = ["run", "-p"];
577
+ const bad = (code, message, flag) => ({ ok: false, error: { code, message, ...(flag ? { flag } : {}) } });
578
+ for (let k = 0; k < args.length; k++) {
579
+ const a = String(args[k]);
580
+ const m = CLAUDE_TO_ENGINE[a];
581
+ if (!m) {
582
+ if (a.startsWith("-")) return bad("engine_flag_unsupported", `the cohort engine does not support ${a}`, a);
583
+ return bad("engine_argument_unsupported", `unexpected positional argument for the cohort engine: ${JSON.stringify(a.slice(0, 40))}`);
584
+ }
585
+ if (!engineFlags.includes(m.flag)) {
586
+ return bad("engine_flag_unsupported", `the cohort engine does not support ${a} yet`, a);
587
+ }
588
+ if (m.flag === "-p") continue; // always emitted once, up front
589
+ if (!m.value) { out.push(m.flag); continue; }
590
+ let v = args[++k];
591
+ if (v === undefined) return bad("engine_argument_unsupported", `${a} needs a value`, a);
592
+ v = String(v);
593
+ if (m.values && !m.values.includes(v)) return bad("engine_flag_unsupported", `the cohort engine does not support ${a} ${v}`, a);
594
+ if (m.model) {
595
+ const tier = cohortTierFor(v, tierMap);
596
+ if (!tier) return bad("engine_model_unmapped", `--model ${v} has no Cohort tier (expected cohort-* or an opus/sonnet/haiku/fable slot)`, a);
597
+ v = tier;
598
+ }
599
+ out.push(m.flag, v);
600
+ }
601
+ if (promptArg != null) out.push("--", String(promptArg));
602
+ return { ok: true, argv: out };
603
+ }
604
+
605
+ /**
606
+ * The engine child's env: the lane's own posture with every Anthropic/Claude
607
+ * credential scrubbed, plus the gateway URL, the seat token minted at spawn,
608
+ * and — when the seat's root is known — the token helper that re-mints it
609
+ * (the engine re-runs it on a TTL and after a 401, so a long-lived
610
+ * `cohort session` never outlives its credential). PURE.
611
+ */
612
+ export function cohortEngineEnv(laneBaseEnv, { baseUrl, token, tokenHelper = null }) {
613
+ const out = {};
614
+ for (const [k, v] of Object.entries(laneBaseEnv || {})) {
615
+ if (/^ANTHROPIC_/i.test(k)) continue;
616
+ if (/^CLAUDE_CODE_/i.test(k) && /(OAUTH|TOKEN|KEY|SECRET|HELPER)/i.test(k)) continue;
617
+ if (/^COHORT_LLM_(TOKEN|API_KEY_HELPER|TOKEN_HELPER)$/i.test(k)) continue;
618
+ out[k] = v;
619
+ }
620
+ out.COHORT_LLM_BASE_URL = baseUrl;
621
+ out.COHORT_LLM_TOKEN = token;
622
+ if (nonEmpty(tokenHelper)) out.COHORT_LLM_TOKEN_HELPER = tokenHelper;
623
+ return out;
624
+ }
625
+
626
+ /** The seat root an engine spawn's token helper reads its key from: the lane's, its MCP seat's, or its env's. PURE. */
627
+ function engineSeatRoot(input, laneEnvValues, deps) {
628
+ const mcpRoot = input.mcp && typeof input.mcp === "object" ? input.mcp.agentRoot : null;
629
+ for (const v of [input.agentRoot, mcpRoot, laneEnvValues && laneEnvValues.AGENT_ROOT, input.env && input.env.AGENT_ROOT, deps.agentRoot]) {
630
+ if (nonEmpty(v)) return String(v);
631
+ }
632
+ return null;
633
+ }
634
+
635
+ function buildCohortEngineSpawn(spec, input, d, deps, fail) {
636
+ if (ENGINE_UNSUPPORTED_LANES.has(input.lane)) {
637
+ return fail("engine_lane_unsupported", `lane "${input.lane}" is interactive or a claude subcommand; the cohort engine runs headless lanes only`);
638
+ }
639
+ if (isRetarget(input.retarget)) {
640
+ return fail("retarget_unsupported", "the cohort engine talks to cohort-llm directly; a router retarget does not apply");
641
+ }
642
+ const perm = Array.isArray(input.permissions) ? input.permissions : (spec.defaultPermissions || []);
643
+ const claudeArgv = spec.argv(input, { perm, mcp: mcpArgs(input.mcp, d), knobs: knobArgs(input.knobs), settings: [] });
644
+ const tierMap = input.cohortTierMap || deps.cohortTierMap;
645
+ const mapped = ENGINE_INTERACTIVE_LANES.includes(input.lane)
646
+ ? translateInteractiveArgv(claudeArgv, { tierMap })
647
+ : translateClaudeArgv(claudeArgv, { prompt: input.prompt, stdin: spec.stdin, tierMap });
648
+ if (!mapped.ok) return fail(mapped.error.code, mapped.error.message);
649
+ // W4-E1 (row 24): a per-session spend cap, for engine cohort run lanes only (a claude
650
+ // spawn ignores `maxBudgetUsd`, so a claude seat's argv is unchanged). Before the prompt's `--`.
651
+ if (input.maxBudgetUsd != null) {
652
+ if (ENGINE_INTERACTIVE_LANES.includes(input.lane)) return fail("engine_argument_unsupported", `lane "${input.lane}" is interactive; --max-budget-usd applies to cohort run lanes`);
653
+ const usd = Number(input.maxBudgetUsd);
654
+ if (!Number.isFinite(usd) || usd <= 0) return fail("engine_argument_unsupported", `maxBudgetUsd must be a positive number, got ${JSON.stringify(input.maxBudgetUsd)}`);
655
+ const at = mapped.argv.indexOf("--");
656
+ mapped.argv.splice(at === -1 ? mapped.argv.length : at, 0, "--max-budget-usd", String(usd));
657
+ }
658
+
659
+ const tokenValue = typeof input.cohortToken === "string" ? input.cohortToken
660
+ : (typeof deps.cohortToken === "function" ? deps.cohortToken() : null);
661
+ const token = tokenValue && typeof tokenValue === "object" ? tokenValue.token : tokenValue;
662
+ if (!nonEmpty(token)) {
663
+ return fail("cohort_token_unavailable", "no cohort-llm seat token for the engine — admit the spawn first (admitSpawn mints/caches one)");
664
+ }
665
+ const baseUrl = String(input.cohortBaseUrl || deps.cohortBaseUrl || (input.env && input.env.COHORT_LLM_BASE_URL) || DEFAULT_LLM_BASE_URL).replace(/\/+$/, "");
666
+
667
+ const envResult = laneEnv(spec, { ...input, retarget: {} }, d);
668
+ if (!envResult.ok) return fail(envResult.error.code, envResult.error.message);
669
+ const seatRoot = engineSeatRoot(input, envResult.env, deps);
670
+ const tokenHelper = seatRoot
671
+ ? apiKeyHelperCommand({ agentRoot: seatRoot, nodeBin: deps.nodeBin || process.execPath, ...(deps.apiKeyHelperPath ? { helperPath: deps.apiKeyHelperPath } : {}) })
672
+ : null;
673
+ const env = cohortEngineEnv(envResult.env, { baseUrl, token, tokenHelper });
674
+ if (input.extraEnv && typeof input.extraEnv === "object") {
675
+ for (const [k, v] of Object.entries(input.extraEnv)) if (v !== undefined && v !== null) env[k] = String(v);
676
+ }
677
+ return {
678
+ ok: true,
679
+ engine: "cohort",
680
+ lane: input.lane,
681
+ bin: deps.nodeBin || process.execPath,
682
+ argv: [deps.engineCliPath || ENGINE_CLI_PATH, ...mapped.argv],
683
+ env,
684
+ stdin: spec.stdin ? (input.prompt == null ? "" : String(input.prompt)) : null,
685
+ retargeting: false,
686
+ };
687
+ }
688
+
689
+ /**
690
+ * Can this seat generate a reply at all — the NECESSARY presence check the
691
+ * daemon's ack-before-work fail-safe reads (agent-daemon claudeAvailable).
692
+ * A claude seat needs a safe claude binary; an engine-cohort seat never
693
+ * spawns claude, so it needs the engine entry point instead (conformance
694
+ * row 1). Neither proves the credential or the network. PURE.
695
+ * @param {{engine:string, claudeBinOk:()=>boolean, engineCliPresent:()=>boolean}} o
696
+ * @returns {boolean}
697
+ */
698
+ export function seatGenerationAvailable({ engine, claudeBinOk, engineCliPresent }) {
699
+ try {
700
+ return engine === "cohort" ? engineCliPresent() === true : claudeBinOk() === true;
701
+ } catch {
702
+ return false;
703
+ }
704
+ }
705
+
706
+ // ---------------------------------------------------------------------------
707
+ // cohort-llm admission (the pre-spawn gate)
708
+ // ---------------------------------------------------------------------------
709
+
710
+ /** A credits hold re-checks after this long. */
711
+ export const COHORT_CREDITS_RECHECK_MS = 10 * 60 * 1000;
712
+ /** A refusal that waiting cannot fix (a bad key, an unpriced tier) re-checks after this long. */
713
+ export const COHORT_FAIL_HOLD_MS = 15 * 60 * 1000;
714
+
715
+ /**
716
+ * Is this spawn served by cohort-llm? engine cohort, a target routed to the
717
+ * cohort provider, or a retarget authenticated by the cohort key helper. PURE.
718
+ * @param {{engine?:string, target?:object|null}} o
719
+ */
720
+ export function isCohortRouted({ engine, target } = {}) {
721
+ if (engine === "cohort") return true;
722
+ const t = target && typeof target === "object" ? target : null;
723
+ if (!t) return false;
724
+ if (t.backend === COHORT_BACKEND) return true;
725
+ return isCohortHelperRetarget(t.envForSpawn || t.retarget || null);
726
+ }
727
+
728
+ /**
729
+ * Turn an admission refusal into the deferral a call site acts on. PURE.
730
+ * `until` is always an epoch ms: the refusal's resetsAt, or a re-check time.
731
+ * @returns {{kind:string, until:number, code:string|null, message:string, refusal:object|null}}
732
+ */
733
+ export function deferralFromAdmission(adm, now) {
734
+ const d = (adm && adm.disposition) || { kind: "fail", code: null };
735
+ const code = d.code || (adm && adm.error && adm.error.code) || null;
736
+ let until;
737
+ if (d.kind === "defer_until") until = d.until;
738
+ else if (d.kind === "retry") until = now + (Number(d.afterMs) || 0);
739
+ else if (d.kind === "hold_for_credits") until = now + COHORT_CREDITS_RECHECK_MS;
740
+ else until = now + COHORT_FAIL_HOLD_MS;
741
+ const what = {
742
+ defer_until: `cohort-llm refused (${code}); deferring until ${new Date(until).toISOString()}`,
743
+ hold_for_credits: "cohort-llm credits are exhausted; holding until a top-up",
744
+ retry: `cohort-llm is busy (${code}); retrying at ${new Date(until).toISOString()}`,
745
+ fail: `cohort-llm cannot admit this seat (${code})`,
746
+ }[d.kind] || `cohort-llm refused (${code})`;
747
+ return { kind: d.kind, until, code, message: what, refusal: (adm && adm.refusal) || null };
748
+ }
749
+
750
+ function holdDeferral(rateGuard, rateGuardDeps, now) {
751
+ if (!rateGuard || typeof rateGuard.checkRateLimit !== "function") return null;
752
+ try {
753
+ const hold = rateGuard.checkRateLimit(COHORT_BACKEND, rateGuardDeps);
754
+ if (hold && hold.allowed === false) {
755
+ return { kind: "defer_until", until: hold.retryAt, code: "cohort_hold", message: `a cohort-llm refusal holds this seat until ${new Date(hold.retryAt).toISOString()}`, refusal: null };
756
+ }
757
+ } catch { /* a hold we cannot read never blocks — the quota read still gates */ }
758
+ return null;
759
+ }
760
+
761
+ /**
762
+ * Record a refusal as a cohort hold, and when one was recorded drop the quota
763
+ * cache: the reading that refused must not be the one a gate trusts once the
764
+ * hold lifts (the cache would otherwise answer for up to its TTL).
765
+ */
766
+ function recordRefusalHold(rateGuard, rateGuardDeps, adm, quota) {
767
+ if (!adm || !adm.refusal || !rateGuard || typeof rateGuard.recordCohortRefusal !== "function") return;
768
+ let rec = null;
769
+ try { rec = rateGuard.recordCohortRefusal(adm.refusal, rateGuardDeps); } catch { /* best-effort */ }
770
+ if (rec && rec.recorded && quota && typeof quota.invalidate === "function") {
771
+ try { quota.invalidate(); } catch { /* best-effort */ }
772
+ }
773
+ }
774
+
775
+ /**
776
+ * May this spawn start? Not cohort-routed → always yes. Cohort-routed → an
777
+ * existing cohort hold (rate-guard, backend "cohort") defers without a network
778
+ * call; otherwise `quota.canAdmit()` decides, and a refusal is recorded as a
779
+ * cohort hold so the next spawn does not ask again. Human replies are NOT
780
+ * exempt: a refused seat would 429/402 them just the same.
781
+ *
782
+ * @param {{engine?:string, target?:object|null}} o
783
+ * @param {{quota?:{canAdmit:Function}, token?:{peek:Function}, rateGuard?:object, rateGuardDeps?:object, now?:()=>number}} [deps]
784
+ * @returns {Promise<{ok:true, cohort:boolean, funding?:string|null, token?:string|null} |
785
+ * {ok:false, cohort:true, deferral:object}>}
786
+ */
787
+ export async function admitSpawn(o = {}, deps = {}) {
788
+ if (!isCohortRouted(o)) return { ok: true, cohort: false };
789
+ const now = typeof deps.now === "function" ? deps.now() : Date.now();
790
+ const held = holdDeferral(deps.rateGuard, deps.rateGuardDeps, now);
791
+ if (held) return { ok: false, cohort: true, deferral: held };
792
+ if (!deps.quota || typeof deps.quota.canAdmit !== "function") {
793
+ return { ok: false, cohort: true, deferral: deferralFromAdmission({ disposition: { kind: "retry", afterMs: 30_000, code: "cohort_quota_unconfigured" } }, now) };
794
+ }
795
+ let adm;
796
+ try { adm = await deps.quota.canAdmit(); } catch (err) {
797
+ adm = { ok: false, error: { code: "cohort_unavailable", message: String(err && err.message || err) }, disposition: { kind: "retry", afterMs: 30_000, code: "cohort_unavailable" } };
798
+ }
799
+ if (adm && adm.ok) {
800
+ const t = deps.token && typeof deps.token.peek === "function" ? deps.token.peek({ allowStale: true }) : null;
801
+ return { ok: true, cohort: true, funding: adm.funding || null, token: (t && t.token) || null };
802
+ }
803
+ recordRefusalHold(deps.rateGuard, deps.rateGuardDeps, adm, deps.quota);
804
+ return { ok: false, cohort: true, deferral: deferralFromAdmission(adm, now) };
805
+ }
806
+
807
+ /**
808
+ * The synchronous twin of {@link admitSpawn} for call sites that must decide
809
+ * inside a synchronous spawn path (the dispatcher). It reads only the quota
810
+ * cache; with nothing cached it answers `{cold:true}` and the caller primes the
811
+ * cache (`quota.canAdmit()`) and retries — it never spawns on a guess.
812
+ */
813
+ export function admitSpawnCached(o = {}, deps = {}) {
814
+ if (!isCohortRouted(o)) return { ok: true, cohort: false };
815
+ const now = typeof deps.now === "function" ? deps.now() : Date.now();
816
+ const held = holdDeferral(deps.rateGuard, deps.rateGuardDeps, now);
817
+ if (held) return { ok: false, cohort: true, deferral: held };
818
+ const adm = deps.quota && typeof deps.quota.canAdmitCached === "function" ? deps.quota.canAdmitCached() : { cold: true };
819
+ if (adm.cold) {
820
+ return { ok: false, cohort: true, cold: true, deferral: { kind: "retry", until: now, code: "cohort_quota_cold", message: "no fresh cohort-llm quota reading yet", refusal: null } };
821
+ }
822
+ if (adm.ok) {
823
+ const t = deps.token && typeof deps.token.peek === "function" ? deps.token.peek({ allowStale: true }) : null;
824
+ return { ok: true, cohort: true, funding: adm.funding || null, token: (t && t.token) || null };
825
+ }
826
+ recordRefusalHold(deps.rateGuard, deps.rateGuardDeps, adm, deps.quota);
827
+ return { ok: false, cohort: true, deferral: deferralFromAdmission(adm, now) };
828
+ }
829
+
830
+ export default {
831
+ buildSpawn, resolveEngine, readSeatEngine, seatGenerationAvailable, ENGINES, DEFAULT_ENGINE, LANE_NAMES, SHELL_LEGACY_LANES,
832
+ translateClaudeArgv, cohortEngineEnv, isCohortRouted, admitSpawn, admitSpawnCached, deferralFromAdmission,
833
+ };