@cohortapp/agent-sdk 2.16.0 → 2.18.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 (529) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +23 -7
  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/poller-daemon-setup.md +4 -1
  9. package/docs/guides/setup-wizard.md +1 -1
  10. package/docs/runbooks/fleet-rollout.md +156 -0
  11. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  12. package/lib/action-executor.js +19 -3
  13. package/lib/budget-guard.mjs +279 -3
  14. package/lib/channels/base-adapter.mjs +3 -1
  15. package/lib/channels/contract.mjs +2 -1
  16. package/lib/channels/inbox-item.mjs +8 -0
  17. package/lib/claude-bin.mjs +5 -6
  18. package/lib/cli/doctor-checks.mjs +141 -10
  19. package/lib/cli/global-setup-extras.mjs +5 -1
  20. package/lib/cli/inbox.mjs +100 -15
  21. package/lib/cli/seat-auth.mjs +463 -0
  22. package/lib/cli/session.mjs +80 -12
  23. package/lib/collective/capture.mjs +8 -6
  24. package/lib/collective/global-config.mjs +63 -1
  25. package/lib/collective/presence.mjs +142 -5
  26. package/lib/comms/send-gate.mjs +559 -1
  27. package/lib/context/budget.mjs +327 -0
  28. package/lib/context/history-scope.mjs +138 -0
  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/economics.mjs +9 -0
  138. package/lib/model-router/failover.mjs +67 -16
  139. package/lib/model-router/llm-task.mjs +39 -3
  140. package/lib/model-router/resolve.mjs +95 -3
  141. package/lib/model-router/spawn.mjs +46 -47
  142. package/lib/model-router/taxonomy.mjs +126 -4
  143. package/lib/org/cost-sync.mjs +141 -11
  144. package/lib/org/inbound/broadcast.mjs +289 -0
  145. package/lib/org/inbound/collective.mjs +375 -0
  146. package/lib/org/inbound/directedness.mjs +96 -8
  147. package/lib/org/inbound/facts.mjs +82 -4
  148. package/lib/org/inbound/hydrate.mjs +555 -51
  149. package/lib/org/inbound/project.mjs +22 -0
  150. package/lib/org/inbound/surfaces.mjs +14 -0
  151. package/lib/org/llm-token.mjs +879 -0
  152. package/lib/org/mesh.mjs +61 -0
  153. package/lib/org/messaging.mjs +3 -1
  154. package/lib/org/protocol.checksum +1 -1
  155. package/lib/org/protocol.mjs +15 -0
  156. package/lib/org/quota.mjs +520 -0
  157. package/lib/org/tool-surface.mjs +104 -16
  158. package/lib/org/ui-parity.mjs +16 -1
  159. package/lib/org/work-ledger.mjs +37 -6
  160. package/lib/rate-guard.mjs +114 -1
  161. package/lib/resource-governor.mjs +41 -6
  162. package/lib/runtime/adapter.mjs +823 -0
  163. package/lib/runtime/child-env.mjs +191 -0
  164. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  165. package/lib/runtime/seat-engine.mjs +162 -0
  166. package/lib/session/ask-ledger.mjs +271 -0
  167. package/lib/session/current-work.mjs +676 -0
  168. package/lib/session/feed-core.mjs +40 -3
  169. package/lib/session/launch-args.mjs +56 -4
  170. package/lib/session/status-summary.mjs +26 -9
  171. package/lib/session/upgrade-notice.mjs +42 -0
  172. package/lib/setup/claude-probe.mjs +117 -13
  173. package/lib/setup/enrich.mjs +13 -10
  174. package/lib/setup/sections/model.mjs +39 -13
  175. package/lib/telemetry/collect.mjs +208 -9
  176. package/lib/upgrade/ignored-drift.mjs +105 -0
  177. package/lib/voice/post-call-brief.mjs +30 -17
  178. package/package.json +15 -3
  179. package/plugins/maestro-skills/skills/board-work.md +5 -0
  180. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  181. package/plugins/maestro-skills/skills/main-session.md +18 -7
  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 +29 -2
  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 +104 -59
  194. package/scripts/daemon/deliver.mjs +30 -1
  195. package/scripts/daemon/dispatcher.mjs +804 -157
  196. package/scripts/daemon/health.mjs +14 -1
  197. package/scripts/daemon/lib/session-router.mjs +310 -42
  198. package/scripts/daemon/maestro-daemon.mjs +11 -0
  199. package/scripts/daemon/prompt-builder.mjs +121 -12
  200. package/scripts/daemon/responder.mjs +315 -146
  201. package/scripts/daemon/sdk-version.mjs +98 -16
  202. package/scripts/eval/probe-gateway.mjs +635 -0
  203. package/scripts/eval/replay/extract.mjs +270 -0
  204. package/scripts/eval/replay/grade.mjs +260 -0
  205. package/scripts/eval/replay/lib/config.mjs +50 -0
  206. package/scripts/eval/replay/lib/effects.mjs +65 -0
  207. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  208. package/scripts/eval/replay/lib/judge.mjs +72 -0
  209. package/scripts/eval/replay/lib/redact.mjs +136 -0
  210. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  211. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  212. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  213. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  214. package/scripts/eval/replay/report.mjs +185 -0
  215. package/scripts/eval/replay/run.mjs +404 -0
  216. package/scripts/fleet/rollout.mjs +1094 -0
  217. package/scripts/hooks/pre-send-audit.sh +36 -245
  218. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  219. package/scripts/hooks/validate-state-yaml.sh +190 -0
  220. package/scripts/huddle/huddle-llm.mjs +361 -0
  221. package/scripts/huddle/huddle-server.mjs +46 -121
  222. package/scripts/local-triggers/autoupdate.sh +448 -78
  223. package/scripts/local-triggers/run-trigger.sh +13 -0
  224. package/scripts/maintenance/pin-integrity.mjs +364 -0
  225. package/scripts/poll-slack-events.sh +41 -9
  226. package/scripts/poller/slack-socket-mode.mjs +28 -3
  227. package/scripts/session/supervisor.mjs +80 -13
  228. package/scripts/spawn-session.sh +13 -0
  229. package/bin/maestro.test.mjs +0 -1574
  230. package/lib/action-executor.test.mjs +0 -871
  231. package/lib/archetype.test.mjs +0 -132
  232. package/lib/assurance/plan-note.test.mjs +0 -234
  233. package/lib/assurance/room-budget.test.mjs +0 -486
  234. package/lib/assurance/tier.test.mjs +0 -174
  235. package/lib/autonomy.test.mjs +0 -66
  236. package/lib/backlog.test.mjs +0 -302
  237. package/lib/backup/policy.test.mjs +0 -305
  238. package/lib/budget-escalate.test.mjs +0 -232
  239. package/lib/budget-guard.envelope.test.mjs +0 -476
  240. package/lib/budget-guard.test.mjs +0 -427
  241. package/lib/cadence-bus-requeue.test.mjs +0 -83
  242. package/lib/cadence-bus-schedule.test.mjs +0 -194
  243. package/lib/cadence-bus.test.mjs +0 -720
  244. package/lib/cadences.test.mjs +0 -230
  245. package/lib/capability/inventory.test.mjs +0 -232
  246. package/lib/capability.test.mjs +0 -78
  247. package/lib/channels/base-adapter.test.mjs +0 -590
  248. package/lib/channels/channels.test.mjs +0 -371
  249. package/lib/channels/contract.test.mjs +0 -162
  250. package/lib/channels/inbox-item.test.mjs +0 -368
  251. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  252. package/lib/channels/pairing.test.mjs +0 -270
  253. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  254. package/lib/channels/slack-adapter.test.mjs +0 -212
  255. package/lib/channels/telegram-adapter.test.mjs +0 -306
  256. package/lib/channels/voice/adapter.test.mjs +0 -278
  257. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  258. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  259. package/lib/charter.test.mjs +0 -89
  260. package/lib/claude-bin.test.mjs +0 -131
  261. package/lib/cli/board.test.mjs +0 -227
  262. package/lib/cli/design.test.mjs +0 -270
  263. package/lib/cli/doctor-checks.test.mjs +0 -336
  264. package/lib/cli/global-setup-extras.test.mjs +0 -462
  265. package/lib/cli/inbox.test.mjs +0 -230
  266. package/lib/cli/session-ack.test.mjs +0 -63
  267. package/lib/cli/session.test.mjs +0 -613
  268. package/lib/collective/capture.test.mjs +0 -121
  269. package/lib/collective/cards.test.mjs +0 -114
  270. package/lib/collective/config.test.mjs +0 -123
  271. package/lib/collective/global-config.test.mjs +0 -220
  272. package/lib/collective/global-skills.test.mjs +0 -126
  273. package/lib/collective/presence.test.mjs +0 -95
  274. package/lib/collective/recall.test.mjs +0 -116
  275. package/lib/collective/vendor-skills.test.mjs +0 -306
  276. package/lib/comms/send-gate.test.mjs +0 -770
  277. package/lib/comms.test.mjs +0 -41
  278. package/lib/cost/ledger-row.test.mjs +0 -183
  279. package/lib/design/design-md.test.mjs +0 -318
  280. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  281. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  282. package/lib/design/fixtures/foundation.json +0 -133
  283. package/lib/design/refresh-gate.test.mjs +0 -144
  284. package/lib/design/write.test.mjs +0 -241
  285. package/lib/diagnostics/alerts.test.mjs +0 -318
  286. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  287. package/lib/diagnostics/counters.test.mjs +0 -206
  288. package/lib/diagnostics/events.test.mjs +0 -290
  289. package/lib/diagnostics/otel.test.mjs +0 -196
  290. package/lib/diagnostics/trace.test.mjs +0 -251
  291. package/lib/env-compat.test.mjs +0 -104
  292. package/lib/execution/disposition.test.mjs +0 -553
  293. package/lib/execution/drive.test.mjs +0 -270
  294. package/lib/execution/effects.test.mjs +0 -344
  295. package/lib/execution/intake.test.mjs +0 -389
  296. package/lib/execution/journal.test.mjs +0 -261
  297. package/lib/execution/match.test.mjs +0 -235
  298. package/lib/execution/pipeline.test.mjs +0 -392
  299. package/lib/execution/route.test.mjs +0 -186
  300. package/lib/execution/surface-policy.test.mjs +0 -162
  301. package/lib/fs-atomic.test.mjs +0 -72
  302. package/lib/fs-ownership.test.mjs +0 -158
  303. package/lib/goals/admission.test.mjs +0 -164
  304. package/lib/goals/classify.test.mjs +0 -167
  305. package/lib/goals/collaborate.test.mjs +0 -336
  306. package/lib/goals/gaps.test.mjs +0 -284
  307. package/lib/goals/loop.test.mjs +0 -845
  308. package/lib/hooks/bus.test.mjs +0 -387
  309. package/lib/identity/persona.test.mjs +0 -142
  310. package/lib/kpi-sensors.test.mjs +0 -278
  311. package/lib/kpi.test.mjs +0 -244
  312. package/lib/learning/config.test.mjs +0 -75
  313. package/lib/learning/counters.test.mjs +0 -69
  314. package/lib/learning/curator-consolidate.test.mjs +0 -238
  315. package/lib/learning/curator.test.mjs +0 -106
  316. package/lib/learning/reflect.test.mjs +0 -0
  317. package/lib/learning/session-index.test.mjs +0 -125
  318. package/lib/learning/skill-writer.test.mjs +0 -210
  319. package/lib/mandate/audit.test.mjs +0 -195
  320. package/lib/mandate/contract.test.mjs +0 -185
  321. package/lib/mandate/derive.test.mjs +0 -274
  322. package/lib/mandate/model.test.mjs +0 -164
  323. package/lib/mandate/refresh.test.mjs +0 -389
  324. package/lib/mcp/server.test.mjs +0 -426
  325. package/lib/model-router/auth-profiles.test.mjs +0 -580
  326. package/lib/model-router/catalog.test.mjs +0 -385
  327. package/lib/model-router/economics.test.mjs +0 -438
  328. package/lib/model-router/failover.test.mjs +0 -439
  329. package/lib/model-router/health.test.mjs +0 -338
  330. package/lib/model-router/integration-coverage.test.mjs +0 -831
  331. package/lib/model-router/integration.test.mjs +0 -564
  332. package/lib/model-router/ledger.test.mjs +0 -415
  333. package/lib/model-router/llm-task.test.mjs +0 -392
  334. package/lib/model-router/org-credentials.test.mjs +0 -265
  335. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  336. package/lib/model-router/reconcile.test.mjs +0 -316
  337. package/lib/model-router/repair.test.mjs +0 -180
  338. package/lib/model-router/spawn.test.mjs +0 -446
  339. package/lib/model-router/taxonomy.test.mjs +0 -410
  340. package/lib/model-router.test.mjs +0 -1207
  341. package/lib/org/activity.test.mjs +0 -134
  342. package/lib/org/approvals.test.mjs +0 -216
  343. package/lib/org/awareness.test.mjs +0 -159
  344. package/lib/org/board-mine-cache.test.mjs +0 -53
  345. package/lib/org/board.test.mjs +0 -187
  346. package/lib/org/bootstrap-context.test.mjs +0 -153
  347. package/lib/org/client.test.mjs +0 -1206
  348. package/lib/org/cohort-client.test.mjs +0 -126
  349. package/lib/org/cost-sync.test.mjs +0 -153
  350. package/lib/org/doctor.test.mjs +0 -346
  351. package/lib/org/engagement-ledger.test.mjs +0 -112
  352. package/lib/org/engagement.test.mjs +0 -739
  353. package/lib/org/handoff.test.mjs +0 -269
  354. package/lib/org/inbound/directedness.test.mjs +0 -668
  355. package/lib/org/inbound/facts.test.mjs +0 -471
  356. package/lib/org/inbound/hydrate.test.mjs +0 -453
  357. package/lib/org/inbound/index.test.mjs +0 -429
  358. package/lib/org/inbound/project.test.mjs +0 -287
  359. package/lib/org/integration-tools.test.mjs +0 -160
  360. package/lib/org/keys.test.mjs +0 -92
  361. package/lib/org/knowledge.test.mjs +0 -326
  362. package/lib/org/leases.test.mjs +0 -235
  363. package/lib/org/mesh-directives.test.mjs +0 -110
  364. package/lib/org/mesh-integration.test.mjs +0 -127
  365. package/lib/org/mesh.test.mjs +0 -400
  366. package/lib/org/messaging.test.mjs +0 -471
  367. package/lib/org/param-contract.test.mjs +0 -477
  368. package/lib/org/policy.test.mjs +0 -237
  369. package/lib/org/protocol.checksum.test.mjs +0 -90
  370. package/lib/org/protocol.test.mjs +0 -323
  371. package/lib/org/push.test.mjs +0 -792
  372. package/lib/org/registry.test.mjs +0 -100
  373. package/lib/org/resource-tools.test.mjs +0 -361
  374. package/lib/org/tool-access.test.mjs +0 -144
  375. package/lib/org/tool-surface-integration.test.mjs +0 -120
  376. package/lib/org/tool-surface.test.mjs +0 -1268
  377. package/lib/org/typing.test.mjs +0 -291
  378. package/lib/org/ui-parity.test.mjs +0 -560
  379. package/lib/org/verify.test.mjs +0 -194
  380. package/lib/org/work-ledger.test.mjs +0 -273
  381. package/lib/plan/adoption-e2e.test.mjs +0 -366
  382. package/lib/plan/budget-enforcement.test.mjs +0 -400
  383. package/lib/plan/compile.test.mjs +0 -382
  384. package/lib/plan/emit.test.mjs +0 -269
  385. package/lib/plan/explain.test.mjs +0 -188
  386. package/lib/prompts/parallelism.test.mjs +0 -177
  387. package/lib/rag/rag.test.mjs +0 -505
  388. package/lib/rate-guard.test.mjs +0 -272
  389. package/lib/reactive-gate.test.mjs +0 -57
  390. package/lib/render.test.mjs +0 -68
  391. package/lib/resource-governor.test.mjs +0 -488
  392. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  393. package/lib/scheduling/jitter.test.mjs +0 -140
  394. package/lib/secrets/broker.test.mjs +0 -280
  395. package/lib/secrets/providers.test.mjs +0 -274
  396. package/lib/security/audit-engine.test.mjs +0 -424
  397. package/lib/security/coerce-args.test.mjs +0 -281
  398. package/lib/security/dangerous-tools.test.mjs +0 -68
  399. package/lib/security/external-content.test.mjs +0 -84
  400. package/lib/security/redact.test.mjs +0 -441
  401. package/lib/security/secret-equal.test.mjs +0 -55
  402. package/lib/session/config.test.mjs +0 -92
  403. package/lib/session/feed-core.test.mjs +0 -198
  404. package/lib/session/first-run.test.mjs +0 -121
  405. package/lib/session/frontdoor.test.mjs +0 -205
  406. package/lib/session/handoffs.test.mjs +0 -183
  407. package/lib/session/identity.test.mjs +0 -180
  408. package/lib/session/inbox-claims.test.mjs +0 -286
  409. package/lib/session/launch-args.test.mjs +0 -157
  410. package/lib/session/liveness.test.mjs +0 -100
  411. package/lib/session/status-summary.test.mjs +0 -118
  412. package/lib/session-permissions.test.mjs +0 -120
  413. package/lib/setup/claude-probe.test.mjs +0 -187
  414. package/lib/setup/completeness.test.mjs +0 -110
  415. package/lib/setup/context-pack.test.mjs +0 -89
  416. package/lib/setup/enrich.test.mjs +0 -115
  417. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  418. package/lib/setup/integration.test.mjs +0 -162
  419. package/lib/setup/io.test.mjs +0 -77
  420. package/lib/setup/runner.test.mjs +0 -132
  421. package/lib/setup/sections/identity.test.mjs +0 -234
  422. package/lib/setup/sections/inventory.test.mjs +0 -198
  423. package/lib/setup/sections/learning.test.mjs +0 -81
  424. package/lib/setup/sections/mandate.test.mjs +0 -388
  425. package/lib/setup/sections/messaging.test.mjs +0 -127
  426. package/lib/setup/sections/model.test.mjs +0 -240
  427. package/lib/setup/sections/org.test.mjs +0 -346
  428. package/lib/setup/sections/orgmail.test.mjs +0 -118
  429. package/lib/setup/sections/recovery.test.mjs +0 -98
  430. package/lib/setup/sections/subagents.test.mjs +0 -429
  431. package/lib/setup/sections/verify.test.mjs +0 -175
  432. package/lib/setup/sot.test.mjs +0 -81
  433. package/lib/setup/state.test.mjs +0 -115
  434. package/lib/singleton.test.mjs +0 -151
  435. package/lib/subagents/cli.test.mjs +0 -389
  436. package/lib/subagents/client.test.mjs +0 -309
  437. package/lib/subagents/gap.test.mjs +0 -234
  438. package/lib/subagents/lock.test.mjs +0 -248
  439. package/lib/subagents/manifest.test.mjs +0 -175
  440. package/lib/subagents/refs.test.mjs +0 -204
  441. package/lib/subagents/resolve.test.mjs +0 -422
  442. package/lib/subagents/schema.test.mjs +0 -328
  443. package/lib/telemetry/alerts.test.mjs +0 -109
  444. package/lib/telemetry/collect.test.mjs +0 -1274
  445. package/lib/tool-definitions-integration.test.mjs +0 -83
  446. package/lib/tool-definitions.test.mjs +0 -437
  447. package/lib/upgrade/global-refresh.test.mjs +0 -65
  448. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  449. package/lib/upgrade/post-steps.test.mjs +0 -200
  450. package/lib/upgrade/verify.test.mjs +0 -164
  451. package/lib/util/fetch-timeout.test.mjs +0 -202
  452. package/lib/util/reconnect.test.mjs +0 -369
  453. package/lib/util/unhandled.test.mjs +0 -216
  454. package/lib/voice/outbound.test.mjs +0 -69
  455. package/lib/voice/session-rotation.test.mjs +0 -114
  456. package/lib/voice/stt.test.mjs +0 -226
  457. package/lib/voice/voice.test.mjs +0 -990
  458. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  459. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  460. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  461. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  462. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  463. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  464. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  465. package/scripts/ci/check.test.mjs +0 -194
  466. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  467. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  468. package/scripts/collective/hook-runner.test.mjs +0 -173
  469. package/scripts/cost/fleet-digest.test.mjs +0 -207
  470. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  471. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  472. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  473. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  474. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  475. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  476. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  477. package/scripts/daemon/assurance.test.mjs +0 -1791
  478. package/scripts/daemon/board-mirror.test.mjs +0 -165
  479. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  480. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  481. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  482. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  483. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  484. package/scripts/daemon/classifier.test.mjs +0 -266
  485. package/scripts/daemon/classify-kind.test.mjs +0 -40
  486. package/scripts/daemon/context-compiler.test.mjs +0 -300
  487. package/scripts/daemon/deliver.test.mjs +0 -564
  488. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  489. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  490. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  491. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  492. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  493. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  494. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  495. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  496. package/scripts/daemon/integration.test.mjs +0 -149
  497. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  498. package/scripts/daemon/lib/session-router.test.mjs +0 -295
  499. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  500. package/scripts/daemon/prompt-builder.test.mjs +0 -344
  501. package/scripts/daemon/responder-cost.test.mjs +0 -68
  502. package/scripts/daemon/responder-history.test.mjs +0 -185
  503. package/scripts/daemon/sdk-version.test.mjs +0 -31
  504. package/scripts/daemon/session-lock.test.mjs +0 -252
  505. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  506. package/scripts/daemon/typing-registry.test.mjs +0 -102
  507. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  508. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  509. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  510. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  511. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  512. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  513. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  514. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  515. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  516. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  517. package/scripts/poller-launchd/install.test.mjs +0 -243
  518. package/scripts/restore-from-backup.test.mjs +0 -181
  519. package/scripts/session/feed.test.mjs +0 -196
  520. package/scripts/session/supervisor-sh.test.mjs +0 -218
  521. package/scripts/session/supervisor.test.mjs +0 -482
  522. package/scripts/setup/configure-macos.test.mjs +0 -306
  523. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  524. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  525. package/scripts/setup/generate-capability.test.mjs +0 -134
  526. package/scripts/setup/init-agent.test.mjs +0 -370
  527. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  528. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  529. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -28,7 +28,13 @@ import { spawn } from "child_process";
