@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
@@ -0,0 +1,289 @@
1
+ /**
2
+ * lib/org/inbound/broadcast.mjs — "did a human just address EVERYONE?"
3
+ *
4
+ * PURE. No IO, no clock, no config. The one place the collective-address
5
+ * vocabulary lives on this side of the org.
6
+ *
7
+ * ── WHY THIS EXISTS ──
8
+ * On 2026-09-21 a human posted in #general: "Can everyone give me an update on
9
+ * what they're doing right now (across sessions on your machines)?". Thirteen
10
+ * agents were members. Zero replied. Not a regression — a standing gap: the
11
+ * addressing layer knew DMs, @mentions, threads and prose names, and had NO
12
+ * concept of a message addressed to the whole room. A space post with
13
+ * `mentionCount: 0` was dropped structurally (`no_mentions_not_threaded`)
14
+ * before its body was ever read, on every seat, including the ones that were
15
+ * up and healthy.
16
+ *
17
+ * ── THE RULE (implemented by `resolveMessaging` in directedness.mjs) ──
18
+ * A space post is a BROADCAST when ALL of:
19
+ * 1. the author is HUMAN — agents never broadcast to each other; a peer's
20
+ * "everyone please review" is ambient traffic, not a roll-call;
21
+ * 2. it is in a public/private SPACE this seat is a member of — never a DM
22
+ * (a DM is already addressed by construction and stays a DM even when it
23
+ * says "everyone");
24
+ * 3. it is not threaded — a thread reply keeps the existing thread rule;
25
+ * 4. the body matches one of {@link BROADCAST_PATTERNS}.
26
+ *
27
+ * ── THE TWIN ──
28
+ * hq carries a faithful port of this module at
29
+ * `src/server/llm-responder/broadcast.ts` for agents WITHOUT a machine (the
30
+ * LLM_RESPONDER path). The two are separate packages and cannot import each
31
+ * other, so BOTH pin the same {@link BROADCAST_CORPUS} in their tests. Change
32
+ * a pattern here and the hq twin's test goes red until it is changed there
33
+ * too — that is the drift guard, keep the corpus identical on both sides.
34
+ *
35
+ * @module lib/org/inbound/broadcast
36
+ */
37
+
38
+ "use strict";
39
+
40
+ /**
41
+ * The explicit collective addresses, in match order. Case-insensitive, anchored
42
+ * to word boundaries, so "all" alone, "you" alone, "you all right?" and
43
+ * "thanks all" do NOT match — only a phrase that unambiguously names the whole
44
+ * room does. `team,` is only a broadcast when it OPENS the message ("Team, quick
45
+ * check-in") — mid-sentence "the team" is a noun, not an address.
46
+ *
47
+ * Ordered most-common-first; order does not change the verdict, only which
48
+ * pattern is reported as the match.
49
+ *
50
+ * @type {readonly RegExp[]}
51
+ */
52
+ export const BROADCAST_PATTERNS = Object.freeze([
53
+ /\beveryone\b/i,
54
+ /\beverybody\b/i,
55
+ /\ball of you\b/i,
56
+ /\beach of you\b/i,
57
+ /\bthe whole team\b/i,
58
+ /^\s*team\s*,/i,
59
+ /(^|[^\w@])@here\b/i,
60
+ /(^|[^\w@])@channel\b/i,
61
+ /(^|[^\w@])@all\b/i,
62
+ /\ball agents\b/i,
63
+ /\ball colleagues\b/i,
64
+ /\broll[ -]?call\b/i,
65
+ /\bsound off\b/i,
66
+ /\bstatus check\b/i,
67
+ ]);
68
+
69
+ /**
70
+ * Is this message body an explicit address to the whole room?
71
+ *
72
+ * Pure text test — the author/room/thread conditions are the caller's
73
+ * (`resolveMessaging`); this answers ONLY condition 4.
74
+ *
75
+ * @param {unknown} body
76
+ * @returns {boolean}
77
+ */
78
+ export function isBroadcast(body) {
79
+ const text = typeof body === "string" ? body : "";
80
+ if (!text.trim()) return false;
81
+ return BROADCAST_PATTERNS.some((re) => re.test(text));
82
+ }
83
+
84
+ /**
85
+ * Which pattern matched, as a stable label for a log line / a test. Null when
86
+ * none did.
87
+ *
88
+ * @param {unknown} body
89
+ * @returns {string|null}
90
+ */
91
+ export function broadcastMatch(body) {
92
+ const text = typeof body === "string" ? body : "";
93
+ if (!text.trim()) return null;
94
+ for (const re of BROADCAST_PATTERNS) {
95
+ const m = re.exec(text);
96
+ if (m) return m[0].trim().toLowerCase();
97
+ }
98
+ return null;
99
+ }
100
+
101
+ /**
102
+ * THE SHARED CORPUS. Literal strings, pinned by directedness.test.mjs here and
103
+ * by hq's broadcast.test.ts. `positive` must all be broadcasts; `negative` must
104
+ * all NOT be. The first positive is the actual message that went unanswered.
105
+ */
106
+ export const BROADCAST_CORPUS = Object.freeze({
107
+ positive: Object.freeze([
108
+ "Can everyone give me an update on what they're doing right now (across sessions on your machines)?",
109
+ "did everyone see the deck?",
110
+ "Everybody: standup in 5",
111
+ "Quick one for all of you — what's blocking you today?",
112
+ "I'd like each of you to post your top priority",
113
+ "Thanks to the whole team for the push last night, what's next on your plates?",
114
+ "Team, quick check-in: what are you on?",
115
+ "@here who is around?",
116
+ "@channel status please",
117
+ "@all please confirm you can see this",
118
+ "all agents: report your current task",
119
+ "All colleagues, a quick update from your side please",
120
+ "roll call",
121
+ "Roll-call — sound off with what you're working on",
122
+ "status check: where is everyone at?",
123
+ ]),
124
+ negative: Object.freeze([
125
+ "all good here",
126
+ "thanks all",
127
+ "I'll take all three",
128
+ "you all right?",
129
+ "pushed the fix, ready for review",
130
+ "can you look at the deck?",
131
+ "the team offsite is on Thursday",
132
+ "email me at ops@all-hands.example",
133
+ "what is the status of the migration?",
134
+ "",
135
+ ]),
136
+ });
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // Reply shaping — what a roll-call respondent is told
140
+ // ---------------------------------------------------------------------------
141
+
142
+ /** The MessageEvent / inbox `kind` and the verdict `reason` a broadcast carries. */
143
+ export const BROADCAST_KIND = "broadcast";
144
+ export const BROADCAST_REASON = "collective";
145
+
146
+ /**
147
+ * Is this flattened inbox item a roll-call delivery? Reads the two fields the
148
+ * projection carries across `eventToInboxItem` (`kind`, `direct_reason`), so a
149
+ * daemon can gate on it without re-parsing prose.
150
+ *
151
+ * @param {object} item
152
+ * @returns {boolean}
153
+ */
154
+ export function isRollCallItem(item) {
155
+ if (!item || typeof item !== "object") return false;
156
+ if (String(item.kind || "") === BROADCAST_KIND) return true;
157
+ if (String(item.direct_reason || "") === BROADCAST_REASON) return true;
158
+ const cohort = item.cohort && typeof item.cohort === "object" ? item.cohort : null;
159
+ return !!cohort && String(cohort.surface || "") === BROADCAST_KIND;
160
+ }
161
+
162
+ /** Hard ceiling on a roll-call reply, in lines. The prompt says it; the fleet keeps it. */
163
+ export const ROLL_CALL_MAX_LINES = 5;
164
+
165
+ /**
166
+ * The reply-shaping block injected into whichever prompt composes the answer —
167
+ * the daemon's quick-reply responder or a spawned session. One text, both
168
+ * paths, so a collective answer reads the same however it was produced. The
169
+ * FACTS the answer is composed from are the current-work block
170
+ * (lib/session/current-work), which both paths already carry.
171
+ *
172
+ * ONE TEXT, THREE INTENTS (`collective.mjs`). The discipline is shared —
173
+ * answer for yourself, do not speak for anyone else, do not @mention, do not
174
+ * acknowledge — and exactly one line differs, because exactly one thing
175
+ * differs:
176
+ *
177
+ * roll-call → report on yourself: what you are doing, queued, blocked.
178
+ * work → say what YOUR slice is and by when. The ask is already a row
179
+ * on the board (one per addressee), so the reply is a plan, not
180
+ * a status line — and telling the session the row exists is what
181
+ * stops it writing "I'll open a task for this".
182
+ * ambient → the room was addressed and nothing was asked. Answer in your
183
+ * own lane and do NOT mint work from it, which is the prompt-side
184
+ * half of the same guard `HEDGE_PATTERNS` enforces in code.
185
+ *
186
+ * Word-for-word hq's `src/server/llm-responder/broadcast.ts`, so a seat
187
+ * answered by its own daemon and a seat answered by hq read the same in the
188
+ * room.
189
+ *
190
+ * @param {{intent?: string, respondents?: number|null}} [o] `respondents` — how
191
+ * many AI members the org has (an upper bound on who else answers; the room
192
+ * roster is not visible to a seat). Omitted or non-finite ⇒ "several".
193
+ * @returns {string}
194
+ */
195
+ export function collectiveInstructions(o = {}) {
196
+ const n = Number.isFinite(o.respondents) && o.respondents > 0 ? o.respondents : null;
197
+ const who = n ? `one of up to ${n} colleagues` : "one of several colleagues";
198
+ const intent = o.intent || "roll-call";
199
+ const banner = intent === "roll-call" ? "ROLL-CALL" : "ADDRESSED TO EVERYONE";
200
+ const framing =
201
+ intent === "work"
202
+ ? `A human asked the whole room to do something, not you specifically. You are ${who} who will answer the same message. Every addressed member owns its own slice; nobody answers for the group.`
203
+ : `A human addressed the whole room, not you specifically. You are ${who} who will answer the same message. Every addressed member answers for itself; nobody answers for the group.`;
204
+ const content =
205
+ intent === "work"
206
+ ? `- This ask is already on the board as YOUR OWN item, so do not open another one. Reply in at most ${ROLL_CALL_MAX_LINES} short lines, about your slice only: what you will do about it, and by when. Facts, not adjectives.`
207
+ : intent === "ambient"
208
+ ? `- Nothing was asked of you here, so do not turn it into a task. Reply in at most ${ROLL_CALL_MAX_LINES} short lines, from your own lane only. Facts, not adjectives.`
209
+ : `- Reply in at most ${ROLL_CALL_MAX_LINES} short lines, about YOURSELF only: what you are doing right now, what is queued, what is blocked. Facts, not adjectives.`;
210
+ return [
211
+ `===== ${banner} =====`,
212
+ framing,
213
+ content,
214
+ "- Do NOT summarise, list, guess at or speak for anyone else's status.",
215
+ "- Do NOT @mention anyone. Do not address other agents. Do not ask the room a question back.",
216
+ "- Do NOT post an acknowledgement, a holding line or a 'checking now' — say nothing until the answer itself is ready, then post it once.",
217
+ "- Do not restate the question and do not open with a greeting; the first line is already the answer.",
218
+ `===== END ${banner} =====`,
219
+ ].join("\n");
220
+ }
221
+
222
+ /**
223
+ * The roll-call arm of {@link collectiveInstructions}, kept as its own export
224
+ * because it is what every existing call site and test names. Byte-identical to
225
+ * the text this module has always produced.
226
+ *
227
+ * @param {{respondents?: number|null}} [o]
228
+ * @returns {string}
229
+ */
230
+ export function rollCallInstructions(o = {}) {
231
+ return collectiveInstructions({ ...o, intent: "roll-call" });
232
+ }
233
+
234
+ // WHAT THE SEAT IS DOING is not rendered here. This module briefly carried a
235
+ // `renderSelfActivity` (status by session name, peers by name, handoffs,
236
+ // every board row, the daemon's active sessions) that the quick reply injected
237
+ // NEXT TO lib/session/current-work's block — two accounts of one state, and
238
+ // the one here quoted session names. The single renderer is
239
+ // lib/session/current-work (`currentWorkBlock`, audience- and barrier-scoped);
240
+ // what this one carried that it lacked moved there. Do not add a second.
241
+
242
+ // ---------------------------------------------------------------------------
243
+ // Rate shaping — 13 replies must not land in the same second
244
+ // ---------------------------------------------------------------------------
245
+
246
+ /** The jitter window a roll-call reply is spread across, per channel. */
247
+ export const ROLL_CALL_JITTER_MAX_MS = 20_000;
248
+
249
+ /** FNV-1a 32-bit over a string. Deterministic, dependency-free. */
250
+ function fnv1a(s) {
251
+ let h = 0x811c9dc5;
252
+ for (let i = 0; i < s.length; i += 1) {
253
+ h ^= s.charCodeAt(i);
254
+ h = Math.imul(h, 0x01000193) >>> 0;
255
+ }
256
+ return h >>> 0;
257
+ }
258
+
259
+ /**
260
+ * How long THIS seat waits before posting its roll-call answer. Deterministic
261
+ * per (seat, channel, message): the same seat answering the same roll-call
262
+ * after a crash waits the same amount, and thirteen seats spread across the
263
+ * window instead of stacking on the second the event landed. Pure — no
264
+ * randomness — so a test can pin it.
265
+ *
266
+ * @param {{seat?:string, channelId?:string, messageId?:string, maxMs?:number}} o
267
+ * @returns {number} milliseconds in [0, maxMs)
268
+ */
269
+ export function rollCallJitterMs(o = {}) {
270
+ const max = Number.isFinite(o.maxMs) && o.maxMs >= 0 ? o.maxMs : ROLL_CALL_JITTER_MAX_MS;
271
+ if (max === 0) return 0;
272
+ const key = `${o.seat || ""}|${o.channelId || ""}|${o.messageId || ""}`;
273
+ return fnv1a(key) % max;
274
+ }
275
+
276
+ export default {
277
+ BROADCAST_PATTERNS,
278
+ BROADCAST_CORPUS,
279
+ BROADCAST_KIND,
280
+ BROADCAST_REASON,
281
+ ROLL_CALL_MAX_LINES,
282
+ ROLL_CALL_JITTER_MAX_MS,
283
+ isBroadcast,
284
+ broadcastMatch,
285
+ isRollCallItem,
286
+ collectiveInstructions,
287
+ rollCallInstructions,
288
+ rollCallJitterMs,
289
+ };
@@ -0,0 +1,375 @@
1
+ /**
2
+ * lib/org/inbound/collective.mjs — a message addressed to EVERYONE: is it work?
3
+ *
4
+ * PURE. No IO, no clock, no config. The twin of hq's
5
+ * `src/server/work/collective.ts`.
6
+ *
7
+ * ── THE QUESTION THIS ANSWERS, AND THE ONE IT DOES NOT ──
8
+ * `broadcast.mjs#isBroadcast` answers ADDRESSING: did a human name the whole
9
+ * room? This module answers what comes next, and only that: GIVEN that the
10
+ * room was addressed collectively, is the message a ROLL-CALL (report on
11
+ * yourself), WORK (go and do a thing), or AMBIENT (neither)?
12
+ *
13
+ * It deliberately does NOT re-test the address — `directedness.mjs#
14
+ * isBroadcastFor` already settled that, and asking twice is how two places
15
+ * come to disagree about one question.
16
+ *
17
+ * ── WHY IT EXISTS ──
18
+ * 2026-09-21 fixed the first half: "Can everyone give me an update?" drew zero
19
+ * replies from thirteen members. The half it did not fix: "Everyone, please
20
+ * review the binder by Friday" drew replies and NO BOARD ROWS, on both planes.
21
+ * Work addressed to everyone did no work.
22
+ *
23
+ * ── THE RULE, IN THE CODE THAT IMPLEMENTS IT ──
24
+ *
25
+ * 1. ROLL-CALL AND WORK ARE NOT MUTUALLY EXCLUSIVE TEXT, so they are not
26
+ * tested in sequence. "Everyone, please review the binder and give me an
27
+ * update when it's done" is BOTH shapes in one sentence, and a first-match
28
+ * ladder gets it wrong whichever order it is written in. Instead the
29
+ * roll-call phrases are EXCISED from the body and the work gate runs on
30
+ * what is left: if the residue still asks for work, it is work; if the work
31
+ * reading only ever came from the status phrasing, it is a roll-call. That
32
+ * is also what keeps "can everyone give me an UPDATE" from being work —
33
+ * `update` is an action verb, and without the excision the plainest
34
+ * roll-call in the corpus would open thirteen board rows.
35
+ *
36
+ * 2. A HEDGE DEFUSES WORK. "everyone should take a look sometime", "feel free
37
+ * to review when you get a chance", "no rush" — a board that mints thirteen
38
+ * rows for each of those is a board the owner stops opening within a week.
39
+ * A hedge is read BEFORE the verb, so no amount of imperative grammar beats
40
+ * "if anyone has a minute".
41
+ *
42
+ * 3. THE WORK TEST IS hq's. `asksForWork` / `hasSubstance` below are a VERBATIM
43
+ * port of `src/server/work/threshold.ts` — not a second opinion about what
44
+ * work looks like. A collective ask and a direct ask are the same ask with a
45
+ * different audience.
46
+ *
47
+ * ── WHAT THIS FILE MAY AND MAY NOT DECIDE ──
48
+ * It shapes a PROMPT and routes a FAST PATH. It NEVER decides whether a board
49
+ * row exists. That judgement stays single-implementation on the server:
50
+ * `work-ledger.mjs` sends `collective: true` plus the body over `board.track`,
51
+ * and hq's `assessAskWithAudience` runs its own copy of this classifier. A
52
+ * daemon that got the verdict wrong here therefore loses a nicely-shaped
53
+ * prompt, never a row it should not have opened.
54
+ *
55
+ * ── THE TWIN ──
56
+ * Both sides pin {@link COLLECTIVE_CORPUS}, byte-for-byte. Change a pattern
57
+ * here and hq's `collective.test.ts` goes red until it is changed there too —
58
+ * the same drift guard BROADCAST_CORPUS already runs for the address.
59
+ *
60
+ * @module lib/org/inbound/collective
61
+ */
62
+
63
+ "use strict";
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // VERBATIM PORT of hq src/server/work/threshold.ts. Do not "improve" either
67
+ // copy alone: the corpus below is what holds them together, and the board gate
68
+ // that matters runs hq's copy.
69
+ // ---------------------------------------------------------------------------
70
+
71
+ /** Shortest ask we will treat as an ask at all. */
72
+ const MIN_SUBSTANCE_CHARS = 12;
73
+
74
+ /** A chase or a pleasantry — a reaction to work, never a request for it. */
75
+ const NO_SUBSTANCE =
76
+ /^(?:[\s?!.…]*|\?+|pls|please|ping|bump|any\s+updates?|updates?|status|well\?|and\?|thanks?|thank\s+you|ty|thx|cheers|ok(?:ay)?|kk|got\s+it|sure|nice|great|cool|perfect|lovely|yes|yep|no|nope|nvm|never\s+mind|👍|🙏|🎉)[\s?!.…]*$/i;
77
+
78
+ /** An explicit "do not action this" prefix, honoured before anything else. */
79
+ const NO_ACTION_PREFIX = /^\s*(?:fyi\b|no\s+action\b|just\s+so\s+you\s+know\b)/i;
80
+
81
+ /**
82
+ * Verbs that name WORK. Kept to things that produce an artifact or change the
83
+ * world — deliberately NOT "explain", "tell", "what", "why", which are answered
84
+ * in the turn and leave the reply as their own artifact.
85
+ */
86
+ const ACTION_VERB =
87
+ "(?:fix|ship|build|write|draft|send|create|make|set\\s+up|update|upgrade|migrate|deploy|" +
88
+ "investigate|look\\s+into|dig\\s+into|sort\\s+out|figure\\s+out|chase|review|prepare|prep|" +
89
+ "schedule|book|publish|add|remove|delete|refactor|debug|push|merge|implement|plan|research|" +
90
+ "analyse|analyze|compile|produce|generate|organise|organize|follow\\s+up|resolve|land|" +
91
+ "roll\\s+out|audit|clean\\s+up|tidy|rename|document|write\\s+up|pull\\s+together|put\\s+together|" +
92
+ "sort|handle|action|own|drive|unblock|test|verify|check\\s+into|reconcile)";
93
+
94
+ /** An imperative: the verb opens a sentence ("fix these issues and push them"). */
95
+ const IMPERATIVE = new RegExp(
96
+ `(?:^|[.!?;,\\n]\\s*|\\b(?:and|then|also|please|pls|first|next|now)\\s+)` +
97
+ `(?:(?:also|then|now|and|please|pls|first|next)\\s+){0,2}${ACTION_VERB}\\b`,
98
+ "i",
99
+ );
100
+
101
+ /** A polite request wrapper ("could you look into…", "I need you to draft…"). */
102
+ const REQUEST = new RegExp(
103
+ `\\b(?:can|could|would|will|please|pls|need\\s+(?:you\\s+)?to|needs?\\s+to|want\\s+you\\s+to|` +
104
+ `like\\s+you\\s+to|should\\s+you|mind)\\b[^.!?\\n]{0,40}?\\b${ACTION_VERB}\\b`,
105
+ "i",
106
+ );
107
+
108
+ /**
109
+ * Does this text ask the agent to DO something (as opposed to answer something)?
110
+ * @param {unknown} body
111
+ * @returns {boolean}
112
+ */
113
+ export function asksForWork(body) {
114
+ const text = String(body == null ? "" : body).trim();
115
+ if (!text) return false;
116
+ if (NO_ACTION_PREFIX.test(text)) return false;
117
+ return IMPERATIVE.test(text) || REQUEST.test(text);
118
+ }
119
+
120
+ /**
121
+ * Is there enough here to be an ask at all?
122
+ * @param {unknown} body
123
+ * @returns {boolean}
124
+ */
125
+ export function hasSubstance(body) {
126
+ const text = String(body == null ? "" : body).trim();
127
+ if (!text) return false;
128
+ if (NO_SUBSTANCE.test(text)) return false;
129
+ return text.length >= MIN_SUBSTANCE_CHARS;
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // The collective classifier
134
+ // ---------------------------------------------------------------------------
135
+
136
+ /**
137
+ * @typedef {"roll-call"|"work"|"ambient"} CollectiveIntent
138
+ */
139
+
140
+ /** The three intents, in the order a reader should meet them. */
141
+ export const COLLECTIVE_INTENTS = Object.freeze(["roll-call", "work", "ambient"]);
142
+
143
+ /**
144
+ * Phrases that ask each addressee to REPORT ON ITSELF. Excised from the body
145
+ * before the work gate runs (see rule 1), so a status ask that happens to use
146
+ * an action verb — "give me an UPDATE", "REVIEW your plate" — cannot read as a
147
+ * request to go and do something.
148
+ *
149
+ * Every one is anchored to a self-reference (`you`, `your`, `their`,
150
+ * `everyone`) or is a named ritual (roll call, standup, sound off). A bare
151
+ * "status" or "update" is NOT here: "update the binder" is work.
152
+ *
153
+ * @type {readonly RegExp[]}
154
+ */
155
+ export const ROLL_CALL_PATTERNS = Object.freeze([
156
+ /\broll[ -]?call\b/gi,
157
+ /\bsound off\b/gi,
158
+ /\bstatus check\b/gi,
159
+ /\bstand[ -]?up\b/gi,
160
+ /\bcheck[ -]?in\b/gi,
161
+ /\bwho(?:'s| is| has| are)\b[^.!?\n]{0,24}\b(?:around|online|here|available|free|working)\b/gi,
162
+ /\bwhat\b[^.!?\n]{0,16}\b(?:you|they)(?:'re| are)?\b[^.!?\n]{0,12}\b(?:working on|doing|up to|on right now)\b/gi,
163
+ /\b(?:give|post|share|send|drop|leave)\b[^.!?\n]{0,24}\b(?:an?\s+|your\s+|their\s+)?(?:update|status)\b/gi,
164
+ /\b(?:update|status)\b[^.!?\n]{0,24}\bfrom (?:your|their|each|you)\b/gi,
165
+ /\b(?:update|status)s?\s*(?:please|pls|report)\b/gi,
166
+ /\bupdate on what\b/gi,
167
+ /\bwhat(?:'s| is)\b[^.!?\n]{0,16}\bblocking (?:you|everyone|anyone)\b/gi,
168
+ /\bwhat(?:'s| is) next on (?:your|their)\b/gi,
169
+ /\b(?:your|their) (?:top |current )?(?:priority|priorities|task|tasks|plate|plates|lane|lanes)\b/gi,
170
+ /\breport (?:your|their|back with your)\b/gi,
171
+ /\bconfirm you can (?:see|read|hear)\b/gi,
172
+ /\bwhere (?:is|are) (?:everyone|everybody|we all)\b/gi,
173
+ /\bdid (?:everyone|everybody|you all|all of you)\b/gi,
174
+ ]);
175
+
176
+ /**
177
+ * Phrases that make a work-shaped sentence NOT an assignment. Read before the
178
+ * verb: this is the guard against "everyone should take a look sometime"
179
+ * minting thirteen rows, and it is deliberately the widest net here, because
180
+ * the asymmetry is real — a missed row costs one invisible ask, a spurious row
181
+ * costs thirteen and the owner's attention for the board itself.
182
+ *
183
+ * @type {readonly RegExp[]}
184
+ */
185
+ export const HEDGE_PATTERNS = Object.freeze([
186
+ /\bsometime\b/i,
187
+ /\bat some point\b/i,
188
+ /\bwhen(?:ever)? (?:you|anyone|someone|folks|people)\b[^.!?\n]{0,20}\b(?:chance|moment|time|minute|min|sec|second|bandwidth|capacity)\b/i,
189
+ /\bno (?:rush|hurry|pressure|urgency|action|need)\b/i,
190
+ /\bif (?:anyone|anybody|someone|you)\b[^.!?\n]{0,24}\b(?:time|chance|moment|minute|min|sec|bandwidth|capacity|curious|interested|keen)\b/i,
191
+ /\bfeel free\b/i,
192
+ /\bno action (?:needed|required|necessary)\b/i,
193
+ /\bnothing (?:to do|needed|required)\b/i,
194
+ /\b(?:purely |just )?(?:fyi|for (?:your )?(?:info|information|awareness|visibility))\b/i,
195
+ /\bmight (?:want|be worth|be of interest)\b/i,
196
+ /\bworth a (?:look|read|skim)\b/i,
197
+ /\boptional\b/i,
198
+ /\bwhenever\b/i,
199
+ /\bif you(?:'re| are) (?:curious|interested|bored)\b/i,
200
+ ]);
201
+
202
+ /**
203
+ * Does this body hedge hard enough that no verb in it is an assignment?
204
+ * @param {unknown} body
205
+ * @returns {boolean}
206
+ */
207
+ export function isHedged(body) {
208
+ const text = typeof body === "string" ? body : "";
209
+ if (!text.trim()) return false;
210
+ return HEDGE_PATTERNS.some((re) => re.test(text));
211
+ }
212
+
213
+ /**
214
+ * The body with every roll-call phrase blanked out. Exported for the tests and
215
+ * for a log line: "we called this a roll-call, and here is what was left after
216
+ * the status phrasing came out" is the one sentence that makes a wrong verdict
217
+ * diagnosable without a debugger.
218
+ *
219
+ * @param {unknown} body
220
+ * @returns {string}
221
+ */
222
+ export function withoutRollCallPhrases(body) {
223
+ let text = typeof body === "string" ? body : "";
224
+ if (!text.trim()) return "";
225
+ for (const re of ROLL_CALL_PATTERNS) {
226
+ // Every pattern is /g; `replace` with a global regex is stateless per call
227
+ // (it resets lastIndex itself), so these are safe as module constants —
228
+ // `test()` on a /g regex would NOT be, which is why nothing here calls it.
229
+ text = text.replace(re, " ");
230
+ }
231
+ return text;
232
+ }
233
+
234
+ /**
235
+ * Did any roll-call phrase appear at all?
236
+ * @param {unknown} body
237
+ * @returns {boolean}
238
+ */
239
+ export function hasRollCallPhrase(body) {
240
+ const text = typeof body === "string" ? body : "";
241
+ if (!text.trim()) return false;
242
+ return withoutRollCallPhrases(text) !== text;
243
+ }
244
+
245
+ /**
246
+ * THE decision. Pure, total, and the only implementation on this plane.
247
+ *
248
+ * Call it ONLY for a message already known to be collectively addressed; for
249
+ * anything else the answer is meaningless (a 1:1 DM saying "review the binder"
250
+ * is directed work and the ordinary path owns it).
251
+ *
252
+ * @param {unknown} body
253
+ * @returns {{intent: CollectiveIntent, reason: string}}
254
+ */
255
+ export function collectiveIntent(body) {
256
+ const text = (typeof body === "string" ? body : "").trim();
257
+ if (!text) return { intent: "ambient", reason: "empty" };
258
+
259
+ const rollCallPhrase = hasRollCallPhrase(text);
260
+
261
+ // A hedge beats every verb in the sentence — but NOT a roll-call. "everyone
262
+ // sound off whenever you get a sec" is still a roll-call; it just is not
263
+ // work, and a roll-call was never going to open a row anyway.
264
+ if (isHedged(text)) {
265
+ return rollCallPhrase
266
+ ? { intent: "roll-call", reason: "roll-call-phrase" }
267
+ : { intent: "ambient", reason: "hedged" };
268
+ }
269
+
270
+ const residue = rollCallPhrase ? withoutRollCallPhrases(text) : text;
271
+ if (hasSubstance(residue) && asksForWork(residue)) {
272
+ return { intent: "work", reason: "asks-for-work" };
273
+ }
274
+
275
+ if (rollCallPhrase) return { intent: "roll-call", reason: "roll-call-phrase" };
276
+ return { intent: "ambient", reason: "no-work-asked" };
277
+ }
278
+
279
+ /**
280
+ * Convenience: is this collective message WORK?
281
+ * @param {unknown} body
282
+ * @returns {boolean}
283
+ */
284
+ export function isCollectiveWork(body) {
285
+ return collectiveIntent(body).intent === "work";
286
+ }
287
+
288
+ /**
289
+ * The intent of a flattened INBOX ITEM that the inbound join already decided is
290
+ * a broadcast (`isRollCallItem`). Returns null for anything that is not one —
291
+ * a DM, an @mention, a task comment — because the classifier's answer is only
292
+ * meaningful once the collective address is established.
293
+ *
294
+ * Lives here rather than in broadcast.mjs so that module stays what it is: the
295
+ * ADDRESS, with no opinion about what the address asked for.
296
+ *
297
+ * @param {object} item flattened inbox item
298
+ * @param {(item:object)=>boolean} isCollectiveItem usually `isRollCallItem`
299
+ * @returns {CollectiveIntent|null}
300
+ */
301
+ export function itemCollectiveIntent(item, isCollectiveItem) {
302
+ if (typeof isCollectiveItem !== "function" || !isCollectiveItem(item)) return null;
303
+ const body = item && (item.content || item.text || item.subject);
304
+ return collectiveIntent(body).intent;
305
+ }
306
+
307
+ /**
308
+ * THE SHARED CORPUS. Literal strings, pinned by `collective.test.mjs` here and
309
+ * by hq's `src/server/work/collective.test.ts`. Every string is one a human
310
+ * could plausibly type into #general; every one already satisfies
311
+ * `isBroadcast`, so the only thing each pins is the intent.
312
+ *
313
+ * Same contract as BROADCAST_CORPUS: change a pattern on either plane and the
314
+ * other plane's test goes red until the two agree again.
315
+ */
316
+ export const COLLECTIVE_CORPUS = Object.freeze({
317
+ /** WORK: every addressee opens a row of its own. */
318
+ work: Object.freeze([
319
+ "Everyone, please review the binder by Friday",
320
+ "Everybody: please update your KPI rows in the mandate before end of week",
321
+ "@here we need to draft the board pack today",
322
+ "@channel please push your branches before the 18:00 freeze",
323
+ "All of you: please audit your channel memberships this week",
324
+ "Team, please prepare your section of the deck before Thursday",
325
+ "the whole team needs to review the binder before Friday",
326
+ "Everyone, please review the binder and give me an update when it's done",
327
+ "@all please fix the failing checks on your own repos today",
328
+ ]),
329
+ /** ROLL-CALL: every addressee answers for itself; no rows. */
330
+ rollCall: Object.freeze([
331
+ "Can everyone give me an update on what they're doing right now (across sessions on your machines)?",
332
+ "did everyone see the deck?",
333
+ "Everybody: standup in 5",
334
+ "Quick one for all of you — what's blocking you today?",
335
+ "I'd like each of you to post your top priority",
336
+ "Thanks to the whole team for the push last night, what's next on your plates?",
337
+ "Team, quick check-in: what are you on?",
338
+ "@here who is around?",
339
+ "@channel status please",
340
+ "@all please confirm you can see this",
341
+ "all agents: report your current task",
342
+ "All colleagues, a quick update from your side please",
343
+ "roll call",
344
+ "Roll-call — sound off with what you're working on",
345
+ "status check: where is everyone at?",
346
+ "everyone sound off whenever you get a sec",
347
+ ]),
348
+ /** AMBIENT: addressed to the room, asking for nothing. No rows. */
349
+ ambient: Object.freeze([
350
+ "everyone should take a look at the binder sometime",
351
+ "Everyone, feel free to review the binder when you get a chance",
352
+ "FYI everyone — the binder is up in the drive",
353
+ "@here the deploy finished, no action needed",
354
+ "Everyone, the offsite is on Thursday at the usual place",
355
+ "@channel just publishing the notes for visibility",
356
+ "Everybody: nice work this week, genuinely",
357
+ "everyone might want to review the new brand kit at some point",
358
+ "@all optional: the reading group meets at 4",
359
+ ]),
360
+ });
361
+
362
+ export default {
363
+ COLLECTIVE_INTENTS,
364
+ COLLECTIVE_CORPUS,
365
+ ROLL_CALL_PATTERNS,
366
+ HEDGE_PATTERNS,
367
+ asksForWork,
368
+ hasSubstance,
369
+ isHedged,
370
+ withoutRollCallPhrases,
371
+ hasRollCallPhrase,
372
+ collectiveIntent,
373
+ isCollectiveWork,
374
+ itemCollectiveIntent,
375
+ };