@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
@@ -18,6 +18,7 @@
18
18
 
19
19
  import { resolve, join } from "path";
20
20
  import { readdirSync, readFileSync, renameSync, mkdirSync, appendFileSync, writeFileSync, unlinkSync } from "fs";
21
+ import { createRequire } from "module";
21
22
 
22
23
  // Load .env before anything else. dotenv is present on every agent machine but
23
24
  // is not a packaged dependency, so we import it dynamically + best-effort: this
@@ -57,7 +58,14 @@ import { makeInboxScanPoller } from "../poller/inbox-scan-poller.mjs";
57
58
  import { existsSync as _existsSync } from "fs";
58
59
  import { isPriorityItem } from "../poller/utils.mjs";
59
60
  import { classifyItem, isDirectedAtAgent } from "./classifier.mjs";
60
- import { dispatch, getStatus, availableSlots, canDispatchBacklog, resetActiveSessions, yieldBacklogForReactive } from "./dispatcher.mjs";
61
+ // A human's roll-call to the whole room ("everyone, status?"). Every addressed
62
+ // seat answers for itself — so it is EXEMPT from the single-winner election,
63
+ // gets a deterministic classification (no LLM triage), no holding ack, and a
64
+ // per-seat jitter so thirteen answers do not land in the same second.
65
+ import { isRollCallItem, rollCallJitterMs } from "../../lib/org/inbound/broadcast.mjs";
66
+ import { itemCollectiveIntent } from "../../lib/org/inbound/collective.mjs";
67
+ import { dispatch, getStatus, availableSlots, canDispatchBacklog, resetActiveSessions, yieldBacklogForReactive, linkActiveSessionBoard } from "./dispatcher.mjs";
68
+ import { boardItemRefFor } from "../../lib/session/current-work.mjs";
61
69
  import { markReactiveStart, markReactiveEnd } from "../../lib/reactive-gate.mjs";
62
70
  import { buildPrompt } from "./prompt-builder.mjs";
63
71
  import { sendQuickResponse, sendHoldingMessage, isQuickReply } from "./responder.mjs";
@@ -93,7 +101,7 @@ const RUNG_MECHANISM = Object.fromEntries(RUNGS.map((r) => [r.id, r.mechanism]))
93
101
  // work and land it in the org's shared, ACL'd store so the fleet's memory
94
102
  // compounds. Fail-open + best-effort — never blocks or throws into the hot path;
95
103
  // local SQLite/interaction logs remain the fail-open cache.
96
- import { isEnabled as orgEnabled, loadOrgConfig, configFromAgent, electResponder, fetchSelfProfile, listMineResult as orgListMineResult } from "../../lib/org/client.mjs";
104
+ import { isEnabled as orgEnabled, loadOrgConfig, configFromAgent, electResponder, fetchSelfProfile, listMineResult as orgListMineResult, call as orgCall } from "../../lib/org/client.mjs";
97
105
  // board.mine cache (design §3.5, WP-M6): the daemon writes state/org/board-mine.json
98
106
  // every 5 min; the SessionStart primer, session_status and `maestro board mine`
99
107
  // read it. The when/what is pure (lib/org/board-mine-cache.mjs); the I/O is here.
@@ -109,6 +117,9 @@ import { recordWorkStep as orgRecordWorkStep, cohortChannelId, cohortMessageId }
109
117
  // it: a holding "let me look into it" must not go out when the reply itself can
110
118
  // never be generated (the storm's other half — a promise the seat cannot keep).
111
119
  import { resolveClaudeBin, isSafeExecutable } from "../../lib/claude-bin.mjs";
120
+ // W4 integration (conformance row 1): an engine-cohort seat never spawns claude, so its
121
+ // presence check is the engine entry point, not the claude binary.
122
+ import { readSeatEngine as _seatEngineForAvailability, seatGenerationAvailable as _seatGenerationAvailable, ENGINE_CLI_PATH as _ENGINE_CLI_PATH } from "../../lib/runtime/adapter.mjs";
112
123
  import { boardPriority } from "./board-mirror.mjs";
113
124
  // Observability spine (WS — diagnostics). mintTraceId + withTrace give each
114
125
  // inbound item ONE trace_id that deep callees inherit via AsyncLocalStorage;
@@ -139,6 +150,7 @@ import { processOne } from "../../lib/execution/pipeline.mjs";
139
150
  // gate is pure (lib/session/frontdoor.mjs); the assurance sweep reopens a
140
151
  // session claim that was neither replied nor done within 20 min.
141
152
  import { makeFrontDoorGate } from "../../lib/session/frontdoor.mjs";
