@cohortapp/agent-sdk 2.16.0 → 2.18.4

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