@cohortapp/agent-sdk 2.17.0 → 2.18.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (531) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +18 -5
  3. package/README.md +1 -0
  4. package/bin/maestro.mjs +62 -0
  5. package/docs/guides/billing-console-keys.md +60 -0
  6. package/docs/guides/front-door-session.md +54 -9
  7. package/docs/guides/mac-mini.md +20 -25
  8. package/docs/guides/setup-wizard.md +1 -1
  9. package/docs/runbooks/fleet-rollout.md +156 -0
  10. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  11. package/lib/action-executor.js +19 -3
  12. package/lib/budget-guard.mjs +279 -3
  13. package/lib/channels/base-adapter.mjs +3 -1
  14. package/lib/channels/contract.mjs +2 -1
  15. package/lib/channels/inbox-item.mjs +8 -0
  16. package/lib/claude-bin.mjs +5 -6
  17. package/lib/cli/doctor-checks.mjs +141 -10
  18. package/lib/cli/global-setup-extras.mjs +5 -1
  19. package/lib/cli/inbox.mjs +100 -15
  20. package/lib/cli/seat-auth.mjs +463 -0
  21. package/lib/cli/session.mjs +80 -12
  22. package/lib/collective/capture-slots.mjs +234 -0
  23. package/lib/collective/capture.mjs +8 -6
  24. package/lib/collective/config.mjs +2 -0
  25. package/lib/collective/global-config.mjs +63 -1
  26. package/lib/collective/loop-guard.mjs +155 -0
  27. package/lib/collective/presence.mjs +142 -5
  28. package/lib/comms/send-gate.mjs +559 -1
  29. package/lib/diagnostics/alerts.mjs +49 -0
  30. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  31. package/lib/engine/agents/definitions.mjs +343 -0
  32. package/lib/engine/agents/persist.mjs +275 -0
  33. package/lib/engine/agents/runtime.mjs +748 -0
  34. package/lib/engine/agents/usage.mjs +95 -0
  35. package/lib/engine/auth-status.mjs +139 -0
  36. package/lib/engine/budget.mjs +194 -0
  37. package/lib/engine/cli.mjs +1204 -0
  38. package/lib/engine/commands/index.mjs +269 -0
  39. package/lib/engine/context/budget.mjs +219 -0
  40. package/lib/engine/context/cache.mjs +125 -0
  41. package/lib/engine/context/child-env.mjs +215 -0
  42. package/lib/engine/context/compaction.mjs +342 -0
  43. package/lib/engine/context/images.mjs +90 -0
  44. package/lib/engine/context/instructions.mjs +327 -0
  45. package/lib/engine/context/lazy-instructions.mjs +169 -0
  46. package/lib/engine/context/manager.mjs +182 -0
  47. package/lib/engine/context/real-path.mjs +91 -0
  48. package/lib/engine/context/secret-values.mjs +163 -0
  49. package/lib/engine/context/settings.mjs +274 -0
  50. package/lib/engine/context/stream-input.mjs +159 -0
  51. package/lib/engine/guard.mjs +152 -0
  52. package/lib/engine/hooks.mjs +713 -0
  53. package/lib/engine/loop.mjs +560 -0
  54. package/lib/engine/mcp/client.mjs +254 -0
  55. package/lib/engine/mcp/config.mjs +301 -0
  56. package/lib/engine/mcp/http.mjs +201 -0
  57. package/lib/engine/mcp/index.mjs +146 -0
  58. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  59. package/lib/engine/mcp/naming.mjs +66 -0
  60. package/lib/engine/mcp/resources.mjs +89 -0
  61. package/lib/engine/mcp/results.mjs +133 -0
  62. package/lib/engine/mcp/stdio.mjs +137 -0
  63. package/lib/engine/mcp/supervisor.mjs +116 -0
  64. package/lib/engine/messages.mjs +104 -0
  65. package/lib/engine/output/json.mjs +164 -0
  66. package/lib/engine/output/stream-json.mjs +266 -0
  67. package/lib/engine/permissions.mjs +845 -0
  68. package/lib/engine/process-identity.mjs +164 -0
  69. package/lib/engine/process-tree.mjs +551 -0
  70. package/lib/engine/prompt.mjs +60 -0
  71. package/lib/engine/session/store.mjs +299 -0
  72. package/lib/engine/session-runtime/args.mjs +97 -0
  73. package/lib/engine/session-runtime/host.mjs +143 -0
  74. package/lib/engine/session-runtime/inbox.mjs +122 -0
  75. package/lib/engine/session-runtime/notifications.mjs +129 -0
  76. package/lib/engine/session-runtime/registry.mjs +328 -0
  77. package/lib/engine/session-runtime/runner.mjs +344 -0
  78. package/lib/engine/session-runtime/socket.mjs +212 -0
  79. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  80. package/lib/engine/skills/index.mjs +321 -0
  81. package/lib/engine/tools/bash-background.mjs +533 -0
  82. package/lib/engine/tools/bash.mjs +216 -0
  83. package/lib/engine/tools/edit.mjs +97 -0
  84. package/lib/engine/tools/glob.mjs +81 -0
  85. package/lib/engine/tools/grep.mjs +224 -0
  86. package/lib/engine/tools/index.mjs +84 -0
  87. package/lib/engine/tools/list-agents.mjs +32 -0
  88. package/lib/engine/tools/ls.mjs +127 -0
  89. package/lib/engine/tools/monitor.mjs +82 -0
  90. package/lib/engine/tools/notebook-edit.mjs +218 -0
  91. package/lib/engine/tools/read.mjs +103 -0
  92. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  93. package/lib/engine/tools/schema.mjs +144 -0
  94. package/lib/engine/tools/send-message.mjs +77 -0
  95. package/lib/engine/tools/session.mjs +70 -0
  96. package/lib/engine/tools/todo.mjs +144 -0
  97. package/lib/engine/tools/toolsearch.mjs +217 -0
  98. package/lib/engine/tools/walk.mjs +193 -0
  99. package/lib/engine/tools/web-switch.mjs +31 -0
  100. package/lib/engine/tools/webfetch-html.mjs +387 -0
  101. package/lib/engine/tools/webfetch-net.mjs +340 -0
  102. package/lib/engine/tools/webfetch.mjs +198 -0
  103. package/lib/engine/tools/websearch.mjs +91 -0
  104. package/lib/engine/tools/workflow.mjs +95 -0
  105. package/lib/engine/tools/write.mjs +76 -0
  106. package/lib/engine/tui/line-editor.mjs +137 -0
  107. package/lib/engine/tui/render.mjs +86 -0
  108. package/lib/engine/tui/tui.mjs +274 -0
  109. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  110. package/lib/engine/wire/effort.mjs +36 -0
  111. package/lib/engine/wire/errors.mjs +496 -0
  112. package/lib/engine/wire/http.mjs +441 -0
  113. package/lib/engine/wire/index.mjs +76 -0
  114. package/lib/engine/wire/openai-chat.mjs +332 -0
  115. package/lib/engine/wire/prompt-cache.mjs +79 -0
  116. package/lib/engine/wire/search.mjs +140 -0
  117. package/lib/engine/wire/sse.mjs +114 -0
  118. package/lib/engine/wire/stall.mjs +349 -0
  119. package/lib/engine/wire/token-provider.mjs +175 -0
  120. package/lib/engine/wire/usage.mjs +192 -0
  121. package/lib/engine/workflow/host.mjs +524 -0
  122. package/lib/engine/workflow/journal.mjs +188 -0
  123. package/lib/engine/workflow/json-schema.mjs +171 -0
  124. package/lib/engine/workflow/meta.mjs +329 -0
  125. package/lib/engine/workflow/notifications.mjs +52 -0
  126. package/lib/engine/workflow/runtime.mjs +447 -0
  127. package/lib/engine/workflow/sandbox.mjs +534 -0
  128. package/lib/engine/workflow/worker.mjs +141 -0
  129. package/lib/engine/workflow/worktree.mjs +74 -0
  130. package/lib/execution/disposition.mjs +1 -1
  131. package/lib/execution/intake.mjs +10 -0
  132. package/lib/execution/surface-policy.mjs +15 -0
  133. package/lib/learning/curator.mjs +8 -6
  134. package/lib/learning/reflect.mjs +8 -6
  135. package/lib/model-router/catalog/cohort.yaml +137 -0
  136. package/lib/model-router/catalog.mjs +118 -1
  137. package/lib/model-router/failover.mjs +67 -16
  138. package/lib/model-router/llm-task.mjs +39 -3
  139. package/lib/model-router/resolve.mjs +89 -3
  140. package/lib/model-router/spawn.mjs +46 -47
  141. package/lib/model-router/taxonomy.mjs +126 -4
  142. package/lib/org/cost-sync.mjs +141 -11
  143. package/lib/org/inbound/broadcast.mjs +289 -0
  144. package/lib/org/inbound/collective.mjs +375 -0
  145. package/lib/org/inbound/directedness.mjs +96 -8
  146. package/lib/org/inbound/facts.mjs +78 -2
  147. package/lib/org/inbound/project.mjs +22 -0
  148. package/lib/org/inbound/surfaces.mjs +14 -0
  149. package/lib/org/llm-token.mjs +879 -0
  150. package/lib/org/mesh.mjs +61 -0
  151. package/lib/org/messaging.mjs +3 -1
  152. package/lib/org/protocol.checksum +1 -1
  153. package/lib/org/protocol.mjs +15 -0
  154. package/lib/org/quota.mjs +520 -0
  155. package/lib/org/tool-surface.mjs +104 -16
  156. package/lib/org/ui-parity.mjs +16 -1
  157. package/lib/org/work-ledger.mjs +37 -6
  158. package/lib/rate-guard.mjs +114 -1
  159. package/lib/resource-governor.mjs +41 -6
  160. package/lib/runtime/adapter.mjs +833 -0
  161. package/lib/runtime/child-env.mjs +191 -0
  162. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  163. package/lib/runtime/seat-engine.mjs +162 -0
  164. package/lib/session/ask-ledger.mjs +271 -0
  165. package/lib/session/current-work.mjs +676 -0
  166. package/lib/session/feed-core.mjs +40 -3
  167. package/lib/session/launch-args.mjs +56 -4
  168. package/lib/session/status-summary.mjs +26 -9
  169. package/lib/session/upgrade-notice.mjs +42 -0
  170. package/lib/setup/claude-probe.mjs +117 -13
  171. package/lib/setup/enrich.mjs +13 -10
  172. package/lib/setup/sections/model.mjs +39 -13
  173. package/lib/telemetry/collect.mjs +229 -11
  174. package/lib/upgrade/ignored-drift.mjs +105 -0
  175. package/lib/voice/post-call-brief.mjs +30 -17
  176. package/package.json +13 -3
  177. package/plugins/maestro-skills/skills/board-work.md +5 -0
  178. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  179. package/plugins/maestro-skills/skills/main-session.md +18 -7
  180. package/scaffold/config/collective.yaml +7 -0
  181. package/scripts/ci/check-durable-write-seam.mjs +3 -1
  182. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  183. package/scripts/ci/run-tests.mjs +47 -19
  184. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  185. package/scripts/collective/hook-runner.mjs +142 -19
  186. package/scripts/continuous-monitor.sh +13 -0
  187. package/scripts/cost/track-claude-usage.mjs +15 -0
  188. package/scripts/daemon/agent-daemon.mjs +408 -20
  189. package/scripts/daemon/assurance.mjs +48 -12
  190. package/scripts/daemon/cadence-consumer.mjs +218 -68
  191. package/scripts/daemon/cadence-handlers.mjs +73 -4
  192. package/scripts/daemon/classifier.mjs +75 -26
  193. package/scripts/daemon/context-compiler.mjs +51 -37
  194. package/scripts/daemon/deliver.mjs +30 -1
  195. package/scripts/daemon/dispatcher.mjs +595 -149
  196. package/scripts/daemon/health.mjs +14 -1
  197. package/scripts/daemon/maestro-daemon.mjs +11 -0
  198. package/scripts/daemon/prompt-builder.mjs +24 -0
  199. package/scripts/daemon/responder.mjs +246 -79
  200. package/scripts/daemon/sdk-version.mjs +98 -16
  201. package/scripts/eval/probe-gateway.mjs +635 -0
  202. package/scripts/eval/replay/extract.mjs +270 -0
  203. package/scripts/eval/replay/grade.mjs +260 -0
  204. package/scripts/eval/replay/lib/config.mjs +50 -0
  205. package/scripts/eval/replay/lib/effects.mjs +65 -0
  206. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  207. package/scripts/eval/replay/lib/judge.mjs +72 -0
  208. package/scripts/eval/replay/lib/redact.mjs +136 -0
  209. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  210. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  211. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  212. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  213. package/scripts/eval/replay/report.mjs +185 -0
  214. package/scripts/eval/replay/run.mjs +404 -0
  215. package/scripts/fleet/rollout.mjs +1151 -0
  216. package/scripts/hooks/pre-send-audit.sh +36 -245
  217. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  218. package/scripts/hooks/validate-state-yaml.sh +190 -0
  219. package/scripts/huddle/huddle-llm.mjs +361 -0
  220. package/scripts/huddle/huddle-server.mjs +46 -121
  221. package/scripts/local-triggers/autoupdate.sh +465 -81
  222. package/scripts/local-triggers/run-trigger.sh +13 -0
  223. package/scripts/maintenance/pin-integrity.mjs +364 -0
  224. package/scripts/poll-slack-events.sh +41 -9
  225. package/scripts/poller/slack-socket-mode.mjs +28 -3
  226. package/scripts/session/supervisor.mjs +80 -13
  227. package/scripts/spawn-session.sh +13 -0
  228. package/bin/maestro.test.mjs +0 -1574
  229. package/lib/action-executor.test.mjs +0 -871
  230. package/lib/archetype.test.mjs +0 -132
  231. package/lib/assurance/plan-note.test.mjs +0 -234
  232. package/lib/assurance/room-budget.test.mjs +0 -486
  233. package/lib/assurance/tier.test.mjs +0 -174
  234. package/lib/autonomy.test.mjs +0 -66
  235. package/lib/backlog.test.mjs +0 -302
  236. package/lib/backup/policy.test.mjs +0 -305
  237. package/lib/budget-escalate.test.mjs +0 -232
  238. package/lib/budget-guard.envelope.test.mjs +0 -476
  239. package/lib/budget-guard.test.mjs +0 -427
  240. package/lib/cadence-bus-requeue.test.mjs +0 -83
  241. package/lib/cadence-bus-schedule.test.mjs +0 -194
  242. package/lib/cadence-bus.test.mjs +0 -720
  243. package/lib/cadences.test.mjs +0 -230
  244. package/lib/capability/inventory.test.mjs +0 -232
  245. package/lib/capability.test.mjs +0 -78
  246. package/lib/channels/base-adapter.test.mjs +0 -590
  247. package/lib/channels/channels.test.mjs +0 -371
  248. package/lib/channels/contract.test.mjs +0 -162
  249. package/lib/channels/inbox-item.test.mjs +0 -368
  250. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  251. package/lib/channels/pairing.test.mjs +0 -270
  252. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  253. package/lib/channels/slack-adapter.test.mjs +0 -212
  254. package/lib/channels/telegram-adapter.test.mjs +0 -306
  255. package/lib/channels/voice/adapter.test.mjs +0 -278
  256. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  257. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  258. package/lib/charter.test.mjs +0 -89
  259. package/lib/claude-bin.test.mjs +0 -131
  260. package/lib/cli/board.test.mjs +0 -227
  261. package/lib/cli/design.test.mjs +0 -270
  262. package/lib/cli/doctor-checks.test.mjs +0 -336
  263. package/lib/cli/global-setup-extras.test.mjs +0 -462
  264. package/lib/cli/inbox.test.mjs +0 -230
  265. package/lib/cli/session-ack.test.mjs +0 -63
  266. package/lib/cli/session.test.mjs +0 -613
  267. package/lib/collective/capture.test.mjs +0 -121
  268. package/lib/collective/cards.test.mjs +0 -114
  269. package/lib/collective/config.test.mjs +0 -123
  270. package/lib/collective/global-config.test.mjs +0 -220
  271. package/lib/collective/global-skills.test.mjs +0 -126
  272. package/lib/collective/presence.test.mjs +0 -95
  273. package/lib/collective/recall.test.mjs +0 -116
  274. package/lib/collective/vendor-skills.test.mjs +0 -306
  275. package/lib/comms/send-gate.test.mjs +0 -770
  276. package/lib/comms.test.mjs +0 -41
  277. package/lib/context/budget.test.mjs +0 -252
  278. package/lib/context/history-scope.test.mjs +0 -79
  279. package/lib/cost/ledger-row.test.mjs +0 -183
  280. package/lib/design/design-md.test.mjs +0 -318
  281. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  282. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  283. package/lib/design/fixtures/foundation.json +0 -133
  284. package/lib/design/refresh-gate.test.mjs +0 -144
  285. package/lib/design/write.test.mjs +0 -241
  286. package/lib/diagnostics/alerts.test.mjs +0 -318
  287. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  288. package/lib/diagnostics/counters.test.mjs +0 -206
  289. package/lib/diagnostics/events.test.mjs +0 -290
  290. package/lib/diagnostics/otel.test.mjs +0 -196
  291. package/lib/diagnostics/trace.test.mjs +0 -251
  292. package/lib/env-compat.test.mjs +0 -104
  293. package/lib/execution/disposition.test.mjs +0 -553
  294. package/lib/execution/drive.test.mjs +0 -270
  295. package/lib/execution/effects.test.mjs +0 -344
  296. package/lib/execution/intake.test.mjs +0 -389
  297. package/lib/execution/journal.test.mjs +0 -261
  298. package/lib/execution/match.test.mjs +0 -235
  299. package/lib/execution/pipeline.test.mjs +0 -392
  300. package/lib/execution/route.test.mjs +0 -186
  301. package/lib/execution/surface-policy.test.mjs +0 -162
  302. package/lib/fs-atomic.test.mjs +0 -72
  303. package/lib/fs-ownership.test.mjs +0 -158
  304. package/lib/goals/admission.test.mjs +0 -164
  305. package/lib/goals/classify.test.mjs +0 -167
  306. package/lib/goals/collaborate.test.mjs +0 -336
  307. package/lib/goals/gaps.test.mjs +0 -284
  308. package/lib/goals/loop.test.mjs +0 -845
  309. package/lib/hooks/bus.test.mjs +0 -387
  310. package/lib/identity/persona.test.mjs +0 -142
  311. package/lib/kpi-sensors.test.mjs +0 -278
  312. package/lib/kpi.test.mjs +0 -244
  313. package/lib/learning/config.test.mjs +0 -75
  314. package/lib/learning/counters.test.mjs +0 -69
  315. package/lib/learning/curator-consolidate.test.mjs +0 -238
  316. package/lib/learning/curator.test.mjs +0 -106
  317. package/lib/learning/reflect.test.mjs +0 -0
  318. package/lib/learning/session-index.test.mjs +0 -125
  319. package/lib/learning/skill-writer.test.mjs +0 -210
  320. package/lib/mandate/audit.test.mjs +0 -195
  321. package/lib/mandate/contract.test.mjs +0 -185
  322. package/lib/mandate/derive.test.mjs +0 -274
  323. package/lib/mandate/model.test.mjs +0 -164
  324. package/lib/mandate/refresh.test.mjs +0 -389
  325. package/lib/mcp/server.test.mjs +0 -426
  326. package/lib/model-router/auth-profiles.test.mjs +0 -580
  327. package/lib/model-router/catalog.test.mjs +0 -385
  328. package/lib/model-router/economics.test.mjs +0 -438
  329. package/lib/model-router/failover.test.mjs +0 -439
  330. package/lib/model-router/health.test.mjs +0 -338
  331. package/lib/model-router/integration-coverage.test.mjs +0 -831
  332. package/lib/model-router/integration.test.mjs +0 -564
  333. package/lib/model-router/ledger.test.mjs +0 -415
  334. package/lib/model-router/llm-task.test.mjs +0 -392
  335. package/lib/model-router/org-credentials.test.mjs +0 -265
  336. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  337. package/lib/model-router/reconcile.test.mjs +0 -316
  338. package/lib/model-router/repair.test.mjs +0 -180
  339. package/lib/model-router/spawn.test.mjs +0 -446
  340. package/lib/model-router/taxonomy.test.mjs +0 -410
  341. package/lib/model-router.test.mjs +0 -1207
  342. package/lib/org/activity.test.mjs +0 -134
  343. package/lib/org/approvals.test.mjs +0 -216
  344. package/lib/org/awareness.test.mjs +0 -159
  345. package/lib/org/board-mine-cache.test.mjs +0 -53
  346. package/lib/org/board.test.mjs +0 -187
  347. package/lib/org/bootstrap-context.test.mjs +0 -153
  348. package/lib/org/client.test.mjs +0 -1206
  349. package/lib/org/cohort-client.test.mjs +0 -126
  350. package/lib/org/cost-sync.test.mjs +0 -153
  351. package/lib/org/doctor.test.mjs +0 -346
  352. package/lib/org/engagement-ledger.test.mjs +0 -112
  353. package/lib/org/engagement.test.mjs +0 -739
  354. package/lib/org/handoff.test.mjs +0 -269
  355. package/lib/org/inbound/directedness.test.mjs +0 -668
  356. package/lib/org/inbound/facts.test.mjs +0 -471
  357. package/lib/org/inbound/hydrate.test.mjs +0 -908
  358. package/lib/org/inbound/index.test.mjs +0 -429
  359. package/lib/org/inbound/project.test.mjs +0 -287
  360. package/lib/org/integration-tools.test.mjs +0 -160
  361. package/lib/org/keys.test.mjs +0 -92
  362. package/lib/org/knowledge.test.mjs +0 -326
  363. package/lib/org/leases.test.mjs +0 -235
  364. package/lib/org/mesh-directives.test.mjs +0 -110
  365. package/lib/org/mesh-integration.test.mjs +0 -127
  366. package/lib/org/mesh.test.mjs +0 -400
  367. package/lib/org/messaging.test.mjs +0 -471
  368. package/lib/org/param-contract.test.mjs +0 -477
  369. package/lib/org/policy.test.mjs +0 -237
  370. package/lib/org/protocol.checksum.test.mjs +0 -90
  371. package/lib/org/protocol.test.mjs +0 -323
  372. package/lib/org/push.test.mjs +0 -792
  373. package/lib/org/registry.test.mjs +0 -100
  374. package/lib/org/resource-tools.test.mjs +0 -361
  375. package/lib/org/tool-access.test.mjs +0 -144
  376. package/lib/org/tool-surface-integration.test.mjs +0 -120
  377. package/lib/org/tool-surface.test.mjs +0 -1268
  378. package/lib/org/typing.test.mjs +0 -291
  379. package/lib/org/ui-parity.test.mjs +0 -560
  380. package/lib/org/verify.test.mjs +0 -194
  381. package/lib/org/work-ledger.test.mjs +0 -273
  382. package/lib/plan/adoption-e2e.test.mjs +0 -366
  383. package/lib/plan/budget-enforcement.test.mjs +0 -400
  384. package/lib/plan/compile.test.mjs +0 -382
  385. package/lib/plan/emit.test.mjs +0 -269
  386. package/lib/plan/explain.test.mjs +0 -188
  387. package/lib/prompts/parallelism.test.mjs +0 -177
  388. package/lib/rag/rag.test.mjs +0 -505
  389. package/lib/rate-guard.test.mjs +0 -272
  390. package/lib/reactive-gate.test.mjs +0 -57
  391. package/lib/render.test.mjs +0 -68
  392. package/lib/resource-governor.test.mjs +0 -488
  393. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  394. package/lib/scheduling/jitter.test.mjs +0 -140
  395. package/lib/secrets/broker.test.mjs +0 -280
  396. package/lib/secrets/providers.test.mjs +0 -274
  397. package/lib/security/audit-engine.test.mjs +0 -424
  398. package/lib/security/coerce-args.test.mjs +0 -281
  399. package/lib/security/dangerous-tools.test.mjs +0 -68
  400. package/lib/security/external-content.test.mjs +0 -84
  401. package/lib/security/redact.test.mjs +0 -441
  402. package/lib/security/secret-equal.test.mjs +0 -55
  403. package/lib/session/config.test.mjs +0 -92
  404. package/lib/session/feed-core.test.mjs +0 -198
  405. package/lib/session/first-run.test.mjs +0 -121
  406. package/lib/session/frontdoor.test.mjs +0 -205
  407. package/lib/session/handoffs.test.mjs +0 -183
  408. package/lib/session/identity.test.mjs +0 -180
  409. package/lib/session/inbox-claims.test.mjs +0 -286
  410. package/lib/session/launch-args.test.mjs +0 -157
  411. package/lib/session/liveness.test.mjs +0 -100
  412. package/lib/session/status-summary.test.mjs +0 -118
  413. package/lib/session-permissions.test.mjs +0 -120
  414. package/lib/setup/claude-probe.test.mjs +0 -187
  415. package/lib/setup/completeness.test.mjs +0 -110
  416. package/lib/setup/context-pack.test.mjs +0 -89
  417. package/lib/setup/enrich.test.mjs +0 -115
  418. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  419. package/lib/setup/integration.test.mjs +0 -162
  420. package/lib/setup/io.test.mjs +0 -77
  421. package/lib/setup/runner.test.mjs +0 -132
  422. package/lib/setup/sections/identity.test.mjs +0 -234
  423. package/lib/setup/sections/inventory.test.mjs +0 -198
  424. package/lib/setup/sections/learning.test.mjs +0 -81
  425. package/lib/setup/sections/mandate.test.mjs +0 -388
  426. package/lib/setup/sections/messaging.test.mjs +0 -127
  427. package/lib/setup/sections/model.test.mjs +0 -240
  428. package/lib/setup/sections/org.test.mjs +0 -346
  429. package/lib/setup/sections/orgmail.test.mjs +0 -118
  430. package/lib/setup/sections/recovery.test.mjs +0 -98
  431. package/lib/setup/sections/subagents.test.mjs +0 -429
  432. package/lib/setup/sections/verify.test.mjs +0 -175
  433. package/lib/setup/sot.test.mjs +0 -81
  434. package/lib/setup/state.test.mjs +0 -115
  435. package/lib/singleton.test.mjs +0 -151
  436. package/lib/subagents/cli.test.mjs +0 -389
  437. package/lib/subagents/client.test.mjs +0 -309
  438. package/lib/subagents/gap.test.mjs +0 -234
  439. package/lib/subagents/lock.test.mjs +0 -248
  440. package/lib/subagents/manifest.test.mjs +0 -175
  441. package/lib/subagents/refs.test.mjs +0 -204
  442. package/lib/subagents/resolve.test.mjs +0 -422
  443. package/lib/subagents/schema.test.mjs +0 -328
  444. package/lib/telemetry/alerts.test.mjs +0 -109
  445. package/lib/telemetry/collect.test.mjs +0 -1274
  446. package/lib/tool-definitions-integration.test.mjs +0 -83
  447. package/lib/tool-definitions.test.mjs +0 -437
  448. package/lib/upgrade/global-refresh.test.mjs +0 -65
  449. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  450. package/lib/upgrade/post-steps.test.mjs +0 -200
  451. package/lib/upgrade/verify.test.mjs +0 -164
  452. package/lib/util/fetch-timeout.test.mjs +0 -202
  453. package/lib/util/reconnect.test.mjs +0 -369
  454. package/lib/util/unhandled.test.mjs +0 -216
  455. package/lib/voice/outbound.test.mjs +0 -69
  456. package/lib/voice/session-rotation.test.mjs +0 -114
  457. package/lib/voice/stt.test.mjs +0 -226
  458. package/lib/voice/voice.test.mjs +0 -990
  459. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  460. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  461. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  462. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  463. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  464. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  465. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  466. package/scripts/ci/check.test.mjs +0 -194
  467. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  468. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  469. package/scripts/collective/hook-runner.test.mjs +0 -173
  470. package/scripts/cost/fleet-digest.test.mjs +0 -207
  471. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  472. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  473. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  474. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  475. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  476. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  477. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  478. package/scripts/daemon/assurance.test.mjs +0 -1791
  479. package/scripts/daemon/board-mirror.test.mjs +0 -165
  480. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  481. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  482. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  483. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  484. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  485. package/scripts/daemon/classifier.test.mjs +0 -266
  486. package/scripts/daemon/classify-kind.test.mjs +0 -40
  487. package/scripts/daemon/context-compiler.test.mjs +0 -406
  488. package/scripts/daemon/deliver.test.mjs +0 -564
  489. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  490. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  491. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  492. package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
  493. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  494. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  495. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  496. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  497. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  498. package/scripts/daemon/integration.test.mjs +0 -149
  499. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  500. package/scripts/daemon/lib/session-router.test.mjs +0 -554
  501. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  502. package/scripts/daemon/prompt-builder.test.mjs +0 -556
  503. package/scripts/daemon/responder-cost.test.mjs +0 -68
  504. package/scripts/daemon/responder-history.test.mjs +0 -221
  505. package/scripts/daemon/sdk-version.test.mjs +0 -31
  506. package/scripts/daemon/session-lock.test.mjs +0 -252
  507. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  508. package/scripts/daemon/typing-registry.test.mjs +0 -102
  509. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  510. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  511. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  512. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  513. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  514. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  515. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  516. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  517. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  518. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  519. package/scripts/poller-launchd/install.test.mjs +0 -243
  520. package/scripts/restore-from-backup.test.mjs +0 -181
  521. package/scripts/session/feed.test.mjs +0 -196
  522. package/scripts/session/supervisor-sh.test.mjs +0 -218
  523. package/scripts/session/supervisor.test.mjs +0 -482
  524. package/scripts/setup/configure-macos.test.mjs +0 -306
  525. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  526. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  527. package/scripts/setup/generate-capability.test.mjs +0 -134
  528. package/scripts/setup/init-agent.test.mjs +0 -370
  529. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  530. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  531. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -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 };