153
+ import { CADENCE_REGISTRY } from "./cadence-handlers.mjs";
142
154
  import { sweepSessionInboxForDaemon } from "../../lib/session/inbox-claims.mjs";
143
155
  import { defaultEffects, scheduleToQueue } from "../../lib/execution/effects.mjs";
144
156
  import { loadReactObligations } from "../../lib/execution/match.mjs";
@@ -459,13 +471,17 @@ export function enrichItem(item, service) {
459
471
  * @returns {boolean}
460
472
  */
461
473
  function claudeAvailable() {
462
- try {
463
- const bin = resolveClaudeBin();
464
- if (!bin || bin === "claude") return false; // bare fallback = nothing found
465
- return isSafeExecutable(bin);
466
- } catch {
467
- return false;
468
- }
474
+ let engine = "claude";
475
+ try { engine = _seatEngineForAvailability(AGENT_REPO_DIR); } catch { engine = "claude"; /* unreadable config: the default engine */ }
476
+ return _seatGenerationAvailable({
477
+ engine,
478
+ claudeBinOk: () => {
479
+ const bin = resolveClaudeBin();
480
+ if (!bin || bin === "claude") return false; // bare fallback = nothing found
481
+ return isSafeExecutable(bin);
482
+ },
483
+ engineCliPresent: () => _existsSync(_ENGINE_CLI_PATH),
484
+ });
469
485
  }
470
486
 
471
487
  /**
@@ -553,15 +569,113 @@ const FASTPATH_WORK_RE = /\b(analy[sz]e|create|build|writ(e|ing)|draft|prepare|r
553
569
  // A DM that reads like a question / greeting / quick ask — safe to answer directly.
554
570
  const FASTPATH_ASK_RE = /(\?|^(what|how|when|where|who|why|which|is |are |was |were |do |did |does |can |could |would |will |have |has |should |hey|hi |hello|thanks|thank you|yo |quick (q|question)|you (there|around|up)))/i;
555
571
 
572
+ /**
573
+ * Resolve the sender_privilege band handed to the classifier.
574
+ *
575
+ * A populated band from upstream wins. The inbox-item writer defaults a
576
+ * missing band to the string "unknown" (lib/channels/inbox-item.mjs), so
577
+ * "unknown" is treated as a miss, and only then does the CEO priority signal
578
+ * decide between "ceo" and "unknown".
579
+ *
580
+ * The previous inline form was
581
+ * `item.sender_privilege || item.priority_signals?.from_ceo ? "ceo" : "unknown"`
582
+ * which parses as `(a || b) ? "ceo" : "unknown"`: every non-empty band —
583
+ * including the default "unknown" — resolved to "ceo", and the classifier
584
+ * escalated the item as if the CEO had sent it.
585
+ *
586
+ * @param {object} item
587
+ * @returns {string}
588
+ */
589
+ export function resolveSenderPrivilege(item) {
590
+ const band = item?.sender_privilege;
591
+ if (band && band !== "unknown") return band;
592
+ return item?.priority_signals?.from_ceo ? "ceo" : "unknown";
593
+ }
594
+
556
595
  /**
557
596
  * Decide whether a directed DM is a "plainly simple question" that can skip the
558
597
  * LLM classifier and be answered directly. Returns a default classResult (the
559
598
  * quick-reply shape) when it qualifies, else null (→ full classify path).
560
599
  * Deliberately CONSERVATIVE: 1:1 DM, short, question-shaped, no work-verbs.
561
- * `deps.fastPathClassify` can override (tests); `deps.disableFastPath` disables.
600
+ * `deps.fastPathClassify` can override (tests); `deps.disableFastPath` (or
601
+ * DAEMON_DISABLE_FASTPATH=1) disables the DM heuristic — NOT the roll-call
602
+ * branch, which is deterministic and is checked before the switch.
603
+ */
604
+ /**
605
+ * The identity string that makes this seat's roll-call jitter DIFFERENT from
606
+ * every other seat's: the org member slug (COHORT_AGENT_ID / config
607
+ * org.cohort.agentId), falling back to the configured first name. Empty only
608
+ * on an unconfigured seat, where every seat would share a key — harmless
609
+ * (they still wait), just not spread.
562
610
  */
611
+ function rollCallSeatKey(deps = {}) {
612
+ try {
613
+ const cfg = deps.orgCfg !== undefined ? deps.orgCfg : daemonOrgCfg();
614
+ return String(
615
+ process.env.COHORT_AGENT_ID ||
616
+ (cfg && cfg.org && cfg.org.cohort && cfg.org.cohort.agentId) ||
617
+ (loadAgent().firstName || ""),
618
+ ).trim().toLowerCase();
619
+ } catch {
620
+ return "";
621
+ }
622
+ }
623
+
563
624
  export function fastPathClassify(item, isDm, deps = {}) {
564
625
  if (deps && typeof deps.fastPathClassify === "function") return deps.fastPathClassify(item, isDm);
626
+ // A COLLECTIVE ADDRESS is classified deterministically. The inbound join
627
+ // already proved the four broadcast conditions (human author, my room, not
628
+ // threaded, collective address) before it stamped the kind, and the answer
629
+ // shape is fixed (≤ 5 lines, for myself). Sending it through the LLM
630
+ // classifier would only add ~5-8 s and a chance of "archive" — the exact
631
+ // silence this surface exists to end, and one that would now also swallow a
632
+ // work ask carrying a deadline. `answerable + respond` routes it to the
633
+ // in-process quick-reply responder, which is where the collective block is
634
+ // injected.
635
+ //
636
+ // This branch sits ABOVE the fast-path kill switches on purpose. The
637
+ // switches (`deps.disableFastPath`, DAEMON_DISABLE_FASTPATH=1) exist to
638
+ // force a plain DM through the full LLM classify path when the heuristic
639
+ // regexes below misjudge one; a roll-call is not a heuristic call — its
640
+ // conditions were proven upstream — so routing it to the classifier under
641
+ // the switch would re-open the "archive" hole the branch closes. Pinned by
642
+ // the "ROLL-CALL: survives the fast-path kill switch" test.
643
+ if (isRollCallItem(item)) {
644
+ // WHICH KIND of collective address this is (`lib/org/inbound/collective.mjs`,
645
+ // the twin of hq's `work/collective.ts`). It does NOT change the ROUTE —
646
+ // every one of the three still takes the quick path — it changes the
647
+ // prompt block and the summary line.
648
+ //
649
+ // ── WHY A WORK BROADCAST DOES NOT SPAWN THIRTEEN SESSIONS ─────────────
650
+ //
651
+ // "Everyone, please review the binder by Friday" is real work for thirteen
652
+ // seats, and the tempting move is to dispatch a session per seat. That is
653
+ // thirteen fifteen-to-forty-minute Claude runs fired by one sentence, on
654
+ // thirteen machines, inside one poll tick — the storm shape this daemon has
655
+ // been burned by before, bought for an ask with a Friday deadline.
656
+ //
657
+ // The proportionate answer is the one the org already has: the quick reply
658
+ // says what this seat will do and by when, and the SAME quick path's
659
+ // `trackWorkStep` opens the seat's OWN board row (hq's gate accepts it now
660
+ // that the audience travels as a fact — see lib/org/work-ledger.mjs). The
661
+ // row is then pulled through the ordinary board-work lane, which is rate-
662
+ // controlled, resumable, and visible. A broadcast makes work VISIBLE AND
663
+ // OWNED here; it does not make thirteen machines start at once.
664
+ const intent = itemCollectiveIntent(item, isRollCallItem) || "roll-call";
665
+ const label = intent === "work" ? "Everyone-work" : intent === "ambient" ? "Everyone" : "Roll-call";
666
+ return {
667
+ priority: "normal",
668
+ action: "respond",
669
+ model: "sonnet",
670
+ summary: `${label}: ${String(item.content || item.subject || "").slice(0, 80)}`,
671
+ category: "action_required",
672
+ directed_at_agent: true,
673
+ answerable: true,
674
+ fast_path: true,
675
+ roll_call: true,
676
+ collective_intent: intent,
677
+ };
678
+ }
565
679
  if (deps && deps.disableFastPath) return null;
566
680
  if (process.env.DAEMON_DISABLE_FASTPATH === "1") return null;
567
681
  if (!isDm) return null; // 1:1 DMs only (never group/ambient)
@@ -627,12 +741,12 @@ export async function answerItem(item, service, itemId, trace_id, deps = {}, rou
627
741
  let classResult = fastPathClassify(item, isDm, deps);
628
742
  if (classResult) {
629
743
  recordClassification(true);
630
- console.log(`[daemon] fast-path DM — skipped classify (${classResult.model}): ${classResult.summary}`);
744
+ console.log(`[daemon] fast-path ${classResult.roll_call ? "roll-call" : "DM"} — skipped classify (${classResult.model}): ${classResult.summary}`);
631
745
  } else {
632
746
  // Classify via Haiku CLI
633
747
  classResult = await _classify({
634
748
  sender: item.sender || "unknown",
635
- sender_privilege: item.sender_privilege || item.priority_signals?.from_ceo ? "ceo" : "unknown",
749
+ sender_privilege: resolveSenderPrivilege(item),
636
750
  channel: item.channel || "unknown",
637
751
  service,
638
752
  content: item.content || item.subject || "",
@@ -681,7 +795,15 @@ export async function answerItem(item, service, itemId, trace_id, deps = {}, rou
681
795
  // respond-to-all. The directed fast path is untouched, so an org outage can
682
796
  // never drop a genuinely-directed DM. Slack keeps its own local gate below,
683
797
  // where no org roster exists to elect against.
684
- if (service === "cohort" && !isDm && !isDirectedAtAgent(item)) {
798
+ //
799
+ // A ROLL-CALL IS EXEMPT. The election elects ONE responder for an ambient
800
+ // message; a human's "everyone, status?" is the opposite — every addressed
801
+ // member owes its own line, and reducing it to a single winner would leave
802
+ // twelve seats silent on a message that named all thirteen. The inbound
803
+ // join proved the address (human author, my room, collective marker), so
804
+ // this is not ambient traffic and there is nothing to hold an election over.
805
+ const rollCall = isRollCallItem(item);
806
+ if (service === "cohort" && !isDm && !rollCall && !isDirectedAtAgent(item)) {
685
807
  const verdict = await _electResponderVerdict(item);
686
808
  if (!verdict.electedMe) {
687
809
  console.log(`[daemon] Election: not elected to respond (${verdict.reason}) — staying silent for ${item.sender} in ${item.channel}`);
@@ -834,6 +956,26 @@ export async function answerItem(item, service, itemId, trace_id, deps = {}, rou
834
956
  // path does not have, so a quick reply would be a wrong answer delivered
835
957
  // fast. With no routing decision (an item the ladder could not place) this
836
958
  // is exactly today's behaviour.
959
+ // ROLL-CALL RATE SHAPING. Thirteen seats received the same event in the
960
+ // same poll tick; without this their answers land in the same second and
961
+ // the room reads as a machine chorus. Each seat waits a deterministic slice
962
+ // of a 0-20 s window keyed on (seat, channel, message) — the same seat
963
+ // re-answering after a crash waits the same amount, and no two seats share
964
+ // a key. Applied to BOTH reply paths (quick reply here, session below).
965
+ let rollCallDelayMs = 0;
966
+ if (rollCall) {
967
+ rollCallDelayMs = rollCallJitterMs({
968
+ seat: rollCallSeatKey(deps),
969
+ channelId: item.channel_id || item.channel || "",
970
+ messageId: cohortMessageId(item) || item.id || "",
971
+ });
972
+ const _sleep = deps.sleep || ((ms) => new Promise((r) => setTimeout(r, ms)));
973
+ if (rollCallDelayMs > 0) {
974
+ console.log(`[daemon] roll-call: waiting ${rollCallDelayMs}ms before answering in ${item.channel} (rate shaping)`);
975
+ await _sleep(rollCallDelayMs);
976
+ }
977
+ }
978
+
837
979
  if (_isQuickReply(classResult, routed)) {
838
980
  console.log(`[daemon] Quick reply path for ${item.sender} (${classResult.model})`);
839
981
  // Free the subscription NOW: evict any already-running backlog sessions
@@ -859,6 +1001,29 @@ export async function answerItem(item, service, itemId, trace_id, deps = {}, rou
859
1001
  // created and closed in the same instant claims the ask was delivered by
860
1002
  // a message; leaving it open is the truthful reading, and the stable
861
1003
  // askKey means whatever really finishes it closes THIS row.
1004
+ //
1005
+ // THIS IS ALSO THE COLLECTIVE PATH. A work-shaped broadcast is answered
1006
+ // here and gets its row here — one per addressee, keyed on the calling
1007
+ // seat server-side, tagged into the channel exactly once across all of
1008
+ // them. A roll-call reaches the same call and is refused by name
1009
+ // (`collective-roll-call`), which is the gate working, not a miss.
1010
+ //
1011
+ // ── AND IT IS ONLY ONE OF THE TWO PLANES. SAY SO. ─────────────────
1012
+ //
1013
+ // Everything in this function is downstream of `answerItem`, which is
1014
+ // only ever reached through `pollService` — and `pollService` returns 0
1015
+ // at its third line (`if (!fd.dispatch) return 0`) whenever
1016
+ // `makeFrontDoorGate` says a live main session owns this service's
1017
+ // inbox. That is the NORMAL state of a healthy seat, and it is the same
1018
+ // fork the 13-day board blackout hid behind: a test that calls
1019
+ // `answerItem` directly is green over a path production does not take.
1020
+ //
1021
+ // On a front-door seat the item never reaches here at all. It becomes
1022
+ // an `inbound` feed line (lib/session/feed-core.mjs), which is why that
1023
+ // line now carries `collective` — the session reads the same verdict
1024
+ // this branch computed and opens its own row per `inbound-triage`
1025
+ // §"Addressed to the whole room". Two planes, one classifier, and
1026
+ // neither of them silently inert.
862
1027
  void trackWorkStep({
863
1028
  item,
864
1029
  stage: "accepted",
@@ -1038,7 +1203,9 @@ export async function answerItem(item, service, itemId, trace_id, deps = {}, rou
1038
1203
  // (the old board mirror) came to exist. Chained rather than fired in
1039
1204
  // parallel because `triage` is not terminal: an `accepted` that commits
1040
1205
  // AFTER a `working` drags the row back out of `running` for good.
1041
- void trackWorkStep({
1206
+ const _trackWorkStep = deps.trackWorkStep || trackWorkStep;
1207
+ const _linkSessionToBoardRow = deps.linkSessionToBoardRow || linkSessionToBoardRow;
1208
+ void _trackWorkStep({
1042
1209
  item,
1043
1210
  stage: "accepted",
1044
1211
  deferred: true,
@@ -1046,13 +1213,23 @@ export async function answerItem(item, service, itemId, trace_id, deps = {}, rou
1046
1213
  note: classResult && classResult.summary ? `Picked this up. ${classResult.summary}` : "Picked this up — starting work now.",
1047
1214
  source: { service, trace_id, classified: String(classResult && classResult.action) },
1048
1215
  }).then(() =>
1049
- trackWorkStep({
1216
+ _trackWorkStep({
1050
1217
  item,
1051
1218
  stage: "working",
1052
1219
  deferred: true,
1053
1220
  source: { service, trace_id },
1054
1221
  }),
1055
- );
1222
+ ).then((res) => {
1223
+ // THE LINK BACK. hq's row id reaches the live session's active.json entry,
1224
+ // so a "what are you doing right now?" reply can cite the board item a
1225
+ // person could open — instead of a session that knows it is working and
1226
+ // a board that knows what on, with nothing joining the two.
1227
+ _linkSessionToBoardRow(item, res);
1228
+ }).catch((err) => {
1229
+ // The chain is fire-and-forget; a rejection here must be a log line, not
1230
+ // an unhandled rejection that takes the daemon down.
1231
+ console.warn(`[board] track chain failed: ${err && err.message}`);
1232
+ });
1056
1233
 
1057
1234
  // Build prompt with holding message context and dispatch
1058
1235
  const prompt = await buildPrompt(item, classResult, {
@@ -1796,6 +1973,92 @@ export function _resetDaemonOrgCfgCache() {
1796
1973
  _cachedDaemonOrgCfg = undefined;
1797
1974
  }
1798
1975
 
1976
+ /**
1977
+ * Startup guard: a cadence with a producer but no prompt file dead-letters on
1978
+ * every tick, identically, forever. A cadence resolves to `mode: escalate` /
1979
+ * `guarded` with a prompt path via config/.cadence-registry.json, or via the
1980
+ * "conventional" schedules/triggers/<id>.md that cadence-consumer.mjs's
1981
+ * getCadenceDef() falls back to for an unknown id; when that file is absent
1982
+ * the tick can never be rendered, and the DLQ refills — 42 times on the seat
1983
+ * this was written on (ib-2026-09-03-eight-cadences-fire-with-no-prompt-file).
1984
+ * Ported from that seat's fork 2026-09-22.
1985
+ *
1986
+ * CADENCE_REGISTRY (the hardcoded, code-reviewed map in cadence-handlers.mjs)
1987
+ * is deliberately EXCLUDED: getCadenceDef() checks it first and it wins, so an
1988
+ * entry there with no `prompt` (skill-curator, nightly-backup — inline handlers
1989
+ * or guards documented as "never escalates") is by design, not a defect.
1990
+ * Checking config/cadences.yaml directly would false-positive on exactly these,
1991
+ * since that compiled file still carries a `prompt:` for every id.
1992
+ *
1993
+ * Sources checked (both are inputs to what actually resolves at tick time):
1994
+ * - config/.cadence-registry.json — the compiled config-driven registry
1995
+ * getCadenceDef() reads once CADENCE_REGISTRY misses.
1996
+ * - config/plan.yaml — active SCHEDULE obligations feed resolveCadences()'s
1997
+ * obligations leg directly (lib/cadences.mjs), independent of the JSON
1998
+ * registry, which is how a cadence kept firing with no plist at all.
1999
+ *
2000
+ * Fail-open throughout: a read/parse failure logs a warning and is skipped,
2001
+ * never blocks daemon startup. Logs once, at boot; returns the ids so a test
2002
+ * (and doctor) can read the answer without the log.
2003
+ * @param {{agentRoot?:string, registry?:object}} [deps] test seams
2004
+ * @returns {string[]} cadence ids whose prompt file is missing
2005
+ */
2006
+ export function assertCadencePromptsExist(deps = {}) {
2007
+ const root = deps.agentRoot || AGENT_REPO_DIR;
2008
+ const hardcoded = deps.registry || CADENCE_REGISTRY;
2009
+ const candidates = new Map(); // id -> prompt path (repo-relative)
2010
+
2011
+ try {
2012
+ const registryPath = join(root, "config", ".cadence-registry.json");
2013
+ if (_existsSync(registryPath)) {
2014
+ const registry = JSON.parse(readFileSync(registryPath, "utf-8")) || {};
2015
+ for (const [id, entry] of Object.entries(registry)) {
2016
+ if (hardcoded[id]) continue; // hardcoded handler wins — trust it
2017
+ candidates.set(id, (entry && entry.prompt) || `schedules/triggers/${id}.md`);
2018
+ }
2019
+ }
2020
+ } catch (err) {
2021
+ console.warn(`[daemon] cadence prompt assertion: could not read .cadence-registry.json (${err && err.message ? err.message : err})`);
2022
+ }
2023
+
2024
+ try {
2025
+ const planPath = join(root, "config", "plan.yaml");
2026
+ if (_existsSync(planPath)) {
2027
+ const plan = yamlLoadSync(readFileSync(planPath, "utf-8"));
2028
+ for (const ob of Array.isArray(plan && plan.obligations) ? plan.obligations : []) {
2029
+ if (ob.kind !== "SCHEDULE" || !ob.cadence_id) continue;
2030
+ if (ob.status && ob.status !== "active") continue;
2031
+ if (hardcoded[ob.cadence_id]) continue; // hardcoded handler wins — trust it
2032
+ candidates.set(ob.cadence_id, ob.prompt || `schedules/triggers/${ob.cadence_id}.md`);
2033
+ }
2034
+ }
2035
+ } catch (err) {
2036
+ console.warn(`[daemon] cadence prompt assertion: could not read plan.yaml (${err && err.message ? err.message : err})`);
2037
+ }
2038
+
2039
+ const missing = [];
2040
+ for (const [id, prompt] of candidates) {
2041
+ if (!_existsSync(join(root, prompt))) missing.push(id);
2042
+ }
2043
+ if (missing.length > 0) {
2044
+ console.warn(
2045
+ `[daemon] ${missing.length} cadence(s) have a producer but no prompt file — every tick will dead-letter: ${missing.sort().join(", ")}`
2046
+ );
2047
+ }
2048
+ return missing;
2049
+ }
2050
+
2051
+ let _yamlLoadSyncCache = null;
2052
+ function yamlLoadSync(text) {
2053
+ if (!_yamlLoadSyncCache) {
2054
+ // require() rather than a dynamic import keeps this assertion synchronous
2055
+ // so it can run inline at the top of main(), before the first poll —
2056
+ // js-yaml ships a CJS build so this resolves cleanly.
2057
+ _yamlLoadSyncCache = createRequire(import.meta.url)("js-yaml");
2058
+ }
2059
+ return _yamlLoadSyncCache.load(text);
2060
+ }
2061
+
1799
2062
  /**
1800
2063
  * Refresh state/org/board-mine.json from hq `board.mine` when the cache is
1801
2064
  * 5 min old (or absent). Fail-open end to end: an un-enrolled seat writes
@@ -1987,6 +2250,87 @@ export async function trackWorkStep(a = {}) {
1987
2250
  }
1988
2251
  }
1989
2252
 
2253
+ /**
2254
+ * Join hq's board row to the daemon session working the same ask (dispatcher
2255
+ * `linkActiveSessionBoard`), so the current-work block names the item. Pure
2256
+ * over its arguments except for the one active.json rewrite the dispatcher
2257
+ * does; fail-open; `linkImpl` is the test seam.
2258
+ *
2259
+ * @param {object} item the inbox item the row was tracked for
2260
+ * @param {object} res the `working` trackWorkStep result
2261
+ * @param {{linkImpl?:Function}} [deps]
2262
+ * @returns {{linked:boolean, sessionId:string|null, parked:boolean}|null} null when there was nothing to link
2263
+ */
2264
+ export function linkSessionToBoardRow(item, res, deps = {}) {
2265
+ try {
2266
+ if (!res || !res.tracked || !res.taskId) return null;
2267
+ const channelId = cohortChannelId(item);
2268
+ const messageId = cohortMessageId(item);
2269
+ if (!channelId || !messageId) return null;
2270
+ const impl = deps.linkImpl || linkActiveSessionBoard;
2271
+ const out = impl({ channelId, messageId }, { taskId: res.taskId, col: res.col || null });
2272
+ if (out && (out.linked || out.parked)) {
2273
+ console.log(`[board] task ${res.taskId} ${out.linked ? `linked to session ${out.sessionId}` : "parked until its session registers"}`);
2274
+ }
2275
+ return out || null;
2276
+ } catch (err) {
2277
+ console.warn(`[board] link to session failed: ${err && err.message}`);
2278
+ return null;
2279
+ }
2280
+ }
2281
+
2282
+ /**
2283
+ * WORK VISIBILITY for a BACKLOG session that is for a BOARD ITEM. An inbox ask
2284
+ * gets accepted→working→done/failed through `trackWorkStep` (board.track,
2285
+ * keyed on the message). A backlog row the execution ladder queued for a
2286
+ * `task_assigned` / `task_comment` event is about an item that ALREADY EXISTS
2287
+ * on a board and was assigned to this agent — `board.track` has no message to
2288
+ * key on and `board.claim` refuses an item that is already assigned. What the
2289
+ * item's thread CAN carry is a comment: one when the session starts on it,
2290
+ * one when the session ends, each naming the session ref so the row and the
2291
+ * session log join. `board.comment` dedupes nothing server-side, so the
2292
+ * idempotency key is (item, stage, session).
2293
+ *
2294
+ * Fire-and-forget, fail-open, never silent — same contract as trackWorkStep.
2295
+ *
2296
+ * @param {object} a - { item (queue row), stage: "working"|"done"|"failed", sessionRef, note?, cfg?, callImpl? }
2297
+ * @returns {Promise<{tracked:boolean, reason:string, itemId?:string|null}>}
2298
+ */
2299
+ export async function trackBoardItemStep(a = {}) {
2300
+ try {
2301
+ const ref = boardItemRefFor(a.item);
2302
+ if (!ref) return { tracked: false, reason: "not-a-board-item" };
2303
+ const stage = String(a.stage || "").trim();
2304
+ if (!["working", "done", "failed"].includes(stage)) return { tracked: false, reason: "bad-stage" };
2305
+ const cfg = a.cfg !== undefined ? a.cfg : daemonOrgCfg();
2306
+ if (!orgEnabled(cfg)) return { tracked: false, reason: "org-disabled" };
2307
+ const conn = configFromAgent(cfg);
2308
+ const sessionRef = String(a.sessionRef || "").trim() || "unknown";
2309
+ const lead =
2310
+ stage === "working" ? "Started on this." :
2311
+ stage === "done" ? "Finished a working session on this." :
2312
+ "A working session on this ended without completing; it is still owed.";
2313
+ const body = `${lead}${a.note ? ` ${String(a.note).slice(0, 2000)}` : ""} (session ${sessionRef})`;
2314
+ const impl = a.callImpl || orgCall;
2315
+ const res = await impl("board.comment", { itemId: ref.itemId, body }, {
2316
+ base: conn.base,
2317
+ token: conn.token,
2318
+ fetchImpl: a.fetchImpl,
2319
+ idempotencyKey: `board.comment:${ref.itemId}:${stage}:${sessionRef}`,
2320
+ });
2321
+ if (!res || res.error) {
2322
+ const reason = res && res.error ? `${res.error.code || "error"}: ${res.error.message || ""}` : "no-response";
2323
+ console.log(`[board] item ${ref.itemId} ${stage} not tracked: ${reason.slice(0, 200)}`);
2324
+ return { tracked: false, reason: reason.slice(0, 200), itemId: ref.itemId };
2325
+ }
2326
+ console.log(`[board] item ${ref.itemId} ${stage} — commented (session ${sessionRef})`);
2327
+ return { tracked: true, reason: "ok", itemId: ref.itemId };
2328
+ } catch (err) {
2329
+ console.warn(`[board] item track failed (${a.stage}): ${err && err.message}`);
2330
+ return { tracked: false, reason: "error" };
2331
+ }
2332
+ }
2333
+
1990
2334
  // A distillation longer than this is truncated before it lands in the store —
1991
2335
  // the shared memory holds a salient one-liner, never a transcript.
1992
2336
  const SESSION_DISTILL_MAX_CHARS = 400;
@@ -2224,9 +2568,34 @@ export function recoverInFlight() {
2224
2568
  // Backlog Sweep
2225
2569
  // ---------------------------------------------------------------------------
2226
2570
 
2227
- async function sweepBacklog() {
2228
- const slots = availableSlots();
2571
+ /**
2572
+ * Drain the backlog queues into sessions. `deps` are test seams only —
2573
+ * `{dispatch, buildPrompt, trackBoardItemStep, availableSlots}` — so the
2574
+ * board-item bookkeeping around a dispatch can be pinned without a child
2575
+ * process or an hq.
2576
+ */
2577
+ export async function sweepBacklog(deps = {}) {
2578
+ const dispatchImpl = deps.dispatch || dispatch;
2579
+ const buildPromptImpl = deps.buildPrompt || buildPrompt;
2580
+ const trackBoardItem = deps.trackBoardItemStep || trackBoardItemStep;
2581
+ const slots = (deps.availableSlots || availableSlots)();
2229
2582
  if (slots <= 0) return; // No capacity
2583
+ // FRONT DOOR (design §3.1, ported from a seat fork 2026-09-22): while the
2584
+ // main session is live it works the board itself (main-session skill, idle
2585
+ // loop). A daemon backlog session on the same rows holds the seat's pen and
2586
+ // voice beside it — four such sessions edited a pack and DM'd a dark seat on
2587
+ // one seat on 9 Sep 2026 while the front door was open. pollService already
2588
+ // steps back from the INBOX on this gate; the backlog sweep is the other
2589
+ // lane that spawns in the seat's name, and it must step back on the same
2590
+ // signal. Fail-open on a reader error (the gate's worst case is the old
2591
+ // behaviour: the daemon sweeps). `deps.frontDoorGate` is the test seam.
2592
+ {
2593
+ const fd = (deps.frontDoorGate || _frontDoorGate).check("cohort");
2594
+ if (!fd.dispatch) {
2595
+ if (fd.changed) console.log(`[daemon] front door live (${fd.reason}) — backlog belongs to the main session; sweep skipped`);
2596
+ return;
2597
+ }
2598
+ }
2230
2599
 
2231
2600
  try {
2232
2601
  const queueDir = join(AGENT_REPO_DIR, "state", "queues");
@@ -2352,7 +2721,7 @@ async function sweepBacklog() {
2352
2721
  );
2353
2722
  }
2354
2723
 
2355
- const prompt = await buildPrompt(null, classResult, {
2724
+ const prompt = await buildPromptImpl(null, classResult, {
2356
2725
  type: "backlog",
2357
2726
  queueItem,
2358
2727
  });
@@ -2361,8 +2730,26 @@ async function sweepBacklog() {
2361
2730
  // they get the same outcome discipline. This onClose does NOT touch the
2362
2731
  // backlog finalize path (the session updates its own queue item) — it is
2363
2732
  // purely the outcome sweep, gated off by default.
2364
- dispatch(prompt, queueItem, classResult, "backlog", {
2733
+ // "Started on this" is said when a child has STARTED — from the
2734
+ // dispatcher's `onSpawn`, with the session id it minted — never at the
2735
+ // call site: `dispatch()` returns silently on rate-limit, governor
2736
+ // QUEUE/DEFER, the backlog cap and a denied claim, and a comment fired
2737
+ // here would have told the board about a session that did not exist
2738
+ // until some later sweep succeeded (if it did). Both comments carry the
2739
+ // real session id, so a retried row that genuinely starts again says so.
2740
+ let startRef = null;
2741
+ dispatchImpl(prompt, queueItem, classResult, "backlog", {
2742
+ onSpawn: ({ sessionId }) => {
2743
+ startRef = String(sessionId || "");
2744
+ void trackBoardItem({ item: queueItem, stage: "working", sessionRef: startRef, note: classResult.summary });
2745
+ },
2365
2746
  onClose: ({ ok, sessionId, stdout }) => {
2747
+ void trackBoardItem({
2748
+ item: queueItem,
2749
+ stage: ok ? "done" : "failed",
2750
+ sessionRef: String(sessionId || startRef || "unknown"),
2751
+ note: ok ? sessionCompletionNote(stdout) : null,
2752
+ });
2366
2753
  if (!ok) return;
2367
2754
  Promise.resolve(sweepOutcomesForSession({ sessionId, stdout, item: queueItem, classResult }))
2368
2755
  .catch(() => { /* fail-open */ });
@@ -2432,6 +2819,7 @@ async function main() {
2432
2819
  // that doctor/operators read. subscribe + bump are both fail-open; an init
2433
2820
  // failure here can never stop the daemon coming up.
2434
2821
  registerHookSubscribers();
2822
+ assertCadencePromptsExist();
2435
2823
 
2436
2824
  // Check for emergency stop
2437
2825
  try {