28
28
  import { join } from "path";
29
29
  import { randomUUID } from "crypto";
30
30
  import { checkRecentlySent, registerSent } from "./session-lock.mjs";
31
- import { routingKey as deriveRoutingKey, createRouter } from "./lib/session-router.mjs";
31
+ import {
32
+ routingKey as deriveRoutingKey,
33
+ createRouter,
34
+ routerItemFromDaemonItem,
35
+ claimSession,
36
+ releaseSession,
37
+ } from "./lib/session-router.mjs";
32
38
  // Permission scoping (security CRITICAL / audit H1). sessionPermissionArgs()
33
39
  // preserves the historical "--dangerously-skip-permissions" by default and only
34
40
  // scopes tools when the operator opts in (MAESTRO_SCOPED_PERMISSIONS=1), so the
@@ -66,11 +72,22 @@ import { generateAck } from "./assurance.mjs";
66
72
  // This path never renders the persona block, which is why the doctrine is a
67
73
  // separately-exported constant rather than part of voiceRules().
68
74
  import { MESSAGE_CRAFT } from "../../lib/identity/persona.mjs";
75
+ // A human's roll-call to the whole room. The reply-shaping block (≤ 5 lines,
76
+ // about myself only, no @mentions, no ack) lives with the vocabulary in
77
+ // broadcast.mjs so the session path (prompt-builder) says the same thing this
78
+ // path does. What the seat is DOING is not read from here: the one renderer of
79
+ // "what am I doing" is lib/session/current-work (loadCurrentWork below), and
80
+ // responder-single-source.test.mjs pins that no second one is imported again.
81
+ import { isRollCallItem, collectiveInstructions } from "../../lib/org/inbound/broadcast.mjs";
82
+ import { itemCollectiveIntent } from "../../lib/org/inbound/collective.mjs";
69
83
 
