@cohortapp/agent-sdk 2.16.0 → 2.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (529) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +23 -7
  3. package/README.md +1 -0
  4. package/bin/maestro.mjs +62 -0
  5. package/docs/guides/billing-console-keys.md +60 -0
  6. package/docs/guides/front-door-session.md +54 -9
  7. package/docs/guides/mac-mini.md +20 -25
  8. package/docs/guides/poller-daemon-setup.md +4 -1
  9. package/docs/guides/setup-wizard.md +1 -1
  10. package/docs/runbooks/fleet-rollout.md +156 -0
  11. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  12. package/lib/action-executor.js +19 -3
  13. package/lib/budget-guard.mjs +279 -3
  14. package/lib/channels/base-adapter.mjs +3 -1
  15. package/lib/channels/contract.mjs +2 -1
  16. package/lib/channels/inbox-item.mjs +8 -0
  17. package/lib/claude-bin.mjs +5 -6
  18. package/lib/cli/doctor-checks.mjs +141 -10
  19. package/lib/cli/global-setup-extras.mjs +5 -1
  20. package/lib/cli/inbox.mjs +100 -15
  21. package/lib/cli/seat-auth.mjs +463 -0
  22. package/lib/cli/session.mjs +80 -12
  23. package/lib/collective/capture.mjs +8 -6
  24. package/lib/collective/global-config.mjs +63 -1
  25. package/lib/collective/presence.mjs +142 -5
  26. package/lib/comms/send-gate.mjs +559 -1
  27. package/lib/context/budget.mjs +327 -0
  28. package/lib/context/history-scope.mjs +138 -0
  29. package/lib/diagnostics/alerts.mjs +49 -0
  30. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  31. package/lib/engine/agents/definitions.mjs +343 -0
  32. package/lib/engine/agents/persist.mjs +275 -0
  33. package/lib/engine/agents/runtime.mjs +748 -0
  34. package/lib/engine/agents/usage.mjs +95 -0
  35. package/lib/engine/auth-status.mjs +139 -0
  36. package/lib/engine/budget.mjs +194 -0
  37. package/lib/engine/cli.mjs +1204 -0
  38. package/lib/engine/commands/index.mjs +269 -0
  39. package/lib/engine/context/budget.mjs +219 -0
  40. package/lib/engine/context/cache.mjs +125 -0
  41. package/lib/engine/context/child-env.mjs +215 -0
  42. package/lib/engine/context/compaction.mjs +342 -0
  43. package/lib/engine/context/images.mjs +90 -0
  44. package/lib/engine/context/instructions.mjs +327 -0
  45. package/lib/engine/context/lazy-instructions.mjs +169 -0
  46. package/lib/engine/context/manager.mjs +182 -0
  47. package/lib/engine/context/real-path.mjs +91 -0
  48. package/lib/engine/context/secret-values.mjs +163 -0
  49. package/lib/engine/context/settings.mjs +274 -0
  50. package/lib/engine/context/stream-input.mjs +159 -0
  51. package/lib/engine/guard.mjs +152 -0
  52. package/lib/engine/hooks.mjs +713 -0
  53. package/lib/engine/loop.mjs +560 -0
  54. package/lib/engine/mcp/client.mjs +254 -0
  55. package/lib/engine/mcp/config.mjs +301 -0
  56. package/lib/engine/mcp/http.mjs +201 -0
  57. package/lib/engine/mcp/index.mjs +146 -0
  58. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  59. package/lib/engine/mcp/naming.mjs +66 -0
  60. package/lib/engine/mcp/resources.mjs +89 -0
  61. package/lib/engine/mcp/results.mjs +133 -0
  62. package/lib/engine/mcp/stdio.mjs +137 -0
  63. package/lib/engine/mcp/supervisor.mjs +116 -0
  64. package/lib/engine/messages.mjs +104 -0
  65. package/lib/engine/output/json.mjs +164 -0
  66. package/lib/engine/output/stream-json.mjs +266 -0
  67. package/lib/engine/permissions.mjs +845 -0
  68. package/lib/engine/process-identity.mjs +164 -0
  69. package/lib/engine/process-tree.mjs +551 -0
  70. package/lib/engine/prompt.mjs +60 -0
  71. package/lib/engine/session/store.mjs +299 -0
  72. package/lib/engine/session-runtime/args.mjs +97 -0
  73. package/lib/engine/session-runtime/host.mjs +143 -0
  74. package/lib/engine/session-runtime/inbox.mjs +122 -0
  75. package/lib/engine/session-runtime/notifications.mjs +129 -0
  76. package/lib/engine/session-runtime/registry.mjs +328 -0
  77. package/lib/engine/session-runtime/runner.mjs +344 -0
  78. package/lib/engine/session-runtime/socket.mjs +212 -0
  79. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  80. package/lib/engine/skills/index.mjs +321 -0
  81. package/lib/engine/tools/bash-background.mjs +533 -0
  82. package/lib/engine/tools/bash.mjs +216 -0
  83. package/lib/engine/tools/edit.mjs +97 -0
  84. package/lib/engine/tools/glob.mjs +81 -0
  85. package/lib/engine/tools/grep.mjs +224 -0
  86. package/lib/engine/tools/index.mjs +84 -0
  87. package/lib/engine/tools/list-agents.mjs +32 -0
  88. package/lib/engine/tools/ls.mjs +127 -0
  89. package/lib/engine/tools/monitor.mjs +82 -0
  90. package/lib/engine/tools/notebook-edit.mjs +218 -0
  91. package/lib/engine/tools/read.mjs +103 -0
  92. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  93. package/lib/engine/tools/schema.mjs +144 -0
  94. package/lib/engine/tools/send-message.mjs +77 -0
  95. package/lib/engine/tools/session.mjs +70 -0
  96. package/lib/engine/tools/todo.mjs +144 -0
  97. package/lib/engine/tools/toolsearch.mjs +217 -0
  98. package/lib/engine/tools/walk.mjs +193 -0
  99. package/lib/engine/tools/web-switch.mjs +31 -0
  100. package/lib/engine/tools/webfetch-html.mjs +387 -0
  101. package/lib/engine/tools/webfetch-net.mjs +340 -0
  102. package/lib/engine/tools/webfetch.mjs +198 -0
  103. package/lib/engine/tools/websearch.mjs +91 -0
  104. package/lib/engine/tools/workflow.mjs +95 -0
  105. package/lib/engine/tools/write.mjs +76 -0
  106. package/lib/engine/tui/line-editor.mjs +137 -0
  107. package/lib/engine/tui/render.mjs +86 -0
  108. package/lib/engine/tui/tui.mjs +274 -0
  109. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  110. package/lib/engine/wire/effort.mjs +36 -0
  111. package/lib/engine/wire/errors.mjs +496 -0
  112. package/lib/engine/wire/http.mjs +441 -0
  113. package/lib/engine/wire/index.mjs +76 -0
  114. package/lib/engine/wire/openai-chat.mjs +332 -0
  115. package/lib/engine/wire/prompt-cache.mjs +79 -0
  116. package/lib/engine/wire/search.mjs +140 -0
  117. package/lib/engine/wire/sse.mjs +114 -0
  118. package/lib/engine/wire/stall.mjs +349 -0
  119. package/lib/engine/wire/token-provider.mjs +175 -0
  120. package/lib/engine/wire/usage.mjs +192 -0
  121. package/lib/engine/workflow/host.mjs +524 -0
  122. package/lib/engine/workflow/journal.mjs +188 -0
  123. package/lib/engine/workflow/json-schema.mjs +171 -0
  124. package/lib/engine/workflow/meta.mjs +329 -0
  125. package/lib/engine/workflow/notifications.mjs +52 -0
  126. package/lib/engine/workflow/runtime.mjs +447 -0
  127. package/lib/engine/workflow/sandbox.mjs +534 -0
  128. package/lib/engine/workflow/worker.mjs +141 -0
  129. package/lib/engine/workflow/worktree.mjs +74 -0
  130. package/lib/execution/disposition.mjs +1 -1
  131. package/lib/execution/intake.mjs +10 -0
  132. package/lib/execution/surface-policy.mjs +15 -0
  133. package/lib/learning/curator.mjs +8 -6
  134. package/lib/learning/reflect.mjs +8 -6
  135. package/lib/model-router/catalog/cohort.yaml +137 -0
  136. package/lib/model-router/catalog.mjs +118 -1
  137. package/lib/model-router/economics.mjs +9 -0
  138. package/lib/model-router/failover.mjs +67 -16
  139. package/lib/model-router/llm-task.mjs +39 -3
  140. package/lib/model-router/resolve.mjs +95 -3
  141. package/lib/model-router/spawn.mjs +46 -47
  142. package/lib/model-router/taxonomy.mjs +126 -4
  143. package/lib/org/cost-sync.mjs +141 -11
  144. package/lib/org/inbound/broadcast.mjs +289 -0
  145. package/lib/org/inbound/collective.mjs +375 -0
  146. package/lib/org/inbound/directedness.mjs +96 -8
  147. package/lib/org/inbound/facts.mjs +82 -4
  148. package/lib/org/inbound/hydrate.mjs +555 -51
  149. package/lib/org/inbound/project.mjs +22 -0
  150. package/lib/org/inbound/surfaces.mjs +14 -0
  151. package/lib/org/llm-token.mjs +879 -0
  152. package/lib/org/mesh.mjs +61 -0
  153. package/lib/org/messaging.mjs +3 -1
  154. package/lib/org/protocol.checksum +1 -1
  155. package/lib/org/protocol.mjs +15 -0
  156. package/lib/org/quota.mjs +520 -0
  157. package/lib/org/tool-surface.mjs +104 -16
  158. package/lib/org/ui-parity.mjs +16 -1
  159. package/lib/org/work-ledger.mjs +37 -6
  160. package/lib/rate-guard.mjs +114 -1
  161. package/lib/resource-governor.mjs +41 -6
  162. package/lib/runtime/adapter.mjs +823 -0
  163. package/lib/runtime/child-env.mjs +191 -0
  164. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  165. package/lib/runtime/seat-engine.mjs +162 -0
  166. package/lib/session/ask-ledger.mjs +271 -0
  167. package/lib/session/current-work.mjs +676 -0
  168. package/lib/session/feed-core.mjs +40 -3
  169. package/lib/session/launch-args.mjs +56 -4
  170. package/lib/session/status-summary.mjs +26 -9
  171. package/lib/session/upgrade-notice.mjs +42 -0
  172. package/lib/setup/claude-probe.mjs +117 -13
  173. package/lib/setup/enrich.mjs +13 -10
  174. package/lib/setup/sections/model.mjs +39 -13
  175. package/lib/telemetry/collect.mjs +208 -9
  176. package/lib/upgrade/ignored-drift.mjs +105 -0
  177. package/lib/voice/post-call-brief.mjs +30 -17
  178. package/package.json +15 -3
  179. package/plugins/maestro-skills/skills/board-work.md +5 -0
  180. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  181. package/plugins/maestro-skills/skills/main-session.md +18 -7
  182. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  183. package/scripts/ci/run-tests.mjs +47 -19
  184. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  185. package/scripts/collective/hook-runner.mjs +29 -2
  186. package/scripts/continuous-monitor.sh +13 -0
  187. package/scripts/cost/track-claude-usage.mjs +15 -0
  188. package/scripts/daemon/agent-daemon.mjs +408 -20
  189. package/scripts/daemon/assurance.mjs +48 -12
  190. package/scripts/daemon/cadence-consumer.mjs +218 -68
  191. package/scripts/daemon/cadence-handlers.mjs +73 -4
  192. package/scripts/daemon/classifier.mjs +75 -26
  193. package/scripts/daemon/context-compiler.mjs +104 -59
  194. package/scripts/daemon/deliver.mjs +30 -1
  195. package/scripts/daemon/dispatcher.mjs +804 -157
  196. package/scripts/daemon/health.mjs +14 -1
  197. package/scripts/daemon/lib/session-router.mjs +310 -42
  198. package/scripts/daemon/maestro-daemon.mjs +11 -0
  199. package/scripts/daemon/prompt-builder.mjs +121 -12
  200. package/scripts/daemon/responder.mjs +315 -146
  201. package/scripts/daemon/sdk-version.mjs +98 -16
  202. package/scripts/eval/probe-gateway.mjs +635 -0
  203. package/scripts/eval/replay/extract.mjs +270 -0
  204. package/scripts/eval/replay/grade.mjs +260 -0
  205. package/scripts/eval/replay/lib/config.mjs +50 -0
  206. package/scripts/eval/replay/lib/effects.mjs +65 -0
  207. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  208. package/scripts/eval/replay/lib/judge.mjs +72 -0
  209. package/scripts/eval/replay/lib/redact.mjs +136 -0
  210. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  211. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  212. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  213. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  214. package/scripts/eval/replay/report.mjs +185 -0
  215. package/scripts/eval/replay/run.mjs +404 -0
  216. package/scripts/fleet/rollout.mjs +1094 -0
  217. package/scripts/hooks/pre-send-audit.sh +36 -245
  218. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  219. package/scripts/hooks/validate-state-yaml.sh +190 -0
  220. package/scripts/huddle/huddle-llm.mjs +361 -0
  221. package/scripts/huddle/huddle-server.mjs +46 -121
  222. package/scripts/local-triggers/autoupdate.sh +448 -78
  223. package/scripts/local-triggers/run-trigger.sh +13 -0
  224. package/scripts/maintenance/pin-integrity.mjs +364 -0
  225. package/scripts/poll-slack-events.sh +41 -9
  226. package/scripts/poller/slack-socket-mode.mjs +28 -3
  227. package/scripts/session/supervisor.mjs +80 -13
  228. package/scripts/spawn-session.sh +13 -0
  229. package/bin/maestro.test.mjs +0 -1574
  230. package/lib/action-executor.test.mjs +0 -871
  231. package/lib/archetype.test.mjs +0 -132
  232. package/lib/assurance/plan-note.test.mjs +0 -234
  233. package/lib/assurance/room-budget.test.mjs +0 -486
  234. package/lib/assurance/tier.test.mjs +0 -174
  235. package/lib/autonomy.test.mjs +0 -66
  236. package/lib/backlog.test.mjs +0 -302
  237. package/lib/backup/policy.test.mjs +0 -305
  238. package/lib/budget-escalate.test.mjs +0 -232
  239. package/lib/budget-guard.envelope.test.mjs +0 -476
  240. package/lib/budget-guard.test.mjs +0 -427
  241. package/lib/cadence-bus-requeue.test.mjs +0 -83
  242. package/lib/cadence-bus-schedule.test.mjs +0 -194
  243. package/lib/cadence-bus.test.mjs +0 -720
  244. package/lib/cadences.test.mjs +0 -230
  245. package/lib/capability/inventory.test.mjs +0 -232
  246. package/lib/capability.test.mjs +0 -78
  247. package/lib/channels/base-adapter.test.mjs +0 -590
  248. package/lib/channels/channels.test.mjs +0 -371
  249. package/lib/channels/contract.test.mjs +0 -162
  250. package/lib/channels/inbox-item.test.mjs +0 -368
  251. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  252. package/lib/channels/pairing.test.mjs +0 -270
  253. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  254. package/lib/channels/slack-adapter.test.mjs +0 -212
  255. package/lib/channels/telegram-adapter.test.mjs +0 -306
  256. package/lib/channels/voice/adapter.test.mjs +0 -278
  257. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  258. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  259. package/lib/charter.test.mjs +0 -89
  260. package/lib/claude-bin.test.mjs +0 -131
  261. package/lib/cli/board.test.mjs +0 -227
  262. package/lib/cli/design.test.mjs +0 -270
  263. package/lib/cli/doctor-checks.test.mjs +0 -336
  264. package/lib/cli/global-setup-extras.test.mjs +0 -462
  265. package/lib/cli/inbox.test.mjs +0 -230
  266. package/lib/cli/session-ack.test.mjs +0 -63
  267. package/lib/cli/session.test.mjs +0 -613
  268. package/lib/collective/capture.test.mjs +0 -121
  269. package/lib/collective/cards.test.mjs +0 -114
  270. package/lib/collective/config.test.mjs +0 -123
  271. package/lib/collective/global-config.test.mjs +0 -220
  272. package/lib/collective/global-skills.test.mjs +0 -126
  273. package/lib/collective/presence.test.mjs +0 -95
  274. package/lib/collective/recall.test.mjs +0 -116
  275. package/lib/collective/vendor-skills.test.mjs +0 -306
  276. package/lib/comms/send-gate.test.mjs +0 -770
  277. package/lib/comms.test.mjs +0 -41
  278. package/lib/cost/ledger-row.test.mjs +0 -183
  279. package/lib/design/design-md.test.mjs +0 -318
  280. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  281. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  282. package/lib/design/fixtures/foundation.json +0 -133
  283. package/lib/design/refresh-gate.test.mjs +0 -144
  284. package/lib/design/write.test.mjs +0 -241
  285. package/lib/diagnostics/alerts.test.mjs +0 -318
  286. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  287. package/lib/diagnostics/counters.test.mjs +0 -206
  288. package/lib/diagnostics/events.test.mjs +0 -290
  289. package/lib/diagnostics/otel.test.mjs +0 -196
  290. package/lib/diagnostics/trace.test.mjs +0 -251
  291. package/lib/env-compat.test.mjs +0 -104
  292. package/lib/execution/disposition.test.mjs +0 -553
  293. package/lib/execution/drive.test.mjs +0 -270
  294. package/lib/execution/effects.test.mjs +0 -344
  295. package/lib/execution/intake.test.mjs +0 -389
  296. package/lib/execution/journal.test.mjs +0 -261
  297. package/lib/execution/match.test.mjs +0 -235
  298. package/lib/execution/pipeline.test.mjs +0 -392
  299. package/lib/execution/route.test.mjs +0 -186
  300. package/lib/execution/surface-policy.test.mjs +0 -162
  301. package/lib/fs-atomic.test.mjs +0 -72
  302. package/lib/fs-ownership.test.mjs +0 -158
  303. package/lib/goals/admission.test.mjs +0 -164
  304. package/lib/goals/classify.test.mjs +0 -167
  305. package/lib/goals/collaborate.test.mjs +0 -336
  306. package/lib/goals/gaps.test.mjs +0 -284
  307. package/lib/goals/loop.test.mjs +0 -845
  308. package/lib/hooks/bus.test.mjs +0 -387
  309. package/lib/identity/persona.test.mjs +0 -142
  310. package/lib/kpi-sensors.test.mjs +0 -278
  311. package/lib/kpi.test.mjs +0 -244
  312. package/lib/learning/config.test.mjs +0 -75
  313. package/lib/learning/counters.test.mjs +0 -69
  314. package/lib/learning/curator-consolidate.test.mjs +0 -238
  315. package/lib/learning/curator.test.mjs +0 -106
  316. package/lib/learning/reflect.test.mjs +0 -0
  317. package/lib/learning/session-index.test.mjs +0 -125
  318. package/lib/learning/skill-writer.test.mjs +0 -210
  319. package/lib/mandate/audit.test.mjs +0 -195
  320. package/lib/mandate/contract.test.mjs +0 -185
  321. package/lib/mandate/derive.test.mjs +0 -274
  322. package/lib/mandate/model.test.mjs +0 -164
  323. package/lib/mandate/refresh.test.mjs +0 -389
  324. package/lib/mcp/server.test.mjs +0 -426
  325. package/lib/model-router/auth-profiles.test.mjs +0 -580
  326. package/lib/model-router/catalog.test.mjs +0 -385
  327. package/lib/model-router/economics.test.mjs +0 -438
  328. package/lib/model-router/failover.test.mjs +0 -439
  329. package/lib/model-router/health.test.mjs +0 -338
  330. package/lib/model-router/integration-coverage.test.mjs +0 -831
  331. package/lib/model-router/integration.test.mjs +0 -564
  332. package/lib/model-router/ledger.test.mjs +0 -415
  333. package/lib/model-router/llm-task.test.mjs +0 -392
  334. package/lib/model-router/org-credentials.test.mjs +0 -265
  335. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  336. package/lib/model-router/reconcile.test.mjs +0 -316
  337. package/lib/model-router/repair.test.mjs +0 -180
  338. package/lib/model-router/spawn.test.mjs +0 -446
  339. package/lib/model-router/taxonomy.test.mjs +0 -410
  340. package/lib/model-router.test.mjs +0 -1207
  341. package/lib/org/activity.test.mjs +0 -134
  342. package/lib/org/approvals.test.mjs +0 -216
  343. package/lib/org/awareness.test.mjs +0 -159
  344. package/lib/org/board-mine-cache.test.mjs +0 -53
  345. package/lib/org/board.test.mjs +0 -187
  346. package/lib/org/bootstrap-context.test.mjs +0 -153
  347. package/lib/org/client.test.mjs +0 -1206
  348. package/lib/org/cohort-client.test.mjs +0 -126
  349. package/lib/org/cost-sync.test.mjs +0 -153
  350. package/lib/org/doctor.test.mjs +0 -346
  351. package/lib/org/engagement-ledger.test.mjs +0 -112
  352. package/lib/org/engagement.test.mjs +0 -739
  353. package/lib/org/handoff.test.mjs +0 -269
  354. package/lib/org/inbound/directedness.test.mjs +0 -668
  355. package/lib/org/inbound/facts.test.mjs +0 -471
  356. package/lib/org/inbound/hydrate.test.mjs +0 -453
  357. package/lib/org/inbound/index.test.mjs +0 -429
  358. package/lib/org/inbound/project.test.mjs +0 -287
  359. package/lib/org/integration-tools.test.mjs +0 -160
  360. package/lib/org/keys.test.mjs +0 -92
  361. package/lib/org/knowledge.test.mjs +0 -326
  362. package/lib/org/leases.test.mjs +0 -235
  363. package/lib/org/mesh-directives.test.mjs +0 -110
  364. package/lib/org/mesh-integration.test.mjs +0 -127
  365. package/lib/org/mesh.test.mjs +0 -400
  366. package/lib/org/messaging.test.mjs +0 -471
  367. package/lib/org/param-contract.test.mjs +0 -477
  368. package/lib/org/policy.test.mjs +0 -237
  369. package/lib/org/protocol.checksum.test.mjs +0 -90
  370. package/lib/org/protocol.test.mjs +0 -323
  371. package/lib/org/push.test.mjs +0 -792
  372. package/lib/org/registry.test.mjs +0 -100
  373. package/lib/org/resource-tools.test.mjs +0 -361
  374. package/lib/org/tool-access.test.mjs +0 -144
  375. package/lib/org/tool-surface-integration.test.mjs +0 -120
  376. package/lib/org/tool-surface.test.mjs +0 -1268
  377. package/lib/org/typing.test.mjs +0 -291
  378. package/lib/org/ui-parity.test.mjs +0 -560
  379. package/lib/org/verify.test.mjs +0 -194
  380. package/lib/org/work-ledger.test.mjs +0 -273
  381. package/lib/plan/adoption-e2e.test.mjs +0 -366
  382. package/lib/plan/budget-enforcement.test.mjs +0 -400
  383. package/lib/plan/compile.test.mjs +0 -382
  384. package/lib/plan/emit.test.mjs +0 -269
  385. package/lib/plan/explain.test.mjs +0 -188
  386. package/lib/prompts/parallelism.test.mjs +0 -177
  387. package/lib/rag/rag.test.mjs +0 -505
  388. package/lib/rate-guard.test.mjs +0 -272
  389. package/lib/reactive-gate.test.mjs +0 -57
  390. package/lib/render.test.mjs +0 -68
  391. package/lib/resource-governor.test.mjs +0 -488
  392. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  393. package/lib/scheduling/jitter.test.mjs +0 -140
  394. package/lib/secrets/broker.test.mjs +0 -280
  395. package/lib/secrets/providers.test.mjs +0 -274
  396. package/lib/security/audit-engine.test.mjs +0 -424
  397. package/lib/security/coerce-args.test.mjs +0 -281
  398. package/lib/security/dangerous-tools.test.mjs +0 -68
  399. package/lib/security/external-content.test.mjs +0 -84
  400. package/lib/security/redact.test.mjs +0 -441
  401. package/lib/security/secret-equal.test.mjs +0 -55
  402. package/lib/session/config.test.mjs +0 -92
  403. package/lib/session/feed-core.test.mjs +0 -198
  404. package/lib/session/first-run.test.mjs +0 -121
  405. package/lib/session/frontdoor.test.mjs +0 -205
  406. package/lib/session/handoffs.test.mjs +0 -183
  407. package/lib/session/identity.test.mjs +0 -180
  408. package/lib/session/inbox-claims.test.mjs +0 -286
  409. package/lib/session/launch-args.test.mjs +0 -157
  410. package/lib/session/liveness.test.mjs +0 -100
  411. package/lib/session/status-summary.test.mjs +0 -118
  412. package/lib/session-permissions.test.mjs +0 -120
  413. package/lib/setup/claude-probe.test.mjs +0 -187
  414. package/lib/setup/completeness.test.mjs +0 -110
  415. package/lib/setup/context-pack.test.mjs +0 -89
  416. package/lib/setup/enrich.test.mjs +0 -115
  417. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  418. package/lib/setup/integration.test.mjs +0 -162
  419. package/lib/setup/io.test.mjs +0 -77
  420. package/lib/setup/runner.test.mjs +0 -132
  421. package/lib/setup/sections/identity.test.mjs +0 -234
  422. package/lib/setup/sections/inventory.test.mjs +0 -198
  423. package/lib/setup/sections/learning.test.mjs +0 -81
  424. package/lib/setup/sections/mandate.test.mjs +0 -388
  425. package/lib/setup/sections/messaging.test.mjs +0 -127
  426. package/lib/setup/sections/model.test.mjs +0 -240
  427. package/lib/setup/sections/org.test.mjs +0 -346
  428. package/lib/setup/sections/orgmail.test.mjs +0 -118
  429. package/lib/setup/sections/recovery.test.mjs +0 -98
  430. package/lib/setup/sections/subagents.test.mjs +0 -429
  431. package/lib/setup/sections/verify.test.mjs +0 -175
  432. package/lib/setup/sot.test.mjs +0 -81
  433. package/lib/setup/state.test.mjs +0 -115
  434. package/lib/singleton.test.mjs +0 -151
  435. package/lib/subagents/cli.test.mjs +0 -389
  436. package/lib/subagents/client.test.mjs +0 -309
  437. package/lib/subagents/gap.test.mjs +0 -234
  438. package/lib/subagents/lock.test.mjs +0 -248
  439. package/lib/subagents/manifest.test.mjs +0 -175
  440. package/lib/subagents/refs.test.mjs +0 -204
  441. package/lib/subagents/resolve.test.mjs +0 -422
  442. package/lib/subagents/schema.test.mjs +0 -328
  443. package/lib/telemetry/alerts.test.mjs +0 -109
  444. package/lib/telemetry/collect.test.mjs +0 -1274
  445. package/lib/tool-definitions-integration.test.mjs +0 -83
  446. package/lib/tool-definitions.test.mjs +0 -437
  447. package/lib/upgrade/global-refresh.test.mjs +0 -65
  448. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  449. package/lib/upgrade/post-steps.test.mjs +0 -200
  450. package/lib/upgrade/verify.test.mjs +0 -164
  451. package/lib/util/fetch-timeout.test.mjs +0 -202
  452. package/lib/util/reconnect.test.mjs +0 -369
  453. package/lib/util/unhandled.test.mjs +0 -216
  454. package/lib/voice/outbound.test.mjs +0 -69
  455. package/lib/voice/session-rotation.test.mjs +0 -114
  456. package/lib/voice/stt.test.mjs +0 -226
  457. package/lib/voice/voice.test.mjs +0 -990
  458. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  459. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  460. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  461. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  462. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  463. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  464. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  465. package/scripts/ci/check.test.mjs +0 -194
  466. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  467. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  468. package/scripts/collective/hook-runner.test.mjs +0 -173
  469. package/scripts/cost/fleet-digest.test.mjs +0 -207
  470. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  471. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  472. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  473. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  474. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  475. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  476. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  477. package/scripts/daemon/assurance.test.mjs +0 -1791
  478. package/scripts/daemon/board-mirror.test.mjs +0 -165
  479. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  480. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  481. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  482. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  483. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  484. package/scripts/daemon/classifier.test.mjs +0 -266
  485. package/scripts/daemon/classify-kind.test.mjs +0 -40
  486. package/scripts/daemon/context-compiler.test.mjs +0 -300
  487. package/scripts/daemon/deliver.test.mjs +0 -564
  488. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  489. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  490. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  491. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  492. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  493. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  494. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  495. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  496. package/scripts/daemon/integration.test.mjs +0 -149
  497. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  498. package/scripts/daemon/lib/session-router.test.mjs +0 -295
  499. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  500. package/scripts/daemon/prompt-builder.test.mjs +0 -344
  501. package/scripts/daemon/responder-cost.test.mjs +0 -68
  502. package/scripts/daemon/responder-history.test.mjs +0 -185
  503. package/scripts/daemon/sdk-version.test.mjs +0 -31
  504. package/scripts/daemon/session-lock.test.mjs +0 -252
  505. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  506. package/scripts/daemon/typing-registry.test.mjs +0 -102
  507. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  508. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  509. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  510. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  511. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  512. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  513. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  514. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  515. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  516. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  517. package/scripts/poller-launchd/install.test.mjs +0 -243
  518. package/scripts/restore-from-backup.test.mjs +0 -181
  519. package/scripts/session/feed.test.mjs +0 -196
  520. package/scripts/session/supervisor-sh.test.mjs +0 -218
  521. package/scripts/session/supervisor.test.mjs +0 -482
  522. package/scripts/setup/configure-macos.test.mjs +0 -306
  523. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  524. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  525. package/scripts/setup/generate-capability.test.mjs +0 -134
  526. package/scripts/setup/init-agent.test.mjs +0 -370
  527. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  528. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  529. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -105,6 +105,7 @@ import { WORK_STAGES } from "./work-ledger.mjs";
