@cohortapp/agent-sdk 2.17.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 (526) 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.mjs +8 -6
  23. package/lib/collective/global-config.mjs +63 -1
  24. package/lib/collective/presence.mjs +142 -5
  25. package/lib/comms/send-gate.mjs +559 -1
  26. package/lib/diagnostics/alerts.mjs +49 -0
  27. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  28. package/lib/engine/agents/definitions.mjs +343 -0
  29. package/lib/engine/agents/persist.mjs +275 -0
  30. package/lib/engine/agents/runtime.mjs +748 -0
  31. package/lib/engine/agents/usage.mjs +95 -0
  32. package/lib/engine/auth-status.mjs +139 -0
  33. package/lib/engine/budget.mjs +194 -0
  34. package/lib/engine/cli.mjs +1204 -0
  35. package/lib/engine/commands/index.mjs +269 -0
  36. package/lib/engine/context/budget.mjs +219 -0
  37. package/lib/engine/context/cache.mjs +125 -0
  38. package/lib/engine/context/child-env.mjs +215 -0
  39. package/lib/engine/context/compaction.mjs +342 -0
  40. package/lib/engine/context/images.mjs +90 -0
  41. package/lib/engine/context/instructions.mjs +327 -0
  42. package/lib/engine/context/lazy-instructions.mjs +169 -0
  43. package/lib/engine/context/manager.mjs +182 -0
  44. package/lib/engine/context/real-path.mjs +91 -0
  45. package/lib/engine/context/secret-values.mjs +163 -0
  46. package/lib/engine/context/settings.mjs +274 -0
  47. package/lib/engine/context/stream-input.mjs +159 -0
  48. package/lib/engine/guard.mjs +152 -0
  49. package/lib/engine/hooks.mjs +713 -0
  50. package/lib/engine/loop.mjs +560 -0
  51. package/lib/engine/mcp/client.mjs +254 -0
  52. package/lib/engine/mcp/config.mjs +301 -0
  53. package/lib/engine/mcp/http.mjs +201 -0
  54. package/lib/engine/mcp/index.mjs +146 -0
  55. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  56. package/lib/engine/mcp/naming.mjs +66 -0
  57. package/lib/engine/mcp/resources.mjs +89 -0
  58. package/lib/engine/mcp/results.mjs +133 -0
  59. package/lib/engine/mcp/stdio.mjs +137 -0
  60. package/lib/engine/mcp/supervisor.mjs +116 -0
  61. package/lib/engine/messages.mjs +104 -0
  62. package/lib/engine/output/json.mjs +164 -0
  63. package/lib/engine/output/stream-json.mjs +266 -0
  64. package/lib/engine/permissions.mjs +845 -0
  65. package/lib/engine/process-identity.mjs +164 -0
  66. package/lib/engine/process-tree.mjs +551 -0
  67. package/lib/engine/prompt.mjs +60 -0
  68. package/lib/engine/session/store.mjs +299 -0
  69. package/lib/engine/session-runtime/args.mjs +97 -0
  70. package/lib/engine/session-runtime/host.mjs +143 -0
  71. package/lib/engine/session-runtime/inbox.mjs +122 -0
  72. package/lib/engine/session-runtime/notifications.mjs +129 -0
  73. package/lib/engine/session-runtime/registry.mjs +328 -0
  74. package/lib/engine/session-runtime/runner.mjs +344 -0
  75. package/lib/engine/session-runtime/socket.mjs +212 -0
  76. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  77. package/lib/engine/skills/index.mjs +321 -0
  78. package/lib/engine/tools/bash-background.mjs +533 -0
  79. package/lib/engine/tools/bash.mjs +216 -0
  80. package/lib/engine/tools/edit.mjs +97 -0
  81. package/lib/engine/tools/glob.mjs +81 -0
  82. package/lib/engine/tools/grep.mjs +224 -0
  83. package/lib/engine/tools/index.mjs +84 -0
  84. package/lib/engine/tools/list-agents.mjs +32 -0
  85. package/lib/engine/tools/ls.mjs +127 -0
  86. package/lib/engine/tools/monitor.mjs +82 -0
  87. package/lib/engine/tools/notebook-edit.mjs +218 -0
  88. package/lib/engine/tools/read.mjs +103 -0
  89. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  90. package/lib/engine/tools/schema.mjs +144 -0
  91. package/lib/engine/tools/send-message.mjs +77 -0
  92. package/lib/engine/tools/session.mjs +70 -0
  93. package/lib/engine/tools/todo.mjs +144 -0
  94. package/lib/engine/tools/toolsearch.mjs +217 -0
  95. package/lib/engine/tools/walk.mjs +193 -0
  96. package/lib/engine/tools/web-switch.mjs +31 -0
  97. package/lib/engine/tools/webfetch-html.mjs +387 -0
  98. package/lib/engine/tools/webfetch-net.mjs +340 -0
  99. package/lib/engine/tools/webfetch.mjs +198 -0
  100. package/lib/engine/tools/websearch.mjs +91 -0
  101. package/lib/engine/tools/workflow.mjs +95 -0
  102. package/lib/engine/tools/write.mjs +76 -0
  103. package/lib/engine/tui/line-editor.mjs +137 -0
  104. package/lib/engine/tui/render.mjs +86 -0
  105. package/lib/engine/tui/tui.mjs +274 -0
  106. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  107. package/lib/engine/wire/effort.mjs +36 -0
  108. package/lib/engine/wire/errors.mjs +496 -0
  109. package/lib/engine/wire/http.mjs +441 -0
  110. package/lib/engine/wire/index.mjs +76 -0
  111. package/lib/engine/wire/openai-chat.mjs +332 -0
  112. package/lib/engine/wire/prompt-cache.mjs +79 -0
  113. package/lib/engine/wire/search.mjs +140 -0
  114. package/lib/engine/wire/sse.mjs +114 -0
  115. package/lib/engine/wire/stall.mjs +349 -0
  116. package/lib/engine/wire/token-provider.mjs +175 -0
  117. package/lib/engine/wire/usage.mjs +192 -0
  118. package/lib/engine/workflow/host.mjs +524 -0
  119. package/lib/engine/workflow/journal.mjs +188 -0
  120. package/lib/engine/workflow/json-schema.mjs +171 -0
  121. package/lib/engine/workflow/meta.mjs +329 -0
  122. package/lib/engine/workflow/notifications.mjs +52 -0
  123. package/lib/engine/workflow/runtime.mjs +447 -0
  124. package/lib/engine/workflow/sandbox.mjs +534 -0
  125. package/lib/engine/workflow/worker.mjs +141 -0
  126. package/lib/engine/workflow/worktree.mjs +74 -0
  127. package/lib/execution/disposition.mjs +1 -1
  128. package/lib/execution/intake.mjs +10 -0
  129. package/lib/execution/surface-policy.mjs +15 -0
  130. package/lib/learning/curator.mjs +8 -6
  131. package/lib/learning/reflect.mjs +8 -6
  132. package/lib/model-router/catalog/cohort.yaml +137 -0
  133. package/lib/model-router/catalog.mjs +118 -1
  134. package/lib/model-router/failover.mjs +67 -16
  135. package/lib/model-router/llm-task.mjs +39 -3
  136. package/lib/model-router/resolve.mjs +89 -3
  137. package/lib/model-router/spawn.mjs +46 -47
  138. package/lib/model-router/taxonomy.mjs +126 -4
  139. package/lib/org/cost-sync.mjs +141 -11
  140. package/lib/org/inbound/broadcast.mjs +289 -0
  141. package/lib/org/inbound/collective.mjs +375 -0
  142. package/lib/org/inbound/directedness.mjs +96 -8
  143. package/lib/org/inbound/facts.mjs +78 -2
  144. package/lib/org/inbound/project.mjs +22 -0
  145. package/lib/org/inbound/surfaces.mjs +14 -0
  146. package/lib/org/llm-token.mjs +879 -0
  147. package/lib/org/mesh.mjs +61 -0
  148. package/lib/org/messaging.mjs +3 -1
  149. package/lib/org/protocol.checksum +1 -1
  150. package/lib/org/protocol.mjs +15 -0
  151. package/lib/org/quota.mjs +520 -0
  152. package/lib/org/tool-surface.mjs +104 -16
  153. package/lib/org/ui-parity.mjs +16 -1
  154. package/lib/org/work-ledger.mjs +37 -6
  155. package/lib/rate-guard.mjs +114 -1
  156. package/lib/resource-governor.mjs +41 -6
  157. package/lib/runtime/adapter.mjs +823 -0
  158. package/lib/runtime/child-env.mjs +191 -0
  159. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  160. package/lib/runtime/seat-engine.mjs +162 -0
  161. package/lib/session/ask-ledger.mjs +271 -0
  162. package/lib/session/current-work.mjs +676 -0
  163. package/lib/session/feed-core.mjs +40 -3
  164. package/lib/session/launch-args.mjs +56 -4
  165. package/lib/session/status-summary.mjs +26 -9
  166. package/lib/session/upgrade-notice.mjs +42 -0
  167. package/lib/setup/claude-probe.mjs +117 -13
  168. package/lib/setup/enrich.mjs +13 -10
  169. package/lib/setup/sections/model.mjs +39 -13
  170. package/lib/telemetry/collect.mjs +208 -9
  171. package/lib/upgrade/ignored-drift.mjs +105 -0
  172. package/lib/voice/post-call-brief.mjs +30 -17
  173. package/package.json +13 -3
  174. package/plugins/maestro-skills/skills/board-work.md +5 -0
  175. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  176. package/plugins/maestro-skills/skills/main-session.md +18 -7
  177. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  178. package/scripts/ci/run-tests.mjs +47 -19
  179. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  180. package/scripts/collective/hook-runner.mjs +29 -2
  181. package/scripts/continuous-monitor.sh +13 -0
  182. package/scripts/cost/track-claude-usage.mjs +15 -0
  183. package/scripts/daemon/agent-daemon.mjs +408 -20
  184. package/scripts/daemon/assurance.mjs +48 -12
  185. package/scripts/daemon/cadence-consumer.mjs +218 -68
  186. package/scripts/daemon/cadence-handlers.mjs +73 -4
  187. package/scripts/daemon/classifier.mjs +75 -26
  188. package/scripts/daemon/context-compiler.mjs +51 -37
  189. package/scripts/daemon/deliver.mjs +30 -1
  190. package/scripts/daemon/dispatcher.mjs +595 -149
  191. package/scripts/daemon/health.mjs +14 -1
  192. package/scripts/daemon/maestro-daemon.mjs +11 -0
  193. package/scripts/daemon/prompt-builder.mjs +24 -0
  194. package/scripts/daemon/responder.mjs +246 -79
  195. package/scripts/daemon/sdk-version.mjs +98 -16
  196. package/scripts/eval/probe-gateway.mjs +635 -0
  197. package/scripts/eval/replay/extract.mjs +270 -0
  198. package/scripts/eval/replay/grade.mjs +260 -0
  199. package/scripts/eval/replay/lib/config.mjs +50 -0
  200. package/scripts/eval/replay/lib/effects.mjs +65 -0
  201. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  202. package/scripts/eval/replay/lib/judge.mjs +72 -0
  203. package/scripts/eval/replay/lib/redact.mjs +136 -0
  204. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  205. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  206. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  207. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  208. package/scripts/eval/replay/report.mjs +185 -0
  209. package/scripts/eval/replay/run.mjs +404 -0
  210. package/scripts/fleet/rollout.mjs +1094 -0
  211. package/scripts/hooks/pre-send-audit.sh +36 -245
  212. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  213. package/scripts/hooks/validate-state-yaml.sh +190 -0
  214. package/scripts/huddle/huddle-llm.mjs +361 -0
  215. package/scripts/huddle/huddle-server.mjs +46 -121
  216. package/scripts/local-triggers/autoupdate.sh +448 -78
  217. package/scripts/local-triggers/run-trigger.sh +13 -0
  218. package/scripts/maintenance/pin-integrity.mjs +364 -0
  219. package/scripts/poll-slack-events.sh +41 -9
  220. package/scripts/poller/slack-socket-mode.mjs +28 -3
  221. package/scripts/session/supervisor.mjs +80 -13
  222. package/scripts/spawn-session.sh +13 -0
  223. package/bin/maestro.test.mjs +0 -1574
  224. package/lib/action-executor.test.mjs +0 -871
  225. package/lib/archetype.test.mjs +0 -132
  226. package/lib/assurance/plan-note.test.mjs +0 -234
  227. package/lib/assurance/room-budget.test.mjs +0 -486
  228. package/lib/assurance/tier.test.mjs +0 -174
  229. package/lib/autonomy.test.mjs +0 -66
  230. package/lib/backlog.test.mjs +0 -302
  231. package/lib/backup/policy.test.mjs +0 -305
  232. package/lib/budget-escalate.test.mjs +0 -232
  233. package/lib/budget-guard.envelope.test.mjs +0 -476
  234. package/lib/budget-guard.test.mjs +0 -427
  235. package/lib/cadence-bus-requeue.test.mjs +0 -83
  236. package/lib/cadence-bus-schedule.test.mjs +0 -194
  237. package/lib/cadence-bus.test.mjs +0 -720
  238. package/lib/cadences.test.mjs +0 -230
  239. package/lib/capability/inventory.test.mjs +0 -232
  240. package/lib/capability.test.mjs +0 -78
  241. package/lib/channels/base-adapter.test.mjs +0 -590
  242. package/lib/channels/channels.test.mjs +0 -371
  243. package/lib/channels/contract.test.mjs +0 -162
  244. package/lib/channels/inbox-item.test.mjs +0 -368
  245. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  246. package/lib/channels/pairing.test.mjs +0 -270
  247. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  248. package/lib/channels/slack-adapter.test.mjs +0 -212
  249. package/lib/channels/telegram-adapter.test.mjs +0 -306
  250. package/lib/channels/voice/adapter.test.mjs +0 -278
  251. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  252. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  253. package/lib/charter.test.mjs +0 -89
  254. package/lib/claude-bin.test.mjs +0 -131
  255. package/lib/cli/board.test.mjs +0 -227
  256. package/lib/cli/design.test.mjs +0 -270
  257. package/lib/cli/doctor-checks.test.mjs +0 -336
  258. package/lib/cli/global-setup-extras.test.mjs +0 -462
  259. package/lib/cli/inbox.test.mjs +0 -230
  260. package/lib/cli/session-ack.test.mjs +0 -63
  261. package/lib/cli/session.test.mjs +0 -613
  262. package/lib/collective/capture.test.mjs +0 -121
  263. package/lib/collective/cards.test.mjs +0 -114
  264. package/lib/collective/config.test.mjs +0 -123
  265. package/lib/collective/global-config.test.mjs +0 -220
  266. package/lib/collective/global-skills.test.mjs +0 -126
  267. package/lib/collective/presence.test.mjs +0 -95
  268. package/lib/collective/recall.test.mjs +0 -116
  269. package/lib/collective/vendor-skills.test.mjs +0 -306
  270. package/lib/comms/send-gate.test.mjs +0 -770
  271. package/lib/comms.test.mjs +0 -41
  272. package/lib/context/budget.test.mjs +0 -252
  273. package/lib/context/history-scope.test.mjs +0 -79
  274. package/lib/cost/ledger-row.test.mjs +0 -183
  275. package/lib/design/design-md.test.mjs +0 -318
  276. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  277. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  278. package/lib/design/fixtures/foundation.json +0 -133
  279. package/lib/design/refresh-gate.test.mjs +0 -144
  280. package/lib/design/write.test.mjs +0 -241
  281. package/lib/diagnostics/alerts.test.mjs +0 -318
  282. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  283. package/lib/diagnostics/counters.test.mjs +0 -206
  284. package/lib/diagnostics/events.test.mjs +0 -290
  285. package/lib/diagnostics/otel.test.mjs +0 -196
  286. package/lib/diagnostics/trace.test.mjs +0 -251
  287. package/lib/env-compat.test.mjs +0 -104
  288. package/lib/execution/disposition.test.mjs +0 -553
  289. package/lib/execution/drive.test.mjs +0 -270
  290. package/lib/execution/effects.test.mjs +0 -344
  291. package/lib/execution/intake.test.mjs +0 -389
  292. package/lib/execution/journal.test.mjs +0 -261
  293. package/lib/execution/match.test.mjs +0 -235
  294. package/lib/execution/pipeline.test.mjs +0 -392
  295. package/lib/execution/route.test.mjs +0 -186
  296. package/lib/execution/surface-policy.test.mjs +0 -162
  297. package/lib/fs-atomic.test.mjs +0 -72
  298. package/lib/fs-ownership.test.mjs +0 -158
  299. package/lib/goals/admission.test.mjs +0 -164
  300. package/lib/goals/classify.test.mjs +0 -167
  301. package/lib/goals/collaborate.test.mjs +0 -336
  302. package/lib/goals/gaps.test.mjs +0 -284
  303. package/lib/goals/loop.test.mjs +0 -845
  304. package/lib/hooks/bus.test.mjs +0 -387
  305. package/lib/identity/persona.test.mjs +0 -142
  306. package/lib/kpi-sensors.test.mjs +0 -278
  307. package/lib/kpi.test.mjs +0 -244
  308. package/lib/learning/config.test.mjs +0 -75
  309. package/lib/learning/counters.test.mjs +0 -69
  310. package/lib/learning/curator-consolidate.test.mjs +0 -238
  311. package/lib/learning/curator.test.mjs +0 -106
  312. package/lib/learning/reflect.test.mjs +0 -0
  313. package/lib/learning/session-index.test.mjs +0 -125
  314. package/lib/learning/skill-writer.test.mjs +0 -210
  315. package/lib/mandate/audit.test.mjs +0 -195
  316. package/lib/mandate/contract.test.mjs +0 -185
  317. package/lib/mandate/derive.test.mjs +0 -274
  318. package/lib/mandate/model.test.mjs +0 -164
  319. package/lib/mandate/refresh.test.mjs +0 -389
  320. package/lib/mcp/server.test.mjs +0 -426
  321. package/lib/model-router/auth-profiles.test.mjs +0 -580
  322. package/lib/model-router/catalog.test.mjs +0 -385
  323. package/lib/model-router/economics.test.mjs +0 -438
  324. package/lib/model-router/failover.test.mjs +0 -439
  325. package/lib/model-router/health.test.mjs +0 -338
  326. package/lib/model-router/integration-coverage.test.mjs +0 -831
  327. package/lib/model-router/integration.test.mjs +0 -564
  328. package/lib/model-router/ledger.test.mjs +0 -415
  329. package/lib/model-router/llm-task.test.mjs +0 -392
  330. package/lib/model-router/org-credentials.test.mjs +0 -265
  331. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  332. package/lib/model-router/reconcile.test.mjs +0 -316
  333. package/lib/model-router/repair.test.mjs +0 -180
  334. package/lib/model-router/spawn.test.mjs +0 -446
  335. package/lib/model-router/taxonomy.test.mjs +0 -410
  336. package/lib/model-router.test.mjs +0 -1207
  337. package/lib/org/activity.test.mjs +0 -134
  338. package/lib/org/approvals.test.mjs +0 -216
  339. package/lib/org/awareness.test.mjs +0 -159
  340. package/lib/org/board-mine-cache.test.mjs +0 -53
  341. package/lib/org/board.test.mjs +0 -187
  342. package/lib/org/bootstrap-context.test.mjs +0 -153
  343. package/lib/org/client.test.mjs +0 -1206
  344. package/lib/org/cohort-client.test.mjs +0 -126
  345. package/lib/org/cost-sync.test.mjs +0 -153
  346. package/lib/org/doctor.test.mjs +0 -346
  347. package/lib/org/engagement-ledger.test.mjs +0 -112
  348. package/lib/org/engagement.test.mjs +0 -739
  349. package/lib/org/handoff.test.mjs +0 -269
  350. package/lib/org/inbound/directedness.test.mjs +0 -668
  351. package/lib/org/inbound/facts.test.mjs +0 -471
  352. package/lib/org/inbound/hydrate.test.mjs +0 -908
  353. package/lib/org/inbound/index.test.mjs +0 -429
  354. package/lib/org/inbound/project.test.mjs +0 -287
  355. package/lib/org/integration-tools.test.mjs +0 -160
  356. package/lib/org/keys.test.mjs +0 -92
  357. package/lib/org/knowledge.test.mjs +0 -326
  358. package/lib/org/leases.test.mjs +0 -235
  359. package/lib/org/mesh-directives.test.mjs +0 -110
  360. package/lib/org/mesh-integration.test.mjs +0 -127
  361. package/lib/org/mesh.test.mjs +0 -400
  362. package/lib/org/messaging.test.mjs +0 -471
  363. package/lib/org/param-contract.test.mjs +0 -477
  364. package/lib/org/policy.test.mjs +0 -237
  365. package/lib/org/protocol.checksum.test.mjs +0 -90
  366. package/lib/org/protocol.test.mjs +0 -323
  367. package/lib/org/push.test.mjs +0 -792
  368. package/lib/org/registry.test.mjs +0 -100
  369. package/lib/org/resource-tools.test.mjs +0 -361
  370. package/lib/org/tool-access.test.mjs +0 -144
  371. package/lib/org/tool-surface-integration.test.mjs +0 -120
  372. package/lib/org/tool-surface.test.mjs +0 -1268
  373. package/lib/org/typing.test.mjs +0 -291
  374. package/lib/org/ui-parity.test.mjs +0 -560
  375. package/lib/org/verify.test.mjs +0 -194
  376. package/lib/org/work-ledger.test.mjs +0 -273
  377. package/lib/plan/adoption-e2e.test.mjs +0 -366
  378. package/lib/plan/budget-enforcement.test.mjs +0 -400
  379. package/lib/plan/compile.test.mjs +0 -382
  380. package/lib/plan/emit.test.mjs +0 -269
  381. package/lib/plan/explain.test.mjs +0 -188
  382. package/lib/prompts/parallelism.test.mjs +0 -177
  383. package/lib/rag/rag.test.mjs +0 -505
  384. package/lib/rate-guard.test.mjs +0 -272
  385. package/lib/reactive-gate.test.mjs +0 -57
  386. package/lib/render.test.mjs +0 -68
  387. package/lib/resource-governor.test.mjs +0 -488
  388. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  389. package/lib/scheduling/jitter.test.mjs +0 -140
  390. package/lib/secrets/broker.test.mjs +0 -280
  391. package/lib/secrets/providers.test.mjs +0 -274
  392. package/lib/security/audit-engine.test.mjs +0 -424
  393. package/lib/security/coerce-args.test.mjs +0 -281
  394. package/lib/security/dangerous-tools.test.mjs +0 -68
  395. package/lib/security/external-content.test.mjs +0 -84
  396. package/lib/security/redact.test.mjs +0 -441
  397. package/lib/security/secret-equal.test.mjs +0 -55
  398. package/lib/session/config.test.mjs +0 -92
  399. package/lib/session/feed-core.test.mjs +0 -198
  400. package/lib/session/first-run.test.mjs +0 -121
  401. package/lib/session/frontdoor.test.mjs +0 -205
  402. package/lib/session/handoffs.test.mjs +0 -183
  403. package/lib/session/identity.test.mjs +0 -180
  404. package/lib/session/inbox-claims.test.mjs +0 -286
  405. package/lib/session/launch-args.test.mjs +0 -157
  406. package/lib/session/liveness.test.mjs +0 -100
  407. package/lib/session/status-summary.test.mjs +0 -118
  408. package/lib/session-permissions.test.mjs +0 -120
  409. package/lib/setup/claude-probe.test.mjs +0 -187
  410. package/lib/setup/completeness.test.mjs +0 -110
  411. package/lib/setup/context-pack.test.mjs +0 -89
  412. package/lib/setup/enrich.test.mjs +0 -115
  413. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  414. package/lib/setup/integration.test.mjs +0 -162
  415. package/lib/setup/io.test.mjs +0 -77
  416. package/lib/setup/runner.test.mjs +0 -132
  417. package/lib/setup/sections/identity.test.mjs +0 -234
  418. package/lib/setup/sections/inventory.test.mjs +0 -198
  419. package/lib/setup/sections/learning.test.mjs +0 -81
  420. package/lib/setup/sections/mandate.test.mjs +0 -388
  421. package/lib/setup/sections/messaging.test.mjs +0 -127
  422. package/lib/setup/sections/model.test.mjs +0 -240
  423. package/lib/setup/sections/org.test.mjs +0 -346
  424. package/lib/setup/sections/orgmail.test.mjs +0 -118
  425. package/lib/setup/sections/recovery.test.mjs +0 -98
  426. package/lib/setup/sections/subagents.test.mjs +0 -429
  427. package/lib/setup/sections/verify.test.mjs +0 -175
  428. package/lib/setup/sot.test.mjs +0 -81
  429. package/lib/setup/state.test.mjs +0 -115
  430. package/lib/singleton.test.mjs +0 -151
  431. package/lib/subagents/cli.test.mjs +0 -389
  432. package/lib/subagents/client.test.mjs +0 -309
  433. package/lib/subagents/gap.test.mjs +0 -234
  434. package/lib/subagents/lock.test.mjs +0 -248
  435. package/lib/subagents/manifest.test.mjs +0 -175
  436. package/lib/subagents/refs.test.mjs +0 -204
  437. package/lib/subagents/resolve.test.mjs +0 -422
  438. package/lib/subagents/schema.test.mjs +0 -328
  439. package/lib/telemetry/alerts.test.mjs +0 -109
  440. package/lib/telemetry/collect.test.mjs +0 -1274
  441. package/lib/tool-definitions-integration.test.mjs +0 -83
  442. package/lib/tool-definitions.test.mjs +0 -437
  443. package/lib/upgrade/global-refresh.test.mjs +0 -65
  444. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  445. package/lib/upgrade/post-steps.test.mjs +0 -200
  446. package/lib/upgrade/verify.test.mjs +0 -164
  447. package/lib/util/fetch-timeout.test.mjs +0 -202
  448. package/lib/util/reconnect.test.mjs +0 -369
  449. package/lib/util/unhandled.test.mjs +0 -216
  450. package/lib/voice/outbound.test.mjs +0 -69
  451. package/lib/voice/session-rotation.test.mjs +0 -114
  452. package/lib/voice/stt.test.mjs +0 -226
  453. package/lib/voice/voice.test.mjs +0 -990
  454. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  455. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  456. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  457. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  458. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  459. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  460. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  461. package/scripts/ci/check.test.mjs +0 -194
  462. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  463. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  464. package/scripts/collective/hook-runner.test.mjs +0 -173
  465. package/scripts/cost/fleet-digest.test.mjs +0 -207
  466. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  467. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  468. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  469. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  470. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  471. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  472. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  473. package/scripts/daemon/assurance.test.mjs +0 -1791
  474. package/scripts/daemon/board-mirror.test.mjs +0 -165
  475. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  476. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  477. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  478. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  479. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  480. package/scripts/daemon/classifier.test.mjs +0 -266
  481. package/scripts/daemon/classify-kind.test.mjs +0 -40
  482. package/scripts/daemon/context-compiler.test.mjs +0 -406
  483. package/scripts/daemon/deliver.test.mjs +0 -564
  484. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  485. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  486. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  487. package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
  488. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  489. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  490. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  491. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  492. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  493. package/scripts/daemon/integration.test.mjs +0 -149
  494. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  495. package/scripts/daemon/lib/session-router.test.mjs +0 -554
  496. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  497. package/scripts/daemon/prompt-builder.test.mjs +0 -556
  498. package/scripts/daemon/responder-cost.test.mjs +0 -68
  499. package/scripts/daemon/responder-history.test.mjs +0 -221
  500. package/scripts/daemon/sdk-version.test.mjs +0 -31
  501. package/scripts/daemon/session-lock.test.mjs +0 -252
  502. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  503. package/scripts/daemon/typing-registry.test.mjs +0 -102
  504. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  505. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  506. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  507. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  508. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  509. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  510. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  511. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  512. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  513. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  514. package/scripts/poller-launchd/install.test.mjs +0 -243
  515. package/scripts/restore-from-backup.test.mjs +0 -181
  516. package/scripts/session/feed.test.mjs +0 -196
  517. package/scripts/session/supervisor-sh.test.mjs +0 -218
  518. package/scripts/session/supervisor.test.mjs +0 -482
  519. package/scripts/setup/configure-macos.test.mjs +0 -306
  520. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  521. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  522. package/scripts/setup/generate-capability.test.mjs +0 -134
  523. package/scripts/setup/init-agent.test.mjs +0 -370
  524. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  525. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  526. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -14,7 +14,16 @@ const AGENT_REPO_DIR = process.env.AGENT_DIR || join(new URL(".", import.meta.ur
14
14
  // --verify` reads `sdk_version` off the dashboard to tell the two apart. The
15
15
  // order of trust (sdk-version.mjs): the manifest of the copied tree, then
16
16
  // node_modules, then the package this file ships in.
17
- const DAEMON_SDK_VERSION = resolveRunningSdkVersion({ agentDir: AGENT_REPO_DIR, ownPackagePath: join(new URL(".", import.meta.url).pathname, "../../package.json") }).version;
17
+ //
18
+ // The dashboard carries the INSTALLED version and the verdict beside it,
19
+ // because the number on its own cannot be checked. A seat reported 2.18.0 to
20
+ // the org off its manifest while node_modules already held 2.18.1: both
21
+ // numbers true, the disagreement the only interesting fact, and nothing on the
22
+ // seat or in the org said it. `sdk_version_agrees` is a real tri-state —
23
+ // `unknown` when the running version was INFERRED from the installed one, so
24
+ // equality by fallback can never be read as a clean hop.
25
+ const SDK = resolveRunningSdkVersion({ agentDir: AGENT_REPO_DIR, ownPackagePath: join(new URL(".", import.meta.url).pathname, "../../package.json") });
26
+ const DAEMON_SDK_VERSION = SDK.version;
18
27
 
19
28
  let stats = {
20
29
  daemon_start: new Date().toISOString(),
@@ -58,6 +67,10 @@ export function writeHealthDashboard() {
58
67
  uptime_s: Math.floor((Date.now() - new Date(stats.daemon_start).getTime()) / 1000),
59
68
  pid: process.pid,
60
69
  sdk_version: DAEMON_SDK_VERSION,
70
+ sdk_version_source: SDK.runningSource,
71
+ sdk_version_installed: SDK.installed,
72
+ sdk_version_agrees: SDK.agrees === null ? "unknown" : String(SDK.agrees),
73
+ sdk_version_drift: SDK.drift,
61
74
  ...stats,
62
75
  dispatcher,
63
76
  };
@@ -39,6 +39,17 @@ process.env.MAESTRO_DAEMON = "1";
39
39
  // ---------------------------------------------------------------------------
40
40
  // 0. Environment — BEFORE anything reads a credential
41
41
  // ---------------------------------------------------------------------------
42
+ // But first: the BOOT MARKER. The wrapper appends this process's stdout to
43
+ // logs/daemon/daemon-<start day>.log, which carries no per-line timestamps and
44
+ // is SHARED by every incarnation that starts on the same day — a crash at
45
+ // 09:00 and the healthy respawn at 09:01 write to the same file. This line is
46
+ // the first thing this incarnation prints, tagged with its pid, so a reader
47
+ // can split the file into "before this process" and "since this process":
48
+ // scripts/local-triggers/autoupdate.sh's health gate scans for fatal lines
49
+ // only AFTER `[DAEMON] boot pid=<its pid>`; without it a one-off crash earlier
50
+ // the same day read as "fatal since restart" every hour until midnight. The
51
+ // format is pinned by autoupdate.test.mjs — change both or neither.
52
+ console.log(`[DAEMON] boot pid=${process.pid} at=${new Date().toISOString()}`);
42
53
  // The core daemon (agent-daemon.mjs, step 4) loads .env itself, but the org
43
54
  // mesh connects at step 3b' — EARLIER in this file. Under launchd the process
44
55
  // environment is bare, so the mesh was reading COHORT_API_KEY before anything
@@ -14,6 +14,11 @@ import { recall as orgRecall } from "../../lib/org/knowledge.mjs";
14
14
  import { outcomeSourceShareable } from "./session-outcomes.mjs";
15
15
  import { withParallelism } from "../../lib/prompts/parallelism.mjs";
16
16
  import { isPrivateConversation, historyDirNames } from "../../lib/context/history-scope.mjs";
17
+ // A human's roll-call to the whole room: the SAME reply-shaping block the
18
+ // quick-reply responder injects, so a roll-call that fell through to a session
19
+ // (quick reply failed transiently) is answered in the same shape.
20
+ import { isRollCallItem, collectiveInstructions } from "../../lib/org/inbound/broadcast.mjs";
21
+ import { itemCollectiveIntent } from "../../lib/org/inbound/collective.mjs";
17
22
 
18
23
  const AGENT_REPO_DIR = process.env.AGENT_DIR || join(new URL(".", import.meta.url).pathname, "../..");
19
24
 
@@ -1068,6 +1073,25 @@ export async function buildPrompt(item, classResult, options = {}) {
1068
1073
  parts.push("");
1069
1074
  }
1070
1075
 
1076
+ // 1d. COLLECTIVE shaping — a human addressed the whole room and every seat
1077
+ // answers for itself. Placed with the contact blocks above because it is the
1078
+ // same kind of instruction: what may and may not be SAID, before any work.
1079
+ //
1080
+ // The intent (roll-call / work / ambient) comes from `collective.mjs`, the
1081
+ // twin of hq's `work/collective.ts`, so the same message gets the same block
1082
+ // whether the seat is driven from here or from hq. A WORK broadcast is told
1083
+ // its board row already exists — otherwise the session's first instinct is to
1084
+ // open a second one.
1085
+ if (type === "inbox" && item && isRollCallItem(item)) {
1086
+ parts.push(
1087
+ collectiveInstructions({
1088
+ intent: itemCollectiveIntent(item, isRollCallItem),
1089
+ respondents: item.respondents,
1090
+ }),
1091
+ );
1092
+ parts.push("");
1093
+ }
1094
+
1071
1095
  // 2. Session context
1072
1096
  parts.push(`Date: ${date}`);
1073
1097
  parts.push(`Classification: priority=${classResult.priority}, action=${action}, category=${classResult.category || "unclassified"}`);
@@ -72,11 +72,21 @@ import { generateAck } from "./assurance.mjs";
72
72
  // This path never renders the persona block, which is why the doctrine is a
73
73
  // separately-exported constant rather than part of voiceRules().
74
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";
75
83
 
76
84
  const AGENT_REPO_DIR = process.env.AGENT_DIR || join(new URL(".", import.meta.url).pathname, "../..");
77
85
  const SONNET_MODEL = "claude-sonnet-4-6";
78
86
  // Resolve claude against the agent's PATH (not launchd's bare env).
79
- 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";
80
90
  import { isPrivateConversation, interactionWriteDirNames } from "../../lib/context/history-scope.mjs";
81
91
  const CLAUDE_BIN = resolveClaudeBin();
82
92
  const CLAUDE_CLI_TIMEOUT_MS = 60_000;
@@ -144,6 +154,82 @@ export function unmeasuredReasonFor({ parsed, timedOut, exitCode, parseError, ti
144
154
  return "no-json-envelope";
145
155
  }
146
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
+
147
233
  /**
148
234
  * Append a cost-ledger row for a responder session (source "responder").
149
235
  *
@@ -176,40 +262,12 @@ function recordResponderCost({ json, model, durationMs, exitCode, unmeasuredReas
176
262
  const trackerPath = join(AGENT_REPO_DIR, "scripts/cost/track-claude-usage.mjs");
177
263
  if (!existsSync(trackerPath)) return;
178
264
 
179
- const usage = json && typeof json.usage === "object" && json.usage ? json.usage : null;
180
- const inputTokens = usage ? Number(usage.input_tokens) : NaN;
181
- const outputTokens = usage ? Number(usage.output_tokens) : NaN;
182
- const measured = Number.isFinite(inputTokens) && Number.isFinite(outputTokens);
183
-
184
- // Map the CLI-resolved model id onto the tracker's coarse pricing class.
185
- const resolved = json && typeof json.model === "string" ? json.model : model || "";
186
- const modelClass = /opus/i.test(resolved) ? "opus" : /haiku/i.test(resolved) ? "haiku" : "sonnet";
187
-
188
- const args = [
189
- trackerPath, "record",
190
- "--cadence", "responder",
191
- "--source", "responder",
192
- "--model", modelClass,
193
- "--duration-ms", String(durationMs),
194
- "--exit", String(exitCode),
195
- ];
196
-
197
- if (measured) {
198
- args.push("--input-tokens", String(inputTokens));
199
- args.push("--output-tokens", String(outputTokens));
200
- const cacheRead = Number(usage.cache_read_input_tokens);
201
- if (Number.isFinite(cacheRead)) args.push("--cache-read-tokens", String(cacheRead));
202
- const cacheWrite = Number(usage.cache_creation_input_tokens);
203
- if (Number.isFinite(cacheWrite)) args.push("--cache-creation-tokens", String(cacheWrite));
204
- const totalCost = Number(json.total_cost_usd);
205
- if (Number.isFinite(totalCost) && totalCost >= 0) args.push("--total-cost-usd", String(totalCost));
206
- } else {
207
- const reason = unmeasuredReason || (json ? "no-usage-field" : "no-json-envelope");
208
- args.push("--tokens-unknown", reason);
209
- 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`);
210
268
  }
211
269
 
212
- spawn(process.execPath, args, {
270
+ spawn(process.execPath, [trackerPath, ...args], {
213
271
  stdio: "ignore",
214
272
  env: { ...process.env, AGENT_ROOT: AGENT_REPO_DIR, AGENT_DIR: AGENT_REPO_DIR },
215
273
  }).unref();
@@ -234,37 +292,56 @@ function recordResponderCost({ json, model, durationMs, exitCode, unmeasuredReas
234
292
  // the same UUID across spawns is the resume mechanism).
235
293
  //
236
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
+
237
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 = {}) {
238
331
  const { sessionId = null, router = null, routingKey = null } = opts;
239
332
  const startedAt = Date.now();
240
333
 
241
334
  return new Promise((resolvePromise, rejectPromise) => {
242
- const args = [
243
- "--print",
244
- ...sessionPermissionArgs({ source: "responder" }),
245
- ...daemonClaudeArgs(undefined, { source: "responder" }),
246
- "--model", model,
247
- "--append-system-prompt", systemPrompt,
248
- // --output-format json is only valid in combination with --print (per b1
249
- // report). We always pass --print above, so this is safe.
250
- //
251
- // This used to be requested ONLY when a sessionId was supplied, which
252
- // meant every non-routed reply ran with no usage envelope and therefore
253
- // no way to price it. Every reply this daemon sends costs real money;
254
- // asking for the envelope unconditionally is what makes it measurable.
255
- // The parse below already has a raw-text fallback for older CLIs.
256
- "--output-format", "json",
257
- ];
258
- if (sessionId) {
259
- 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;
260
339
  }
340
+ const args = spawnSpec.argv;
261
341
 
262
- const proc = spawn(CLAUDE_BIN, args, {
342
+ const proc = spawn(spawnSpec.bin, args, {
263
343
  stdio: ["pipe", "pipe", "pipe"],
264
- // Force claude CLI onto keychain OAuth (Max subscription); strip any
265
- // stale ANTHROPIC_API_KEY/AUTH_TOKEN inherited from the daemon env.
266
- // Augment PATH so the subsession finds homebrew/nvm tools.
267
- env: { ...process.env, PATH: augmentedPath(), ANTHROPIC_API_KEY: "", ANTHROPIC_AUTH_TOKEN: "" },
344
+ env: spawnSpec.env,
268
345
  });
269
346
 
270
347
  let stdout = "";
@@ -327,11 +404,13 @@ function runClaudeCLI(systemPrompt, userPrompt, model, opts = {}) {
327
404
  parsed = trimmed ? JSON.parse(trimmed) : null;
328
405
  if (!parsed) parseError = new Error("no stdout");
329
406
  } catch (err) {
330
- // Legacy fallback (rollout-safety): older CLIs or unexpected output
331
- // shapes shouldn't crash the daemon. Log a warning, surface the raw
332
- // 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.
333
412
  parseError = err;
334
- 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)`);
335
414
  }
336
415
 
337
416
  // Every reply this daemon sends is a paid Claude session — INCLUDING the
@@ -358,11 +437,7 @@ function runClaudeCLI(systemPrompt, userPrompt, model, opts = {}) {
358
437
  return;
359
438
  }
360
439
 
361
- if (parsed) {
362
- resolvePromise({ text: parsed.result ?? "", jsonResult: parsed, exitCode: code });
363
- } else {
364
- resolvePromise({ text: trimmed, jsonResult: null, exitCode: code });
365
- }
440
+ resolvePromise(cliResultFor({ parsed, rawStdout: trimmed, exitCode: code }));
366
441
  });
367
442
 
368
443
  try {
@@ -659,6 +734,7 @@ export async function loadConversationHistory(item, deps = {}) {
659
734
  // the real generator, so behaviour is unchanged. The public senders call
660
735
  // generateResponse() through this slot.
661
736
  let _generateResponse;
737
+
662
738
  /**
663
739
  * Test seam: replace the response generator. Pass a function
664
740
  * `(item, classResult) => Promise<string>` to force reply text; pass nothing to
@@ -683,11 +759,99 @@ export function setGenerateResponseForTests(fn) {
683
759
  * and the flag is deliberately not kept "just in case", because keeping it is
684
760
  * how it comes back.
685
761
  */
686
- 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 = {}) {
687
850
  const preamble = loadPreamble();
688
851
  const profile = loadUserProfile(item.sender);
689
852
  // The classifier's recommended model (Opus for CEO, Sonnet for routine).
690
853
  const model = classResult.model === "opus" ? "claude-opus-4-6" : SONNET_MODEL;
854
+ const rollCall = isRollCallItem(item);
691
855
 
692
856
  const systemPrompt = `${preamble}
693
857
 
@@ -698,20 +862,20 @@ If it's informational, acknowledge appropriately.
698
862
  Keep responses focused — 1-4 sentences for simple items, up to a short paragraph for more nuanced ones.
699
863
  Match the sender's tone and urgency level.
700
864
  ${profile ? `\nSender profile:\n${profile}` : ""}
701
-
865
+ ${rollCall ? `\n${collectiveInstructions({ intent: itemCollectiveIntent(item, isRollCallItem), respondents: item.respondents })}\n` : ""}
702
866
  ${MESSAGE_CRAFT}`;
703
867
 
704
- 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);
705
877
 
706
- const userContent = [
707
- `From: ${item.sender} (${item.sender_privilege || "unknown"})`,
708
- `Via: ${item.service} / ${item.channel}`,
709
- item.subject ? `Subject: ${item.subject}` : null,
710
- conversationHistory ? `\nRecent conversation history:\n${conversationHistory}` : null,
711
- `\nCurrent message:\n${item.content || "(empty)"}`,
712
- item.thread_context ? `\nThread context:\n${item.thread_context}` : null,
713
- `\nClassification: ${classResult.summary}`,
714
- ].filter(Boolean).join("\n");
878
+ const userContent = buildQuickReplyUserContent({ item, classResult, conversationHistory, currentWork, rollCall });
715
879
 
716
880
  // (b2) Session-router decision. Compute the routing key from a daemon→router
717
881
  // adapter view of the item. If the item can't be keyed (unknown service,
@@ -756,7 +920,7 @@ ${MESSAGE_CRAFT}`;
756
920
  // and released in a `finally`: a throw anywhere in between must not leave the
757
921
  // room's key claimed forever, which would silently disable continuity for it.
758
922
  try {
759
- const cliResult = await runClaudeCLI(systemPrompt, userContent, model, {
923
+ const cliResult = await (deps.runCLI || runClaudeCLI)(systemPrompt, userContent, model, {
760
924
  sessionId,
761
925
  router: key ? router : null,
762
926
  routingKey: key,
@@ -764,7 +928,10 @@ ${MESSAGE_CRAFT}`;
764
928
 
765
929
  const text = (cliResult.text || "").trim();
766
930
  if (!text) {
767
- throw new Error("claude CLI returned empty result text in generateResponse");
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)` : ""}`);
768
935
  }
769
936
 
770
937
  // (b3) On success, touch the registry with the CLI-resolved session_id so
@@ -1,22 +1,56 @@
1
1
  /**
2
2
  * scripts/daemon/sdk-version.mjs — which SDK version is the code THIS daemon
3
- * process is running?
3
+ * process is running, which one is INSTALLED beside it, and do they agree?
4
4
  *
5
- * The daemon runs from the agent dir's COPIED scripts/daemon, not from
6
- * node_modules. autoupdate installs the next version into node_modules first
7
- * and merges the files a minute or two later; a daemon that (re)starts inside
8
- * that window would report the node_modules version while executing the old
9
- * copies, and `maestro upgrade --verify` would go green on a lie. So the
10
- * order of trust is the version of the COPIED tree first:
5
+ * The daemon runs from the agent dir's COPIED scripts/daemon (and ../../lib),
6
+ * not from node_modules. `autoupdate.sh` installs the next version into
7
+ * node_modules FIRST and merges the files a minute or two later; a daemon that
8
+ * (re)starts inside that window executes the old copies while node_modules
9
+ * already holds the new package. So the RUNNING version is the version of the
10
+ * COPIED tree, and the order of trust for it is:
11
11
  *
12
12
  * 1. .maestro/shipped-manifest.json `sdkVersion` — written by the upgrade
13
13
  * that copied the files (2.12+), i.e. the version of scripts/daemon/*;
14
14
  * 2. node_modules/@cohortapp/agent-sdk/package.json — a pre-2.12 manifest
15
- * has no sdkVersion; the window above is then the only inaccuracy left;
15
+ * has no sdkVersion, so this is a fallback and NOT an observation of the
16
+ * copied tree (see `runningIsInferred` below);
16
17
  * 3. the package this file itself ships in (a framework checkout).
17
18
  *
19
+ * ── WHY IT NO LONGER RETURNS ONE STRING ──────────────────────────────────────
20
+ * It used to answer with `{version, source}` and nothing else, and every caller
21
+ * took `.version`. That is a true answer to "what am I executing" and a silent
22
+ * one about everything around it: this seat reported 2.18.0 to the org — read
23
+ * off a manifest written by the last `maestro upgrade` — while node_modules had
24
+ * already been moved to 2.18.1. Neither number was wrong. What was missing was
25
+ * that they DISAGREED, which is the only fact an operator watching a rollout
26
+ * actually wants, and the one the shape could not express.
27
+ *
28
+ * It matters twice over now: `autoupdate.sh`'s health gate compares the version
29
+ * a beat REPORTED against the version it INSTALLED, and refuses to call the hop
30
+ * healthy when they differ. A resolver that collapses the two teaches that gate
31
+ * to compare a number with itself.
32
+ *
33
+ * So the answer carries both, plus the verdict:
34
+ *
35
+ * running what the copied tree is, i.e. what is executing
36
+ * runningSource "manifest" | "node_modules" | "package" | "none"
37
+ * runningIsInferred true when `running` did NOT come from the manifest — the
38
+ * copied tree's version was not observed, only assumed
39
+ * installed node_modules/@cohortapp/agent-sdk, independently read
40
+ * agrees true | false | null (null = one of them is unknown, or
41
+ * `running` was inferred FROM `installed`, in which case
42
+ * equality is an artefact of the fallback and proves
43
+ * nothing — it must never read as agreement)
44
+ * drift a one-line human reason when `agrees === false`, else ""
45
+ *
46
+ * `version` and `source` stay on the object, unchanged, so existing callers
47
+ * keep working; `version` is `running`.
48
+ *
18
49
  * Read once at daemon start, never again (the dashboard must say what the
19
- * process IS, not what node_modules became meanwhile).
50
+ * process IS, not what node_modules became meanwhile). `installed` is read in
51
+ * the same breath for the same reason: a later read would compare this
52
+ * process's code against a node_modules that has moved on since, which is a
53
+ * different question and not this module's.
20
54
  *
21
55
  * @module scripts/daemon/sdk-version
22
56
  */
@@ -31,21 +65,69 @@ export const SDK_PACKAGE = "@cohortapp/agent-sdk";
31
65
  /**
32
66
  * Pure over an injected reader. `readJson(path)` returns the parsed file or
33
67
  * throws / returns null; every source is fail-open.
68
+ *
34
69
  * @param {{agentDir:string, ownPackagePath?:string, readJson?:(p:string)=>any}} o
35
- * @returns {{version:string, source:"manifest"|"node_modules"|"package"|"none"}}
70
+ * @returns {{version:string, source:"manifest"|"node_modules"|"package"|"none",
71
+ * running:string, runningSource:"manifest"|"node_modules"|"package"|"none",
72
+ * runningIsInferred:boolean, installed:string,
73
+ * agrees:boolean|null, drift:string}}
36
74
  */
37
75
  export function resolveRunningSdkVersion(o = {}) {
38
76
  const readJson = o.readJson || ((p) => JSON.parse(readFileSync(p, "utf8")));
39
77
  const tryRead = (p) => { try { return readJson(p); } catch { return null; /* absent/corrupt → next source */ } };
40
- const manifest = tryRead(join(o.agentDir, ".maestro", "shipped-manifest.json"));
41
- if (manifest && typeof manifest.sdkVersion === "string" && manifest.sdkVersion) return { version: manifest.sdkVersion, source: "manifest" };
78
+
79
+ // INSTALLED — node_modules, read on its own terms and never skipped, because
80
+ // it is half of the comparison even when the manifest answers first.
42
81
  const dep = tryRead(join(o.agentDir, "node_modules", ...SDK_PACKAGE.split("/"), "package.json"));
43
- if (dep && dep.name === SDK_PACKAGE && typeof dep.version === "string") return { version: dep.version, source: "node_modules" };
44
- if (o.ownPackagePath) {
82
+ const installed = dep && dep.name === SDK_PACKAGE && typeof dep.version === "string" ? dep.version : "";
83
+
84
+ // RUNNING — the copied tree, in the documented order of trust.
85
+ let running = "";
86
+ let runningSource = "none";
87
+ const manifest = tryRead(join(o.agentDir, ".maestro", "shipped-manifest.json"));
88
+ if (manifest && typeof manifest.sdkVersion === "string" && manifest.sdkVersion) {
89
+ running = manifest.sdkVersion;
90
+ runningSource = "manifest";
91
+ } else if (installed) {
92
+ running = installed;
93
+ runningSource = "node_modules";
94
+ } else if (o.ownPackagePath) {
45
95
  const own = tryRead(o.ownPackagePath);
46
- if (own && own.name === SDK_PACKAGE && typeof own.version === "string") return { version: own.version, source: "package" };
96
+ if (own && own.name === SDK_PACKAGE && typeof own.version === "string") {
97
+ running = own.version;
98
+ runningSource = "package";
99
+ }
47
100
  }
48
- return { version: "", source: "none" };
101
+
102
+ return {
103
+ version: running,
104
+ source: runningSource,
105
+ running,
106
+ runningSource,
107
+ runningIsInferred: runningSource !== "manifest",
108
+ installed,
109
+ ...verdict(running, runningSource, installed),
110
+ };
111
+ }
112
+
113
+ /**
114
+ * The comparison, separated so it is testable on its own and so the three
115
+ * answers are exhaustive in one place.
116
+ *
117
+ * `null` is not a weaker `true`. A run whose `running` was INFERRED from
118
+ * `installed` has not compared anything — the two strings are equal because one
119
+ * was copied from the other — and reporting that as agreement is precisely the
120
+ * substitution ("it is running because it is installed") that let a seat stay
121
+ * six days behind an hourly "up to date".
122
+ */
123
+ function verdict(running, runningSource, installed) {
124
+ if (!running || !installed) return { agrees: null, drift: "" };
125
+ if (runningSource !== "manifest") return { agrees: null, drift: "" };
126
+ if (running === installed) return { agrees: true, drift: "" };
127
+ return {
128
+ agrees: false,
129
+ drift: `running ${running} (copied tree) but ${installed} is installed — an upgrade installed the package and has not merged the files yet, or a merge failed`,
130
+ };
49
131
  }
50
132
 
51
133
  export default { resolveRunningSdkVersion, SDK_PACKAGE };