70
84
  const AGENT_REPO_DIR = process.env.AGENT_DIR || join(new URL(".", import.meta.url).pathname, "../..");
71
85
  const SONNET_MODEL = "claude-sonnet-4-6";
72
86
  // Resolve claude against the agent's PATH (not launchd's bare env).
73
- import { resolveClaudeBin, augmentedPath, daemonClaudeArgs } from "../../lib/claude-bin.mjs";
87
+ import { resolveClaudeBin } from "../../lib/claude-bin.mjs";
88
+ import { buildSpawn } from "../../lib/runtime/adapter.mjs";
89
+ import { resolveSeatSpawn } from "../../lib/runtime/seat-engine.mjs";
90
+ import { isPrivateConversation, interactionWriteDirNames } from "../../lib/context/history-scope.mjs";
74
91
  const CLAUDE_BIN = resolveClaudeBin();
75
92
  const CLAUDE_CLI_TIMEOUT_MS = 60_000;
76
93
  const SESSION_REGISTRY_PATH = join(AGENT_REPO_DIR, "state", "daemon", "session-router-registry.json");
@@ -101,45 +118,15 @@ function getRouter() {
101
118
  }
102
119
 
103
120
  /**
104
- * Translate a daemon-shaped item into the source/channel/thread_ts shape
105
- * that session-router's routingKey() expects.
121
+ * Translate a daemon-shaped item into the `{source, channel, …}` shape
122
+ * `session-router`'s `routingKey()` expects.
106
123
  *
107
- * Daemon items use {service, channel, thread_id, sender_email, ...}; the
108
- * router's pure key fn was specced against {source, channel, thread_ts,
109
- * thread_id, ...} per memo §4.2. This adapter is the seam between them.
110
- *
111
- * Returns null for items the router can't key (e.g. service we don't yet
112
- * support, or missing required fields). Caller falls back to a fresh
113
- * pre-minted UUID + EPHEMERAL semantics.
124
+ * The adapter itself now lives in `lib/session-router.mjs` beside the key
125
+ * function, because `dispatcher.mjs` needs the SAME translation: two copies
126
+ * would let one room be keyed two ways, which is the same thing as having no
127
+ * router. Exported here for the responder's own tests.
114
128
  */