105
105
  // sessions, open cadence handoffs and the cached board-mine count, read from
106
106
  // local state so the tool answers with no token and no network.
107
107
  import { readSessionStatus, statusLine } from "../session/status-summary.mjs";
108
+ import { currentWorkBlock } from "../session/current-work.mjs";
108
109
  // The hq vocabularies the model must pick from (an out-of-vocabulary enum value
109
110
  // is a BAD_REQUEST). Sourced from the wire contract so the tool table and the
110
111
  // normaliser can never disagree about what hq accepts.
@@ -343,6 +344,23 @@ export const ORG_TOOLS = Object.freeze([
343
344
  access: "read",
344
345
  binding: { kind: "rpc", method: "messaging.history" },
345
346
  },
347
+ {
348
+ name: "messaging_silences",
349
+ title: "Why a message got no reply",
350
+ description:
351
+ "WHY a person's message got NO reply from any AI colleague (messaging.silences): the routing " +
352
+ "verdicts recorded when a message was read as addressed to a person, or nobody was elected to " +
353
+ "answer. Pass messageId for ONE message, or channelId for a channel's recent silences. Use it " +
354
+ "when someone asks why nobody answered, before guessing.",
355
+ input_schema: S({
356
+ messageId: str("The message that went unanswered (one of messageId / channelId is required)."),
357
+ channelId: str("Channel id for its recent silences (one of messageId / channelId is required)."),
358
+ windowDays: num("Channel arm only: how far back to look (server default 14, max 90)."),
359
+ limit: num("Max rows (server default applies)."),
360
+ }),
361
+ access: "read",
362
+ binding: { kind: "rpc", method: "messaging.silences" },
363
+ },
346
364
  {
347
365
  name: "messaging_send",
348
366
  title: "Send org message",
@@ -892,8 +910,19 @@ export const ORG_TOOLS = Object.freeze([
892
910
  "needed. `live:false` with state 'stale' means the session exists but stopped beating; " +
893
911
  "'absent' means it is not running (the daemon is then answering inbound itself). Use it " +
894
912
  "before addressing the main session with SendMessage, or to answer \"is the front door " +
895
- "up?\". Never quote session names to a human — they are internal.",
896
- input_schema: S({}),
913
+ "up?\". `currentWork` is the bounded \"what am I doing right now\" block — the daemon's live " +
914
+ "sessions with the board item each is for, board items in progress, peers, and whether an " +
915
+ "interactive session is open on the box (existence only) — so a \"what are you doing?\" is " +
916
+ "answered from fact. It is SCOPED TO AN AUDIENCE: pass the asker (`audienceSender`, " +
917
+ "`audienceChannelId`, `audiencePrivate`) and a live session from another conversation is " +
918
+ "reported as existing only — no summary, no sender, no board id — because its summary is " +
919
+ "a restatement of someone else's message. With no audience given, nothing " +
920
+ "cross-conversation is disclosed. Never quote session names to a human — they are internal.",
921
+ input_schema: S({
922
+ audienceSender: str("The person the answer is for (their sender name as the inbound item carries it)."),
923
+ audienceChannelId: str("The channel (or DM) id the answer will be posted in."),
924
+ audiencePrivate: bool("true when that channel is a 1:1 DM with audienceSender — their own asks from it may then be described back to them."),
925
+ }),
897
926
  access: "read",
898
927
  binding: { kind: "local" },
899
928
  },
@@ -3095,7 +3124,14 @@ async function screenOrgOutbound({ channel, recipient, text, agentRoot, screenIm
3095
3124
  screen = null;
3096
3125
  }
3097
3126
  }
3098
- if (typeof screen !== "function") return { allow: true, reason: "" };
3127
+ if (typeof screen !== "function") {
3128
+ // The gate module would not load. This used to return allow SILENTLY — the
3129
+ // whole outbound perimeter off, for every org tool call, with nothing in the
3130
+ // log to say so. Now it is counted, and the one rule that needs no module
3131
+ // still runs.
3132
+ await countFailOpen({ channel, stage: "org-tool-import", error: "send-gate module unavailable", agentRoot, bumpImpl });
3133
+ return lastResortScaffold(text);
3134
+ }
3099
3135
  try {
3100
3136
  const verdict = await screen({
3101
3137
  channel,
@@ -3108,19 +3144,57 @@ async function screenOrgOutbound({ channel, recipient, text, agentRoot, screenIm
3108
3144
  }
3109
3145
  return { allow: true, reason: "", text: verdict?.redactedText };
3110
3146
  } catch (err) {
3111
- try {
3112
- let bump = bumpImpl;
3113
- if (typeof bump !== "function") ({ bump } = await import("../diagnostics/counters.mjs"));
3114
- bump(
3115
- "channel.send_gate.fail_open",
3116
- { channel, stage: "org-tool", error: err && err.message },
3117
- { agentRoot },
3118
- );
3119
- } catch {
3120
- /* a counter must never crash the path it observes */
3147
+ await countFailOpen({ channel, stage: "org-tool", error: err && err.message, agentRoot, bumpImpl });
3148
+ return lastResortScaffold(text);
3149
+ }
3150
+ }
3151
+
3152
+ /** Count a send-gate fail-open. Never throws — a counter must not crash the path it observes. */
3153
+ async function countFailOpen({ channel, stage, error, agentRoot, bumpImpl }) {
3154
+ try {
3155
+ let bump = bumpImpl;
3156
+ if (typeof bump !== "function") ({ bump } = await import("../diagnostics/counters.mjs"));
3157
+ bump("channel.send_gate.fail_open", { channel, stage, error }, { agentRoot });
3158
+ } catch {
3159
+ /* a counter must never crash the path it observes */
3160
+ }
3161
+ }
3162
+
3163
+ /**
3164
+ * The fail-open FLOOR. Fail-open is the right posture for POLICY when the shared
3165
+ * gate is unreachable or threw — a transient fault must not gag the agent inside
3166
+ * its own workspace — and the wrong one for SCAFFOLD: the body that gets through
3167
+ * on the day the gate is broken is exactly the body this rule exists to stop, and
3168
+ * every recorded leak was internal.
3169
+ *
3170
+ * So this is the floor no fault lowers — the same literal set as SCAFFOLD_MARKERS
3171
+ * in lib/comms/send-gate.mjs, inlined because the branch it defends is the branch
3172
+ * where importing that module is what failed. `tool-surface.test.mjs` asserts the
3173
+ * two lists stay identical.
3174
+ */
3175
+ export const SCAFFOLD_MARKERS_FLOOR = Object.freeze([
3176
+ "<user_message>", "</user_message>", "<thinking>", "</thinking>",
3177
+ "<system-reminder>", "</system-reminder>",
3178
+ "<function_calls>", "</function_calls>", "<function_results>", "</function_results>",
3179
+ "<invoke name=", "</invoke>", "[auto-reply:", "<<<UNTRUSTED_", "<END_UNTRUSTED_",
3180
+ ]);
3181
+
3182
+ /** @returns {{allow:boolean, reason:string}} */
3183
+ export function lastResortScaffold(text) {
3184
+ const lc = String(text || "")
3185
+ .replace(/```[\s\S]*?```/g, " ")
3186
+ .replace(/```[\s\S]*$/g, " ")
3187
+ .replace(/(`+)[\s\S]*?\1/g, " ")
3188
+ .toLowerCase();
3189
+ for (const marker of SCAFFOLD_MARKERS_FLOOR) {
3190
+ if (lc.includes(marker.toLowerCase())) {
3191
+ return {
3192
+ allow: false,
3193
+ reason: `session-scaffold marker "${marker}" in the outbound body (send-gate unavailable — the scaffold floor still applies)`,
3194
+ };
3121
3195
  }
3122
- return { allow: true, reason: "" };
3123
3196
  }
3197
+ return { allow: true, reason: "" };
3124
3198
  }
3125
3199
 
3126
3200
  /** Outbound screen inputs (recipient + text) for a screened tool/method. */
@@ -3355,8 +3429,22 @@ async function executeOrgToolInner(name, input = {}, o = {}) {
3355
3429
  // Offline-capable local tools first.
3356
3430
  if (name === "org_describe") return describeProtocol(input);
3357
3431
  if (name === "session_status") {
3358
- const status = readSessionStatus(cfg.agentRoot, { now: Date.now() });
3359
- return okFrame({ ...status, agentRoot: cfg.agentRoot, summary: statusLine(status) });
3432
+ const now = Date.now();
3433
+ const status = readSessionStatus(cfg.agentRoot, { now });
3434
+ // The "what am I doing right now" block (lib/session/current-work): the
3435
+ // same fact the daemon's reply paths inject, so a front door answering
3436
+ // through tools and a daemon child answering from its prompt say the
3437
+ // same thing. Read-only; "" when there is nothing to say.
3438
+ let currentWork = "";
3439
+ try {
3440
+ const audience = {
3441
+ sender: input.audienceSender ? String(input.audienceSender) : "",
3442
+ channelId: input.audienceChannelId ? String(input.audienceChannelId) : "",
3443
+ isPrivate: input.audiencePrivate === true,
3444
+ };
3445
+ currentWork = await currentWorkBlock(cfg.agentRoot, { now, audience });
3446
+ } catch { currentWork = ""; }
3447
+ return okFrame({ ...status, agentRoot: cfg.agentRoot, summary: statusLine(status), currentWork });
3360
3448
  }
3361
3449
 
3362
3450
  // Every network tool needs a token; per §1.4 the server stays up and each
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * lib/org/ui-parity.mjs — Agent UI-parity helpers (full human-action mirror).
3
3
  *
4
- * A 1:1 mirror of the human Cohort UI: 373 named convenience wrappers over the
4
+ * A 1:1 mirror of the human Cohort UI: 374 named convenience wrappers over the
5
5
  * /v1 RPC binding, one per method behind a HUMAN SURFACE — the things a person
6
6
  * can do by clicking (charter, member, team, channel, persona, profile, sop,
7
7
  * escalation, annotation, contact, file, memory, compact, notification,
@@ -3311,6 +3311,21 @@ export function messagingSearch(params, o = {}) {
3311
3311
  return call("messaging.search", params || {}, o);
3312
3312
  }
3313
3313
 
3314
+ /**
3315
+ * WHY a message in a room got no reply from the ambient responder
3316
+ * (messaging.silences). READ-ONLY. The `responder.silence` verdicts for ONE
3317
+ * message ({ messageId }) or a channel over a window ({ channelId,
3318
+ * windowDays? }); one of the two is required. Each row carries the operator
3319
+ * reason (human_mentioned | addressed_to_human | nobody_elected |
3320
+ * no_responders) and its text. The agent-plane twin of the "Unanswered
3321
+ * messages" panel on /settings/ai — same reader, same decoder.
3322
+ * @param {object} params - { channelId?, messageId?, windowDays?, limit? }
3323
+ * @param {object} o - { base, token, fetchImpl? }
3324
+ */
3325
+ export function messagingSilences(params, o = {}) {
3326
+ return call("messaging.silences", params || {}, o);
3327
+ }
3328
+
3314
3329
  /**
3315
3330
  * The channels this seat can see (messaging.channels). READ-ONLY.
3316
3331
  * @param {object} params - { kind?, limit? }
@@ -22,11 +22,13 @@
22
22
  * be two answers within a week. So there is one, and this is a client of it.
23
23
  *
24
24
  * IDENTITY, NOT DIGESTS. We send the ask's identity (`service`, `channelId`,
25
- * `messageId`) and let the server derive the dedupe key. The hashing algorithm
26
- * therefore exists in exactly one repo, and a daemon can never compute a
27
- * different key than hq would for the same message — which is what makes
28
- * re-delivery after a dead session find the SAME row instead of opening a
29
- * second one.
25
+ * `messageId`, and for a collective ask the ADDRESSEE, which the server takes
26
+ * from the calling SEAT rather than from the wire) and let the server derive
27
+ * the dedupe key. The hashing algorithm therefore exists in exactly one repo,
28
+ * and a daemon can never compute a different key than hq would for the same
29
+ * message — which is what makes re-delivery after a dead session find the SAME
30
+ * row instead of opening a second one, and what keeps thirteen seats answering
31
+ * one broadcast on thirteen rows instead of trampling one.
30
32
  *
31
33
  * FAIL-OPEN, NEVER SILENT. Every path returns a benign `{tracked:false, reason}`
32
34
  * rather than throwing: a board write must never cost the user their reply. But
@@ -43,12 +45,15 @@
43
45
  "use strict";
44
46
 
45
47
  import { isEnabled as orgEnabled, configFromAgent, trackWork } from "./client.mjs";
48
+ import { isRollCallItem } from "./inbound/broadcast.mjs";
46
49
 
47
50
  /** The stages the ledger understands. Mirrors hq's `WorkStage`. */
48
51
  export const WORK_STAGES = ["accepted", "working", "blocked", "review", "done", "failed"];
49
52
 
50
53
  const MAX_NOTE = 4000;
51
54
  const MAX_TITLE = 200;
55
+ /** hq accepts 4000; we clip to the same 1000 `maestro board track --why` uses. */
56
+ const MAX_WHY = 1000;
52
57
  const MAX_BODY = 20000;
53
58
 
54
59
  /**
@@ -137,7 +142,10 @@ function clip(s, n) {
137
142
  * @param {boolean} [a.deferred] could this NOT be answered inside the turn?
138
143
  * (true for the session path — the same signal that fires the holding
139
144
  * message; false for the quick-reply path). The server's gate reads it.
140
- * @param {string} [a.title] row title (defaults server-side from the ask)
145
+ * @param {string} [a.title] row title, READ ONLY WHEN THE ROW OPENS (defaults
146
+ * server-side from the ask)
147
+ * @param {string} [a.why] why the row was opened → `why.reason`, also read
148
+ * only when the row opens (hq protects the key)
141
149
  * @param {string} [a.priority] P0..P4, applied when the row is created
142
150
  * @param {string} [a.detail] row detail, written once at open
143
151
  * @param {string} [a.note] a progress comment for this step
@@ -179,12 +187,35 @@ export async function recordWorkStep(a = {}) {
179
187
  // it only when the address is genuine, never guessed. A channel message
180
188
  // the agent merely overheard is not assigned work.
181
189
  directed: !!(a.item && a.item.priority_signals && a.item.priority_signals.mentions_agent),
190
+ // ── THE AUDIENCE, AS A FACT — NEVER AS A JUDGEMENT ──────────────────
191
+ //
192
+ // A broadcast is not an @mention and `mentions_agent` must never start
193
+ // claiming it is: that flag gates the daemon's own directed-message
194
+ // rules, and widening it to cover "everyone" would change what this seat
195
+ // REPLIES to, not just what it tracks. So the audience travels as its own
196
+ // structural fact — the inbound join already stamped the verdict reason
197
+ // `collective`, and `isRollCallItem` reads it back off the flattened item
198
+ // with no new plumbing.
199
+ //
200
+ // WHETHER a collective ask is WORK is emphatically not decided here.
201
+ // hq's `work/collective.ts` decides it, from the body we are sending,
202
+ // under the same gate that serves hq's own responder — so a roll-call
203
+ // ("give me an update") and a hedged aside ("take a look sometime") are
204
+ // refused by name, and a seat that got its local classifier wrong loses a
205
+ // well-shaped prompt rather than opening a row it should not have.
206
+ collective: isRollCallItem(a.item),
182
207
  authorKind: a.item && a.item.author_kind === "AI_AGENT" ? "AI_AGENT" : "HUMAN",
183
208
  deferred: a.deferred === true,
184
209
  };
185
210
  const requester = requesterMemberId(a.item);
186
211
  if (requester) params.requesterId = requester;
187
212
  if (a.title) params.title = clip(a.title, MAX_TITLE);
213
+ // `why` lands as the row's `why.reason` on hq (methods/board/track.ts →
214
+ // ledger.ts `if (!taskId)`). WRITTEN ONCE, AT OPEN: `reason` is in hq's
215
+ // PROTECTED_WHY_KEYS, so sending it on a later step is accepted and
216
+ // ignored. Same for `title`. Callers that want either must send it with
217
+ // the step that opens the row.
218
+ if (a.why) params.why = clip(a.why, MAX_WHY);
188
219
  // The classifier's urgency, already mapped onto hq's P-scale by the caller
189
220
  // (`board-mirror.mjs#boardPriority` — hq 400s the whole call on anything
190
221
  // that is not P0..P4). Only ever read when the row is CREATED.
@@ -127,6 +127,10 @@ export function readState(provider, deps) {
127
127
  consecutive429: numOr0(raw.consecutive429),
128
128
  lastBackoffMs: numOr0(raw.lastBackoffMs),
129
129
  updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : undefined,
130
+ // cohort holds only (recordCohortRefusal): what kind of hold this is.
131
+ ...(typeof raw.kind === "string" ? { kind: raw.kind } : {}),
132
+ ...(typeof raw.code === "string" ? { code: raw.code } : {}),
133
+ ...(typeof raw.holdKind === "string" ? { holdKind: raw.holdKind } : {}),
130
134
  };
131
135
  } catch {
132
136
  return { ...EMPTY_STATE };
@@ -204,6 +208,14 @@ export function recordSuccess(provider, deps) {
204
208
  // Only rewrite if there is something to clear (avoids churning the file on
205
209
  // every successful spawn).
206
210
  const prev = readState(provider, deps);
211
+ // A cohort hold is not a breaker a clean run disproves. A window/spend
212
+ // (defer_until) or credits hold says "the gateway will refuse until T": a
213
+ // session admitted before the window ran out that exits cleanly afterwards
214
+ // proves nothing about T. Only a still-live `retry` hold (concurrency,
215
+ // overload) is cleared by success; the others stand until they expire.
216
+ if (sanitizeProvider(provider) === COHORT_BACKEND && prev.openUntil > clock(deps)() && prev.holdKind !== "retry") {
217
+ return prev;
218
+ }
207
219
  if (prev.openUntil === 0 && prev.consecutive429 === 0 && prev.lastBackoffMs === 0) {
208
220
  return { ...EMPTY_STATE };
209
221
  }
@@ -309,4 +321,105 @@ export function recordUsageLimit(provider, resetAt, deps) {
309
321
  return { openUntil: next.openUntil, resetAt: typeof resetAt === "number" && resetAt > now ? resetAt : null };
310
322
  }
311
323
 
312
- export const _internals = { RATE_BASE_BACKOFF_MS, RATE_MAX_BACKOFF_MS, RATE_BACKOFF_FACTOR, RATE_USAGE_LIMIT_HOLD_MS };
324
+ // ---------------------------------------------------------------------------
325
+ // cohort-llm refusals (design §4.6 / §8.3)
326
+ // ---------------------------------------------------------------------------
327
+ //
328
+ // A cohort-llm hard refusal is NOT a provider incident, and must never be read
329
+ // as one:
330
+ // - it is keyed by the backend "cohort" (state/rate-limits/cohort.json), never
331
+ // the Anthropic provider file, and it never touches the router's backend
332
+ // breaker (lib/model-router/health.mjs) — a seat that spent its weekly
333
+ // window has not found a broken backend;
334
+ // - a limit or spend refusal holds until the refusal's own resetsAt, not a
335
+ // decorrelated-jitter backoff (retrying a drained window only burns spawns);
336
+ // - the English stderr regexes above stay exactly as they were for the
337
+ // claude/anthropic backend. Call sites check `classifyCohortRefusal` FIRST
338
+ // on a cohort-routed run, because a cohort 429 body carries the words
339
+ // "rate_limit_error" and would otherwise trip the Anthropic breaker.
340
+
341
+ import { parseRefusalBody, refusalDisposition } from "./org/quota.mjs";
342
+
343
+ /** The backend key every cohort refusal is recorded under. */
344
+ export const COHORT_BACKEND = "cohort";
345
+ /** A credits hold re-checks after this long (a top-up can land any time). */
346
+ export const COHORT_CREDITS_RECHECK_MS = num(process.env.COHORT_CREDITS_RECHECK_MS, 10 * 60 * 1000);
347
+
348
+ /**
349
+ * Find a cohort refusal in a run's output: an engine JSON result, a gateway
350
+ * error body echoed by the claude binary, or a bare refusal object. Scans
351
+ * every balanced top-level JSON object in the text. null when there is none.
352
+ * @param {string} text
353
+ * @returns {object|null} a normalised refusal
354
+ */
355
+ export function classifyCohortRefusal(text) {
356
+ if (!text || typeof text !== "string" || !/cohort_/.test(text)) return null;
357
+ for (const candidate of jsonObjects(text)) {
358
+ let o;
359
+ try { o = JSON.parse(candidate); } catch { continue; }
360
+ const direct = parseRefusalBody(o);
361
+ if (direct) return direct;
362
+ // An engine result: {is_error, error:{code,...}} or {cohort:{refusal}}.
363
+ const nested = o && typeof o === "object" ? (o.cohort && o.cohort.refusal) || o.refusal : null;
364
+ const r = nested ? parseRefusalBody({ cohort: nested }) : null;
365
+ if (r) return r;
366
+ }
367
+ return null;
368
+ }
369
+
370
+ /** Yield balanced top-level {...} substrings (string-literal aware). */
371
+ function* jsonObjects(text) {
372
+ let depth = 0, start = -1, inStr = false, esc = false;
373
+ for (let i = 0; i < text.length; i++) {
374
+ const c = text[i];
375
+ if (inStr) {
376
+ if (esc) esc = false;
377
+ else if (c === "\\") esc = true;
378
+ else if (c === '"') inStr = false;
379
+ continue;
380
+ }
381
+ if (c === '"') { if (depth > 0) inStr = true; continue; }
382
+ if (c === "{") { if (depth === 0) start = i; depth++; }
383
+ else if (c === "}" && depth > 0) { depth--; if (depth === 0 && start >= 0) { yield text.slice(start, i + 1); start = -1; } }
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Record a cohort refusal against the "cohort" backend per its disposition:
389
+ * defer_until hold until resetsAt (never shortens a longer hold)
390
+ * hold_for_credits hold for COHORT_CREDITS_RECHECK_MS, then look again
391
+ * retry hold for afterMs
392
+ * fail nothing recorded (not a timing problem)
393
+ * The Anthropic provider file and the backoff/consecutive counters are never
394
+ * touched.
395
+ *
396
+ * @param {object} refusal a normalised refusal (lib/org/quota.mjs)
397
+ * @param {object} [deps] { now?, stateDir?, agentRoot?, retryAfterMs? }
398
+ * @returns {{recorded:boolean, openUntil:number|null, disposition:object}}
399
+ */
400
+ export function recordCohortRefusal(refusal, deps) {
401
+ const now = clock(deps)();
402
+ const disposition = refusalDisposition(refusal, { now, retryAfterMs: deps && deps.retryAfterMs });
403
+ let until = null;
404
+ if (disposition.kind === "defer_until") until = disposition.until;
405
+ else if (disposition.kind === "hold_for_credits") until = now + COHORT_CREDITS_RECHECK_MS;
406
+ else if (disposition.kind === "retry") until = now + disposition.afterMs;
407
+ if (until === null || until <= now) return { recorded: false, openUntil: null, disposition };
408
+ const prev = readState(COHORT_BACKEND, deps);
409
+ // The hold that ends LAST names the kind: a short retry recorded inside a
410
+ // live multi-day window hold must not relabel it as clearable.
411
+ const prevLive = prev.openUntil > now;
412
+ const keepPrev = prevLive && prev.openUntil >= until;
413
+ const next = {
414
+ openUntil: Math.max(prev.openUntil, until),
415
+ consecutive429: prev.consecutive429,
416
+ lastBackoffMs: prev.lastBackoffMs,
417
+ kind: "cohort_refusal",
418
+ code: keepPrev && prev.code ? prev.code : disposition.code,
419
+ holdKind: keepPrev ? (prev.holdKind || "defer_until") : disposition.kind,
420
+ };
421
+ writeState(COHORT_BACKEND, next, deps);
422
+ return { recorded: true, openUntil: next.openUntil, disposition };
423
+ }
424
+
425
+ export const _internals = { RATE_BASE_BACKOFF_MS, RATE_MAX_BACKOFF_MS, RATE_BACKOFF_FACTOR, RATE_USAGE_LIMIT_HOLD_MS, jsonObjects };
@@ -229,11 +229,44 @@ function reaperAgentRoot(explicit) {
229
229
  * session either. Requires a FULL command line (`ps -eo … command`), not `comm`.
230
230
  */
231
231
  export function isDaemonSpawnedClaude(line, agentRoot) {
232
- if (!/claude/i.test(line)) return false;
233
- if (/Claude\.app|Claude Helper|watchdog|grep|ps -eo/.test(line)) return false;
234
- if (!/(^|\s)--print(\s|$)/.test(line)) return false; // interactive Claude Code is never --print
235
- if (agentRoot && !line.includes(agentRoot)) return false; // scope to THIS agent's own sessions
236
- return true;
232
+ const kind = classifyGovernedProcess(line, agentRoot);
233
+ return kind === "claude" || kind === "engine-run";
234
+ }
235
+
236
+ /** The engine's headless entry: `…/lib/engine/cli.mjs run` (the adapter always emits `run -p`). */
237
+ const ENGINE_RUN_RE = /(^|\s)\S*\/lib\/engine\/cli\.mjs\s+run(\s|$)/;
238
+ /** A long-lived engine front door sets its process title to `cohort-session:<name>` (session-runtime/runner.mjs). */
239
+ const ENGINE_SESSION_RE = /(^|\s)cohort-session:\S/;
240
+
241
+ /**
242
+ * What kind of agent process a `ps` line is, as the governor sees it. Pure.
243
+ *
244
+ * "claude" a daemon-spawned `claude --print` lane for this agent
245
+ * "engine-run" a daemon-spawned engine lane for this agent: `node …/lib/engine/cli.mjs run -p …`
246
+ * (the engine twin of `claude --print`; the agent root is on its argv the same way,
247
+ * via `--mcp-config <AGENT_ROOT>/.mcp.json`)
248
+ * "engine-session" a long-lived engine front door or peer (`cohort-session:<name>`)
249
+ * null anything else — including a human's interactive Claude Code session
250
+ *
251
+ * The concurrency ceiling and the reaper count "claude" and "engine-run" only. An
252
+ * "engine-session" is the engine twin of an interactive Claude Code session (a front
253
+ * door `claude --name <first>-main`, or a peer): those are never `--print`, so the
254
+ * governor has never counted or reaped them, and a long-lived front door must not be
255
+ * killed at REAP_SESSION_MS. Its title also carries no agent root to scope by.
256
+ *
257
+ * @param {string} line a FULL `ps -eo …,command` line
258
+ * @param {string} [agentRoot]
259
+ * @returns {"claude"|"engine-run"|"engine-session"|null}
260
+ */
261
+ export function classifyGovernedProcess(line, agentRoot) {
262
+ const l = String(line || "");
263
+ if (ENGINE_SESSION_RE.test(l)) return "engine-session";
264
+ if (/Claude\.app|Claude Helper|watchdog|grep|ps -eo/.test(l)) return null;
265
+ if (agentRoot && !l.includes(agentRoot)) return null; // scope to THIS agent's own sessions
266
+ if (ENGINE_RUN_RE.test(l) && /(^|\s)(-p|--print)(\s|$)/.test(l)) return "engine-run";
267
+ if (!/claude/i.test(l)) return null;
268
+ if (!/(^|\s)--print(\s|$)/.test(l)) return null; // interactive Claude Code is never --print
269
+ return "claude";
237
270
  }
238
271
 
239
272
  export function liveClaudeStats(opts = {}) {
@@ -248,7 +281,9 @@ export function liveClaudeStats(opts = {}) {
248
281
  // FULL command (not comm) so we can require `--print` + the agent root — i.e.
249
282
  // count ONLY this daemon's own sessions, never a human's interactive Claude
250
283
  // Code session (which would otherwise inflate the count and get reaped).
251
- const out = execFileSync("/bin/ps", ["-eo", "pid,etime,rss,command"], { encoding: "utf-8", timeout: 4000 });
284
+ const out = typeof opts.psImpl === "function"
285
+ ? String(opts.psImpl())
286
+ : execFileSync("/bin/ps", ["-eo", "pid,etime,rss,command"], { encoding: "utf-8", timeout: 4000 });
252
287
  let count = 0;
253
288
  let rssKb = 0;
254
289
  let stale = 0;