@cohortapp/agent-sdk 2.17.0 → 2.18.4

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