115
- function deriveRouterItem(item) {
116
- if (!item || typeof item !== "object") return null;
117
-
118
- if (item.service === "slack") {
119
- const channel = item.channel || item.channel_id;
120
- if (!channel) return null;
121
- return {
122
- source: "slack",
123
- channel,
124
- thread_ts: item.thread_id || item.thread_ts || null,
125
- ts: item.ts || item.timestamp || null,
126
- };
127
- }
128
-
129
- if (item.service === "gmail") {
130
- const tid = item.thread_id || item.threadId;
131
- if (!tid) return null;
132
- return { source: "gmail", thread_id: tid };
133
- }
134
-
135
- if (item.service === "calendar") {
136
- const eid = item.event_id || item.eventId;
137
- if (!eid) return null;
138
- return { source: "calendar", event_id: eid };
139
- }
140
-
141
- return null;
142
- }
129
+ export const deriveRouterItem = routerItemFromDaemonItem;
143
130
 
144
131
  // Lazy token access — dotenv loads in daemon main before these are called
145
132
  function getSlackToken() {
@@ -167,6 +154,82 @@ export function unmeasuredReasonFor({ parsed, timedOut, exitCode, parseError, ti
167
154
  return "no-json-envelope";
168
155
  }
169
156
 
157
+ /**
158
+ * Build the value `runClaudeCLI` resolves with, from a clean-exit close.
159
+ *
160
+ * With a parsed JSON envelope the reply text is `result`. Without one the
161
+ * call FAILS CLOSED: `text` is "" and `error` names why. The raw stdout is
162
+ * never surfaced as text — it is a whole model turn, not a reply, and the
163
+ * caller's empty-text guard turns it into "nothing to send" rather than a
164
+ * message on the wire. `withheldChars` records how much was dropped so the
165
+ * operator can see the size of the turn that was kept off the wire.
166
+ *
167
+ * @param {{ parsed: object|null, rawStdout: string, exitCode: number|null }} a
168
+ * @returns {{ text: string, jsonResult: object|null, exitCode: number|null, error?: string, withheldChars?: number }}
169
+ */
170
+ export function cliResultFor({ parsed, rawStdout, exitCode }) {
171
+ if (parsed) {
172
+ return { text: parsed.result ?? "", jsonResult: parsed, exitCode };
173
+ }
174
+ return {
175
+ text: "",
176
+ jsonResult: null,
177
+ exitCode,
178
+ error: "claude CLI output was not JSON; raw stdout withheld from the wire",
179
+ withheldChars: (rawStdout || "").length,
180
+ };
181
+ }
182
+
183
+ /**
184
+ * The tracker argv for a finished responder spawn — PURE, so the ledger row a
185
+ * given envelope produces is a unit test (engine-envelope-parity.test.mjs).
186
+ * `args` is everything after the tracker's script path.
187
+ *
188
+ * @param {{json:object|null, model:string, durationMs:number, exitCode:number, unmeasuredReason:string|null}} o
189
+ * @returns {{args:string[], measured:boolean, unmeasured:string|null}}
190
+ */
191
+ export function responderCostArgs({ json, model, durationMs, exitCode, unmeasuredReason }) {
192
+ const usage = json && typeof json.usage === "object" && json.usage ? json.usage : null;
193
+ const inputTokens = usage ? Number(usage.input_tokens) : NaN;
194
+ const outputTokens = usage ? Number(usage.output_tokens) : NaN;
195
+ const measured = Number.isFinite(inputTokens) && Number.isFinite(outputTokens);
196
+
197
+ // Map the CLI-resolved model id onto the tracker's coarse pricing class.
198
+ const resolved = json && typeof json.model === "string" ? json.model : model || "";
199
+ const modelClass = /opus/i.test(resolved) ? "opus" : /haiku/i.test(resolved) ? "haiku" : "sonnet";
200
+
201
+ const args = [
202
+ "record",
203
+ "--cadence", "responder",
204
+ "--source", "responder",
205
+ "--model", modelClass,
206
+ "--duration-ms", String(durationMs),
207
+ "--exit", String(exitCode),
208
+ ];
209
+
210
+ let unmeasured = null;
211
+ if (measured) {
212
+ args.push("--input-tokens", String(inputTokens));
213
+ args.push("--output-tokens", String(outputTokens));
214
+ const cacheRead = Number(usage.cache_read_input_tokens);
215
+ if (Number.isFinite(cacheRead)) args.push("--cache-read-tokens", String(cacheRead));
216
+ const cacheWrite = Number(usage.cache_creation_input_tokens);
217
+ if (Number.isFinite(cacheWrite)) args.push("--cache-creation-tokens", String(cacheWrite));
218
+ // A NUMBER, not anything Number() will coerce: the Cohort Engine writes
219
+ // `total_cost_usd: null` when not every request reported gateway cost,
220
+ // and `Number(null)` is 0 — which passed the finite/non-negative check
221
+ // and priced the session as $0.00 authoritative. Absent is the honest
222
+ // flag: the tracker then prices from tokens or records the row unpriced.
223
+ // (dispatcher.mjs#parseUsageFromText carries the same fix.)
224
+ const totalCost = json.total_cost_usd;
225
+ if (typeof totalCost === "number" && Number.isFinite(totalCost) && totalCost >= 0) args.push("--total-cost-usd", String(totalCost));
226
+ } else {
227
+ unmeasured = unmeasuredReason || (json ? "no-usage-field" : "no-json-envelope");
228
+ args.push("--tokens-unknown", unmeasured);
229
+ }
230
+ return { args, measured, unmeasured };
231
+ }
232
+
170
233
  /**
171
234
  * Append a cost-ledger row for a responder session (source "responder").
172
235
  *
@@ -199,40 +262,12 @@ function recordResponderCost({ json, model, durationMs, exitCode, unmeasuredReas
199
262
  const trackerPath = join(AGENT_REPO_DIR, "scripts/cost/track-claude-usage.mjs");
200
263
  if (!existsSync(trackerPath)) return;
201
264
 
202
- const usage = json && typeof json.usage === "object" && json.usage ? json.usage : null;
203
- const inputTokens = usage ? Number(usage.input_tokens) : NaN;
204
- const outputTokens = usage ? Number(usage.output_tokens) : NaN;
205
- const measured = Number.isFinite(inputTokens) && Number.isFinite(outputTokens);
206
-
207
- // Map the CLI-resolved model id onto the tracker's coarse pricing class.
208
- const resolved = json && typeof json.model === "string" ? json.model : model || "";
209
- const modelClass = /opus/i.test(resolved) ? "opus" : /haiku/i.test(resolved) ? "haiku" : "sonnet";
210
-
211
- const args = [
212
- trackerPath, "record",
213
- "--cadence", "responder",
214
- "--source", "responder",
215
- "--model", modelClass,
216
- "--duration-ms", String(durationMs),
217
- "--exit", String(exitCode),
218
- ];
219
-
220
- if (measured) {
221
- args.push("--input-tokens", String(inputTokens));
222
- args.push("--output-tokens", String(outputTokens));
223
- const cacheRead = Number(usage.cache_read_input_tokens);
224
- if (Number.isFinite(cacheRead)) args.push("--cache-read-tokens", String(cacheRead));
225
- const cacheWrite = Number(usage.cache_creation_input_tokens);
226
- if (Number.isFinite(cacheWrite)) args.push("--cache-creation-tokens", String(cacheWrite));
227
- const totalCost = Number(json.total_cost_usd);
228
- if (Number.isFinite(totalCost) && totalCost >= 0) args.push("--total-cost-usd", String(totalCost));
229
- } else {
230
- const reason = unmeasuredReason || (json ? "no-usage-field" : "no-json-envelope");
231
- args.push("--tokens-unknown", reason);
232
- console.warn(`[responder] session usage unmeasured (${reason}) — recorded as unknown, not zero`);
265
+ const { args, unmeasured } = responderCostArgs({ json, model, durationMs, exitCode, unmeasuredReason });
266
+ if (unmeasured) {
267
+ console.warn(`[responder] session usage unmeasured (${unmeasured}) — recorded as unknown, not zero`);
233
268
  }
234
269
 
235
- spawn(process.execPath, args, {
270
+ spawn(process.execPath, [trackerPath, ...args], {
236
271
  stdio: "ignore",
237
272
  env: { ...process.env, AGENT_ROOT: AGENT_REPO_DIR, AGENT_DIR: AGENT_REPO_DIR },
238
273
  }).unref();
@@ -257,37 +292,56 @@ function recordResponderCost({ json, model, durationMs, exitCode, unmeasuredReas
257
292
  // the same UUID across spawns is the resume mechanism).
258
293
  //
259
294
  // @returns {Promise<{ text: string, jsonResult: object|null, exitCode: number }>}
295
+ /**
296
+ * The responder lane's spawn — binary, argv, env — from the runtime adapter
297
+ * (lane "responder"): --print <permissions> <bare MCP> --model <m>
298
+ * --append-system-prompt <sys> --output-format json [--session-id <id>].
299
+ * --output-format json is requested unconditionally (it is only valid with
300
+ * --print, which is always passed): a reply without the usage envelope cannot
301
+ * be priced, and the parse keeps a raw-text fallback for older CLIs. Env is the
302
+ * stock posture — stale ANTHROPIC_API_KEY/AUTH_TOKEN stripped so the CLI rides
303
+ * the seat's subscription credential; PATH augmented.
304
+ *
305
+ * W4-E1 (CF-44): `seat` is the seat's engine resolved once for this spawn
306
+ * (lib/runtime/seat-engine.mjs). A claude seat adds no fields, so its spawn is
307
+ * unchanged; an engine-cohort seat runs `cli.mjs run`, continuing --session-id.
308
+ * @param {{systemPrompt:string, userPrompt:string, model:string, sessionId?:string|null, env?:object, bin?:string, seat?:{fields:object}}} o
309
+ */
310
+ export function responderSpawn({ systemPrompt, userPrompt, model, sessionId = null, env = process.env, bin = CLAUDE_BIN, seat = { fields: {} } }) {
311
+ return buildSpawn({
312
+ lane: "responder",
313
+ bin,
314
+ model,
315
+ systemPrompt,
316
+ prompt: userPrompt,
317
+ sessionId,
318
+ permissions: sessionPermissionArgs({ source: "responder" }),
319
+ mcp: { source: "responder" },
320
+ env,
321
+ ...seat.fields,
322
+ });
323
+ }
324
+
260
325
  function runClaudeCLI(systemPrompt, userPrompt, model, opts = {}) {
326
+ // W4-E1: the seat's engine, once per spawn (a claude seat reads config/agent.json only).
327
+ return resolveSeatSpawn(AGENT_REPO_DIR).then((seat) => runSeatCLI(seat, systemPrompt, userPrompt, model, opts));
328
+ }
329
+
330
+ function runSeatCLI(seat, systemPrompt, userPrompt, model, opts = {}) {
261
331
  const { sessionId = null, router = null, routingKey = null } = opts;
262
332
  const startedAt = Date.now();
263
333
 
264
334
  return new Promise((resolvePromise, rejectPromise) => {
265
- const args = [
266
- "--print",
267
- ...sessionPermissionArgs({ source: "responder" }),
268
- ...daemonClaudeArgs(undefined, { source: "responder" }),
269
- "--model", model,
270
- "--append-system-prompt", systemPrompt,
271
- // --output-format json is only valid in combination with --print (per b1
272
- // report). We always pass --print above, so this is safe.
273
- //
274
- // This used to be requested ONLY when a sessionId was supplied, which
275
- // meant every non-routed reply ran with no usage envelope and therefore
276
- // no way to price it. Every reply this daemon sends costs real money;
277
- // asking for the envelope unconditionally is what makes it measurable.
278
- // The parse below already has a raw-text fallback for older CLIs.
279
- "--output-format", "json",
280
- ];
281
- if (sessionId) {
282
- args.push("--session-id", sessionId);
335
+ const spawnSpec = responderSpawn({ systemPrompt, userPrompt, model, sessionId, seat });
336
+ if (!spawnSpec.ok) {
337
+ rejectPromise(new Error(`claude CLI unavailable: ${spawnSpec.error.message}`));
338
+ return;
283
339
  }
340
+ const args = spawnSpec.argv;
284
341
 
285
- const proc = spawn(CLAUDE_BIN, args, {
342
+ const proc = spawn(spawnSpec.bin, args, {
286
343
  stdio: ["pipe", "pipe", "pipe"],
287
- // Force claude CLI onto keychain OAuth (Max subscription); strip any
288
- // stale ANTHROPIC_API_KEY/AUTH_TOKEN inherited from the daemon env.
289
- // Augment PATH so the subsession finds homebrew/nvm tools.
290
- env: { ...process.env, PATH: augmentedPath(), ANTHROPIC_API_KEY: "", ANTHROPIC_AUTH_TOKEN: "" },
344
+ env: spawnSpec.env,
291
345
  });
292
346
 
293
347
  let stdout = "";
@@ -350,11 +404,13 @@ function runClaudeCLI(systemPrompt, userPrompt, model, opts = {}) {
350
404
  parsed = trimmed ? JSON.parse(trimmed) : null;
351
405
  if (!parsed) parseError = new Error("no stdout");
352
406
  } catch (err) {
353
- // Legacy fallback (rollout-safety): older CLIs or unexpected output
354
- // shapes shouldn't crash the daemon. Log a warning, surface the raw
355
- // text, and let the caller decide whether to call router.touch.
407
+ // Fail closed: older CLIs or unexpected output shapes must not crash
408
+ // the daemon, but raw stdout of a failed parse is a whole model turn
409
+ // (tool chatter, thinking, the un-enveloped reply) and has reached the
410
+ // wire before. It is withheld; the caller sees empty text plus an
411
+ // error and treats it as nothing to send.
356
412
  parseError = err;
357
- console.warn(`[responder] claude CLI JSON parse failed (sessionId=${sessionId}): ${err.message} — falling back to raw stdout`);
413
+ console.error(`[responder] claude CLI JSON parse failed (sessionId=${sessionId}): ${err.message} — raw stdout withheld from the wire (${trimmed.length} chars)`);
358
414
  }
359
415
 
360
416
  // Every reply this daemon sends is a paid Claude session — INCLUDING the
@@ -381,11 +437,7 @@ function runClaudeCLI(systemPrompt, userPrompt, model, opts = {}) {
381
437
  return;
382
438
  }
383
439
 
384
- if (parsed) {
385
- resolvePromise({ text: parsed.result ?? "", jsonResult: parsed, exitCode: code });
386
- } else {
387
- resolvePromise({ text: trimmed, jsonResult: null, exitCode: code });
388
- }
440
+ resolvePromise(cliResultFor({ parsed, rawStdout: trimmed, exitCode: code }));
389
441
  });
390
442
 
391
443
  try {
@@ -438,10 +490,17 @@ function logInteraction(item, responseText, o = {}) {
438
490
  ? resolveSlackChannel(item)
439
491
  : (item.channel_id || item.channel || null);
440
492
 
441
- // Write to both channel-ID and sender-slug directories
442
- const dirs = [];
443
- if (channelId) dirs.push(join(AGENT_REPO_DIR, "memory", "interactions", service, String(channelId)));
444
- dirs.push(join(AGENT_REPO_DIR, "memory", "interactions", service, `dm-${senderSlug}`));
493
+ // The room always; the PERSON only when the exchange was actually private.
494
+ // This used to write both unconditionally, which made `dm-<slug>` a mixed
495
+ // store of private and public-room content — inert only for as long as
496
+ // nothing read it per-service. It is read now, so the gate belongs at both
497
+ // ends (`lib/context/history-scope.mjs`, design §5.5).
498
+ const dirs = interactionWriteDirNames({
499
+ channelId,
500
+ senderSlug,
501
+ isPrivate: isPrivateConversation(item),
502
+ }).map((name) => join(AGENT_REPO_DIR, "memory", "interactions", service, String(name)));
503
+ if (dirs.length === 0) return;
445
504
 
446
505
  const incomingEntry = {
447
506
  ts: item.ts || item.timestamp || new Date().toISOString(),
@@ -675,6 +734,7 @@ export async function loadConversationHistory(item, deps = {}) {
675
734
  // the real generator, so behaviour is unchanged. The public senders call
676
735
  // generateResponse() through this slot.
677
736
  let _generateResponse;
737
+
678
738
  /**
679
739
  * Test seam: replace the response generator. Pass a function
680
740
  * `(item, classResult) => Promise<string>` to force reply text; pass nothing to
@@ -699,11 +759,99 @@ export function setGenerateResponseForTests(fn) {
699
759
  * and the flag is deliberately not kept "just in case", because keeping it is
700
760
  * how it comes back.
701
761
  */
702
- async function realGenerateResponse(item, classResult) {
762
+ /**
763
+ * The bounded "what am I doing right now" block for a quick reply.
764
+ *
765
+ * The quick path is the one that answers a DM or a channel question IN the
766
+ * turn, and it carried nothing about the agent's own sessions: a "what are you
767
+ * doing right now?" was answered from the conversation alone, i.e. from
768
+ * nothing. lib/session/current-work reads the front door, its peers, the
769
+ * daemon's live children (with the board item each is linked to), the board
770
+ * items in progress and — as existence only — any interactive session a
771
+ * person has open on the box. ≤ 8 lines; "" when there is nothing to say.
772
+ *
773
+ * SCOPED TO THE SENDER'S CONVERSATION. A live session's summary is a
774
+ * restatement of someone's message; it is disclosed here only when it came
775
+ * from this item's conversation, and what is disclosable passes the sender's
776
+ * information barrier — the context compiler's `forbidsTextFor`, the same
777
+ * predicate the spawn path applies to history. Fail-open on the block (no
778
+ * block, not a failed reply); fail-CLOSED on the barrier (no predicate → the
779
+ * audience carries none and only same-conversation work is shown, which is
780
+ * the narrower answer). Injectable for tests.
781
+ *
782
+ * ON A ROLL-CALL this is the whole account of the seat — the front door, its
783
+ * peers, the daemon's live children, the board rows in progress / blocked /
784
+ * queued, the open handoffs — read ONCE, through the same audience and
785
+ * barrier as any other reply (a room never hears a private ask restated). A
786
+ * roll-call asks for `explicitEmpty`: when nothing is known the block still
787
+ * carries the header and the one line that says so, because a roll-call
788
+ * composed from no block is composed from invention.
789
+ *
790
+ * THIS is the path where an absent front door means "the daemon is
791
+ * answering on its own" — so this caller, and only this one, supplies
792
+ * {@link MAIN_ABSENT_NOTE}. The renderer is shared with `maestro session
793
+ * status`, `session_status` and the spawned session's context, from which
794
+ * that sentence would be false, so it does not carry the copy itself.
795
+ */
796
+ export const MAIN_ABSENT_NOTE = "this reply comes from the background responder";
797
+
798
+ export async function loadCurrentWork(item, deps = {}) {
799
+ const rollCall = isRollCallItem(item);
800
+ try {
801
+ const mod = await import("../../lib/session/current-work.mjs");
802
+ const impl = deps.currentWorkBlock || mod.currentWorkBlock;
803
+ let forbidsText;
804
+ try {
805
+ const ff = deps.forbidsTextFor || (await import("./context-compiler.mjs")).forbidsTextFor;
806
+ const slug = item && item.sender ? String(item.sender).replace(/\s+/g, "-").toLowerCase() : null;
807
+ forbidsText = ff(slug);
808
+ } catch {
809
+ forbidsText = undefined;
810
+ }
811
+ const audience = mod.audienceForItem(item, { isPrivate: isPrivateConversation(item), forbidsText });
812
+ return (await impl(AGENT_REPO_DIR, { now: Date.now(), audience, explicitEmpty: rollCall, mainAbsentNote: MAIN_ABSENT_NOTE })) || "";
813
+ } catch {
814
+ return "";
815
+ }
816
+ }
817
+
818
+ /**
819
+ * The quick reply's user content. PURE — exported so the shape is pinned.
820
+ *
821
+ * The current-work block goes in under its own heading with the one rule the
822
+ * persona needs: describe it as your own work, and never quote a session name
823
+ * or id (they are internal — `session_status` says the same).
824
+ */
825
+ export function buildQuickReplyUserContent({ item = {}, classResult = {}, conversationHistory = null, currentWork = "", rollCall = false } = {}) {
826
+ return [
827
+ `From: ${item.sender} (${item.sender_privilege || "unknown"})`,
828
+ `Via: ${item.service} / ${item.channel}`,
829
+ item.subject ? `Subject: ${item.subject}` : null,
830
+ conversationHistory ? `\nRecent conversation history:\n${conversationHistory}` : null,
831
+ currentWork
832
+ ? `\n${currentWork}\n(If asked what you are doing, answer from the lines above — as your own work, in your own words, board item ids included where a person could look one up. Never quote session names or ids.)`
833
+ : null,
834
+ `\nCurrent message:\n${item.content || "(empty)"}`,
835
+ // A roll-call's thread context is the room's other answers — not evidence
836
+ // about this seat, and exactly what the reply must not summarise.
837
+ !rollCall && item.thread_context ? `\nThread context:\n${item.thread_context}` : null,
838
+ `\nClassification: ${classResult.summary}`,
839
+ ].filter(Boolean).join("\n");
840
+ }
841
+
842
+ /**
843
+ * The real, CLI-backed answer generator. `deps` are test seams only —
844
+ * `{runCLI, currentWorkBlock, forbidsTextFor, loadConversationHistory}` — so
845
+ * the user content that reaches the model can be pinned without a spawn.
846
+ * Exported for that pin; production calls it through the `_generateResponse`
847
+ * slot with no deps.
848
+ */
849
+ export async function realGenerateResponse(item, classResult, deps = {}) {
703
850
  const preamble = loadPreamble();
704
851
  const profile = loadUserProfile(item.sender);
705
852
  // The classifier's recommended model (Opus for CEO, Sonnet for routine).
706
853
  const model = classResult.model === "opus" ? "claude-opus-4-6" : SONNET_MODEL;
854
+ const rollCall = isRollCallItem(item);
707
855
 
708
856
  const systemPrompt = `${preamble}
709
857
 
@@ -714,20 +862,20 @@ If it's informational, acknowledge appropriately.
714
862
  Keep responses focused — 1-4 sentences for simple items, up to a short paragraph for more nuanced ones.
715
863
  Match the sender's tone and urgency level.
716
864
  ${profile ? `\nSender profile:\n${profile}` : ""}
717
-
865
+ ${rollCall ? `\n${collectiveInstructions({ intent: itemCollectiveIntent(item, isRollCallItem), respondents: item.respondents })}\n` : ""}
718
866
  ${MESSAGE_CRAFT}`;
719
867
 
720
- const conversationHistory = await loadConversationHistory(item);
868
+ // A roll-call is answered from what this seat KNOWS about itself — its main
869
+ // session, its peers and their purposes, its board items, the sessions the
870
+ // daemon has running — not from the room's history, which is other people's
871
+ // chatter and exactly what the answer must not summarise. ONE view of self:
872
+ // the audience-scoped current-work block (lib/session/current-work; the
873
+ // session registry, barrier-aware, so a room never hears a private ask
874
+ // restated), read once by loadCurrentWork on every path.
875
+ const conversationHistory = rollCall ? null : await (deps.loadConversationHistory || loadConversationHistory)(item);
876
+ const currentWork = await loadCurrentWork(item, deps);
721
877
 
722
- const userContent = [
723
- `From: ${item.sender} (${item.sender_privilege || "unknown"})`,
724
- `Via: ${item.service} / ${item.channel}`,
725
- item.subject ? `Subject: ${item.subject}` : null,
726
- conversationHistory ? `\nRecent conversation history:\n${conversationHistory}` : null,
727
- `\nCurrent message:\n${item.content || "(empty)"}`,
728
- item.thread_context ? `\nThread context:\n${item.thread_context}` : null,
729
- `\nClassification: ${classResult.summary}`,
730
- ].filter(Boolean).join("\n");
878
+ const userContent = buildQuickReplyUserContent({ item, classResult, conversationHistory, currentWork, rollCall });
731
879
 
732
880
  // (b2) Session-router decision. Compute the routing key from a daemon→router
733
881
  // adapter view of the item. If the item can't be keyed (unknown service,
@@ -738,14 +886,25 @@ ${MESSAGE_CRAFT}`;
738
886
  let sessionId = null;
739
887
  if (routerItem) {
740
888
  try {
741
- key = deriveRoutingKey(routerItem);
742
- const decision = router.route(key);
743
- if (decision.decision === "RESUME" && decision.resumeId) {
744
- sessionId = decision.resumeId;
889
+ const candidateKey = deriveRoutingKey(routerItem);
890
+ const decision = router.route(candidateKey);
891
+ // One CLI process per key. `claimSession` is the same lease the dispatcher
892
+ // takes, in the same process, so a 60-second quick reply can never be
893
+ // handed the session id of a long agentic run that is still going — and
894
+ // two quick replies from one burst cannot share a transcript either.
895
+ if (claimSession(candidateKey)) {
896
+ key = candidateKey;
897
+ if (decision.decision === "RESUME" && decision.resumeId) {
898
+ sessionId = decision.resumeId;
899
+ } else {
900
+ // EPHEMERAL or EPHEMERAL_REPLACE — pre-mint a fresh UUID. Reusing
901
+ // the same key on next call (with a different sessionId) is fine;
902
+ // touch() will overwrite the registry entry.
903
+ sessionId = randomUUID();
904
+ }
745
905
  } else {
746
- // EPHEMERAL or EPHEMERAL_REPLACE — pre-mint a fresh UUID. Reusing
747
- // the same key on next call (with a different sessionId) is fine;
748
- // touch() will overwrite the registry entry.
906
+ // Already in flight: spawn cold, and stay out of the registry so the
907
+ // running session keeps ownership of the row.
749
908
  sessionId = randomUUID();
750
909
  }
751
910
  } catch (err) {
@@ -757,30 +916,40 @@ ${MESSAGE_CRAFT}`;
757
916
  }
758
917
  }
759
918
 
760
- const cliResult = await runClaudeCLI(systemPrompt, userContent, model, {
761
- sessionId,
762
- router: key ? router : null,
763
- routingKey: key,
764
- });
919
+ // The claim is held across the spawn AND the registry write that follows it,
920
+ // and released in a `finally`: a throw anywhere in between must not leave the
921
+ // room's key claimed forever, which would silently disable continuity for it.
922
+ try {
923
+ const cliResult = await (deps.runCLI || runClaudeCLI)(systemPrompt, userContent, model, {
924
+ sessionId,
925
+ router: key ? router : null,
926
+ routingKey: key,
927
+ });
765
928
 
766
- const text = (cliResult.text || "").trim();
767
- if (!text) {
768
- throw new Error("claude CLI returned empty result text in generateResponse");
769
- }
929
+ const text = (cliResult.text || "").trim();
930
+ if (!text) {
931
+ // Empty text is "nothing to send" — including the fail-closed non-JSON
932
+ // path, where the raw model turn was withheld. Throwing here is what
933
+ // keeps it off the wire; the reason rides along for the log.
934
+ throw new Error(`claude CLI returned empty result text in generateResponse${cliResult.error ? ` (${cliResult.error}; ${cliResult.withheldChars ?? 0} chars withheld)` : ""}`);
935
+ }
770
936
 
771
- // (b3) On success, touch the registry with the CLI-resolved session_id so
772
- // the next call routed to this key gets a RESUME decision. Skip touch on
773
- // is_error responses or when JSON parsing failed (legacy fallback path).
774
- if (key && cliResult.jsonResult && cliResult.jsonResult.is_error !== true) {
775
- const claudeSessionId = cliResult.jsonResult.session_id || sessionId;
776
- try {
777
- await router.touch(key, { claudeSessionId, model });
778
- } catch (err) {
779
- console.warn(`[responder] router.touch failed for ${key}: ${err.message}`);
937
+ // (b3) On success, touch the registry with the CLI-resolved session_id so
938
+ // the next call routed to this key gets a RESUME decision. Skip touch on
939
+ // is_error responses or when JSON parsing failed (legacy fallback path).
940
+ if (key && cliResult.jsonResult && cliResult.jsonResult.is_error !== true) {
941
+ const claudeSessionId = cliResult.jsonResult.session_id || sessionId;
942
+ try {
943
+ await router.touch(key, { claudeSessionId, model });
944
+ } catch (err) {
945
+ console.warn(`[responder] router.touch failed for ${key}: ${err.message}`);
946
+ }
780
947
  }
781
- }
782
948
 
783
- return text;
949
+ return text;
950
+ } finally {
951
+ if (key) releaseSession(key);
952
+ }
784
953
  }
785
954
 
786
955
  // Default the generator slot to the real CLI-backed implementation. (Declared