@cohortapp/agent-sdk 2.17.0 → 2.18.4

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