@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
@@ -1,486 +0,0 @@
1
- /**
2
- * room-budget.test.mjs — one room, one interim, whatever else is going on.
3
- *
4
- * The production failure this pins: the interim budget was per OBLIGATION and
5
- * never per ROOM. Eight agents with N concurrent obligations each in
6
- * `capital-formation` emitted up to N acks + 2N progress pings + N failure
7
- * notices into one channel — 9,187 messages in 21 days, ~45 % of them exact
8
- * duplicates, two independent timers narrating the same overlapping work at the
9
- * same timestamp. Every one of those decisions was individually correct.
10
- *
11
- * Run: node --test lib/assurance/room-budget.test.mjs
12
- */
13
-
14
- import { test, describe, beforeEach } from "node:test";
15
- import assert from "node:assert/strict";
16
- import { mkdtempSync, rmSync, readFileSync, mkdirSync, writeFileSync } from "node:fs";
17
- import { tmpdir } from "node:os";
18
- import { join } from "node:path";
19
-
20
- import {
21
- DEFAULT_ROOM_INTERIM_WINDOW_MS,
22
- DEFAULT_ROOM_NOTICE_WINDOW_MS,
23
- roomInterimWindowMs,
24
- roomNoticeWindowMs,
25
- roomKey,
26
- noticeKey,
27
- textDigest,
28
- interimAllowed,
29
- noticeAllowed,
30
- recordInterim,
31
- recordNotice,
32
- pruneLedger,
33
- claimRoomInterim,
34
- claimRoomNotice,
35
- ledgerPath,
36
- LEDGER_BASENAME,
37
- readLedger,
38
- _emptyLedger,
39
- } from "./room-budget.mjs";
40
-
41
- const ROOM_INTERIM_WINDOW_MS = DEFAULT_ROOM_INTERIM_WINDOW_MS;
42
-
43
- const T0 = 1_757_000_000_000; // a fixed instant; nothing here reads a clock
44
-
45
- // ---------------------------------------------------------------------------
46
- // The pure half
47
- // ---------------------------------------------------------------------------
48
-
49
- describe("roomKey — a room is (service, channel) and nothing else", () => {
50
- test("service and channel together name the room", () => {
51
- assert.equal(roomKey({ service: "cohort", channel: "C-CAPFORM" }), "cohort|c-capform");
52
- });
53
-
54
- test("case and surrounding space never make two rooms out of one", () => {
55
- assert.equal(roomKey({ service: "Cohort", channel: " C-CapForm " }), roomKey({ service: "cohort", channel: "c-capform" }));
56
- });
57
-
58
- test("a missing channel is still a room, not a crash", () => {
59
- assert.equal(typeof roomKey({ service: "cohort", channel: null }), "string");
60
- assert.equal(typeof roomKey({}), "string");
61
- assert.equal(typeof roomKey(), "string");
62
- });
63
-
64
- test("two different channels on one service are two rooms", () => {
65
- assert.notEqual(roomKey({ service: "cohort", channel: "A" }), roomKey({ service: "cohort", channel: "B" }));
66
- });
67
-
68
- test("the same channel name on two services is two rooms", () => {
69
- assert.notEqual(roomKey({ service: "cohort", channel: "general" }), roomKey({ service: "slack", channel: "general" }));
70
- });
71
- });
72
-
73
- describe("interimAllowed — the pure decision", () => {
74
- test("an empty ledger allows the first interim", () => {
75
- const v = interimAllowed({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
76
- assert.equal(v.allowed, true);
77
- assert.equal(v.reason, "room-budget-free");
78
- });
79
-
80
- test("a second interim inside the window is refused, and says which room and when", () => {
81
- const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
82
- const v = interimAllowed({ ledger: led, service: "cohort", channel: "C1", now: T0 + 60_000 });
83
- assert.equal(v.allowed, false);
84
- assert.equal(v.reason, "room-budget-spent");
85
- assert.equal(v.lastAt, T0);
86
- assert.equal(v.room, roomKey({ service: "cohort", channel: "C1" }));
87
- });
88
-
89
- test("the window reopens exactly at the boundary, not a millisecond early", () => {
90
- const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
91
- assert.equal(interimAllowed({ ledger: led, service: "cohort", channel: "C1", now: T0 + ROOM_INTERIM_WINDOW_MS - 1 }).allowed, false);
92
- assert.equal(interimAllowed({ ledger: led, service: "cohort", channel: "C1", now: T0 + ROOM_INTERIM_WINDOW_MS }).allowed, true);
93
- });
94
-
95
- test("a spent room never silences a DIFFERENT room", () => {
96
- const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
97
- assert.equal(interimAllowed({ ledger: led, service: "cohort", channel: "C2", now: T0 + 1 }).allowed, true);
98
- assert.equal(interimAllowed({ ledger: led, service: "slack", channel: "C1", now: T0 + 1 }).allowed, true);
99
- });
100
-
101
- test("a clock that goes backwards does not hand out a free interim", () => {
102
- // NTP steps, a restored snapshot, a test harness. `now` before the recorded
103
- // stamp must read as "inside the window", never as "the window elapsed".
104
- const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
105
- assert.equal(interimAllowed({ ledger: led, service: "cohort", channel: "C1", now: T0 - 60 * 60_000 }).allowed, false);
106
- });
107
-
108
- test("a corrupt or absent entry reads as 'never spoken here', never as a permanent gag", () => {
109
- for (const bad of [{ rooms: null }, { rooms: { "cohort|c1": "yesterday" } }, {}, null, undefined, "nonsense"]) {
110
- assert.equal(interimAllowed({ ledger: bad, service: "cohort", channel: "C1", now: T0 }).allowed, true, `${JSON.stringify(bad)} must fail open`);
111
- }
112
- });
113
-
114
- test("the decision is pure: it never mutates the ledger it is given", () => {
115
- const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
116
- const before = JSON.stringify(led);
117
- interimAllowed({ ledger: led, service: "cohort", channel: "C1", now: T0 + 1 });
118
- assert.equal(JSON.stringify(led), before);
119
- });
120
-
121
- test("the window is env-tunable and defaults to fifteen minutes", () => {
122
- assert.equal(DEFAULT_ROOM_INTERIM_WINDOW_MS, 15 * 60_000);
123
- const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
124
- assert.equal(interimAllowed({ ledger: led, service: "cohort", channel: "C1", now: T0 + 2000, windowMs: 1000 }).allowed, true);
125
- });
126
- });
127
-
128
- describe("recordInterim / pruneLedger", () => {
129
- test("recording returns a NEW ledger and leaves the old one untouched", () => {
130
- const led = _emptyLedger();
131
- const next = recordInterim({ ledger: led, service: "cohort", channel: "C1", now: T0 });
132
- assert.notEqual(led, next);
133
- assert.deepEqual(led.rooms, {});
134
- assert.equal(next.rooms[roomKey({ service: "cohort", channel: "C1" })], T0);
135
- });
136
-
137
- test("the ledger does not grow without bound — entries older than two windows are dropped", () => {
138
- // The store is a durable file on a machine that runs for months. An entry
139
- // that can never refuse anything again is landfill.
140
- let led = _emptyLedger();
141
- for (let i = 0; i < 50; i++) led = recordInterim({ ledger: led, service: "cohort", channel: `OLD-${i}`, now: T0 });
142
- led = recordInterim({ ledger: led, service: "cohort", channel: "FRESH", now: T0 + 3 * ROOM_INTERIM_WINDOW_MS });
143
- const pruned = pruneLedger({ ledger: led, now: T0 + 3 * ROOM_INTERIM_WINDOW_MS });
144
- assert.equal(Object.keys(pruned.rooms).length, 1);
145
- assert.ok(pruned.rooms[roomKey({ service: "cohort", channel: "FRESH" })]);
146
- });
147
-
148
- test("pruning never drops an entry that can still refuse an interim", () => {
149
- const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
150
- const pruned = pruneLedger({ ledger: led, now: T0 + ROOM_INTERIM_WINDOW_MS });
151
- assert.equal(interimAllowed({ ledger: pruned, service: "cohort", channel: "C1", now: T0 + ROOM_INTERIM_WINDOW_MS - 1 }).allowed, false);
152
- });
153
- });
154
-
155
- // ---------------------------------------------------------------------------
156
- // The durable edge
157
- // ---------------------------------------------------------------------------
158
-
159
- describe("claimRoomInterim — the durable edge, with an injected clock and fs", () => {
160
- let ROOT;
161
- beforeEach(() => {
162
- if (ROOT) { try { rmSync(ROOT, { recursive: true, force: true }); } catch { /* */ } }
163
- ROOT = mkdtempSync(join(tmpdir(), "room-budget-"));
164
- });
165
-
166
- test("THE FLOOD FIX: N concurrent obligations in one room yield exactly ONE interim", () => {
167
- // 8 agents' worth of concurrent asks, arriving together, as measured.
168
- const said = [];
169
- for (let i = 0; i < 40; i++) {
170
- const v = claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0 + i * 1000, agentRoot: ROOT });
171
- if (v.allowed) said.push(i);
172
- }
173
- assert.deepEqual(said, [0], `expected one interim across 40 obligations, got ${said.length}`);
174
- });
175
-
176
- test("…and the next window allows exactly one more, not a backlog burst", () => {
177
- for (let i = 0; i < 40; i++) claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0 + i * 1000, agentRoot: ROOT });
178
- let allowed = 0;
179
- for (let i = 0; i < 40; i++) {
180
- if (claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0 + ROOM_INTERIM_WINDOW_MS + i * 1000, agentRoot: ROOT }).allowed) allowed++;
181
- }
182
- assert.equal(allowed, 1);
183
- });
184
-
185
- test("a refused claim writes nothing and does not move the window", () => {
186
- claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT });
187
- claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + 10 * 60_000, agentRoot: ROOT });
188
- // If the refused claim had stamped the ledger, the window would run from
189
- // T0+10min and the room would stay gagged past T0+15min.
190
- assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + ROOM_INTERIM_WINDOW_MS, agentRoot: ROOT }).allowed, true);
191
- });
192
-
193
- test("busy rooms never gag quiet ones", () => {
194
- for (let i = 0; i < 20; i++) claimRoomInterim({ service: "cohort", channel: "busy", now: T0 + i, agentRoot: ROOT });
195
- assert.equal(claimRoomInterim({ service: "cohort", channel: "quiet", now: T0 + 100, agentRoot: ROOT }).allowed, true);
196
- });
197
-
198
- test("the store is durable — a fresh process sees the spend", () => {
199
- claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT });
200
- const onDisk = JSON.parse(readFileSync(ledgerPath(ROOT), "utf-8"));
201
- assert.equal(onDisk.rooms[roomKey({ service: "cohort", channel: "C1" })], T0);
202
- assert.equal(readLedger({ agentRoot: ROOT }).rooms[roomKey({ service: "cohort", channel: "C1" })], T0);
203
- });
204
-
205
- test("it lives beside the obligation ledger it governs", () => {
206
- assert.equal(ledgerPath(ROOT), join(ROOT, "state", "obligations", "room-budget.json"));
207
- });
208
-
209
- test("an unreadable store fails OPEN — a broken file may not silence a human", () => {
210
- mkdirSync(join(ROOT, "state", "obligations"), { recursive: true });
211
- writeFileSync(ledgerPath(ROOT), "{ this is not json");
212
- const v = claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT });
213
- assert.equal(v.allowed, true);
214
- assert.equal(v.degraded, true, "…but it SAYS it is degraded, so the condition is measurable");
215
- });
216
-
217
- test("an unwritable store still lets the message out, and says so", () => {
218
- const v = claimRoomInterim({
219
- service: "cohort", channel: "C1", now: T0, agentRoot: ROOT,
220
- deps: { writeImpl: () => { throw new Error("EROFS"); } },
221
- });
222
- assert.equal(v.allowed, true);
223
- assert.equal(v.degraded, true);
224
- });
225
-
226
- test("the clock is injected, never read from the wall", () => {
227
- // Passing `now` is the contract; omitting it is a caller bug, not a licence
228
- // to read Date.now() from inside a decision module.
229
- const v = claimRoomInterim({ service: "cohort", channel: "C1", agentRoot: ROOT });
230
- assert.equal(v.allowed, false);
231
- assert.equal(v.reason, "no-clock");
232
- });
233
-
234
- test("fs is injectable end to end — no real filesystem needed", () => {
235
- let stored = null;
236
- const deps = {
237
- readImpl: () => stored,
238
- writeImpl: (_p, obj) => { stored = obj; },
239
- };
240
- assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT, deps }).allowed, true);
241
- assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + 1, agentRoot: ROOT, deps }).allowed, false);
242
- assert.equal(stored.rooms[roomKey({ service: "cohort", channel: "C1" })], T0);
243
- });
244
- });
245
-
246
- describe("claimRoomInterim commit:false — ask without spending", () => {
247
- let ROOT;
248
- beforeEach(() => {
249
- if (ROOT) { try { rmSync(ROOT, { recursive: true, force: true }); } catch { /* */ } }
250
- ROOT = mkdtempSync(join(tmpdir(), "room-budget-peek-"));
251
- });
252
-
253
- test("a peek does not spend the budget", () => {
254
- // The interim's text comes from a model call that can fail. Spending the
255
- // room's fifteen minutes BEFORE the message exists gags the room in
256
- // exchange for nothing — so the caller asks first, composes, then claims.
257
- const peek = claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT, commit: false });
258
- assert.equal(peek.allowed, true);
259
- assert.equal(peek.committed, false);
260
- assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + 1, agentRoot: ROOT }).allowed, true, "the peek left the budget unspent");
261
- });
262
-
263
- test("a peek still reports a spent room", () => {
264
- claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT });
265
- const peek = claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + 1000, agentRoot: ROOT, commit: false });
266
- assert.equal(peek.allowed, false);
267
- assert.equal(peek.reason, "room-budget-spent");
268
- });
269
-
270
- test("a committing claim says so", () => {
271
- assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT }).committed, true);
272
- });
273
- });
274
-
275
- // ---------------------------------------------------------------------------
276
- // The HONEST SCOPE of the guarantee
277
- // ---------------------------------------------------------------------------
278
-
279
- describe("the budget is per (SEAT, service, channel) — not per (service, channel)", () => {
280
- let A, B;
281
- beforeEach(() => {
282
- A = mkdtempSync(join(tmpdir(), "room-seat-a-"));
283
- B = mkdtempSync(join(tmpdir(), "room-seat-b-"));
284
- });
285
-
286
- test("two seats in ONE room each get their own interim, because they share no store", () => {
287
- // This is the arithmetic the module header now states rather than glosses.
288
- // `agentRoot` is AGENT_DIR — one agent's repo on one Mac mini. There is no
289
- // shared filesystem between seats, so the guarantee this module can make is
290
- // per seat. The measured room had EIGHT seats in it: the ceiling this gate
291
- // alone imposes there is 8 x (60/15) = 32 interims an hour, against a
292
- // measured content-free rate of ~14/h. It is the backstop, not the thing
293
- // that removed the measured flood.
294
- //
295
- // Pinned as a TEST rather than left in prose because the comment that said
296
- // otherwise survived a review: an operator who reads "one room, one message"
297
- // will size the next change wrong.
298
- assert.equal(claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0, agentRoot: A }).allowed, true);
299
- assert.equal(claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0 + 1, agentRoot: B }).allowed, true,
300
- "a second SEAT is not gagged by the first — there is no store between them");
301
- // …and within either seat the guarantee still holds absolutely.
302
- assert.equal(claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0 + 2, agentRoot: A }).allowed, false);
303
- assert.equal(claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0 + 3, agentRoot: B }).allowed, false);
304
- });
305
-
306
- test("the eight-seat ceiling is exactly eight per window, and no more", () => {
307
- const seats = [];
308
- for (let i = 0; i < 8; i++) seats.push(mkdtempSync(join(tmpdir(), `room-seat-${i}-`)));
309
- let spoke = 0;
310
- // Each seat tries ten times inside one window; each may speak once.
311
- for (const seat of seats) {
312
- for (let i = 0; i < 10; i++) {
313
- if (claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0 + i, agentRoot: seat }).allowed) spoke++;
314
- }
315
- }
316
- assert.equal(spoke, 8, "eight seats, eight interims per window — the honest ceiling");
317
- for (const seat of seats) rmSync(seat, { recursive: true, force: true });
318
- });
319
- });
320
-
321
- describe("the window default is resolved at the EDGE, never captured at import", () => {
322
- // The gate report for this package claimed the pure modules read no
323
- // clock/env/fs. A `const X = process.env.Y` at module scope falsified that
324
- // and, more concretely, meant an operator who exported the variable after the
325
- // first import silently got the stale default.
326
- test("roomInterimWindowMs reads the env it is given, every time it is called", () => {
327
- assert.equal(roomInterimWindowMs({}), DEFAULT_ROOM_INTERIM_WINDOW_MS);
328
- assert.equal(roomInterimWindowMs({ ASSURANCE_ROOM_INTERIM_WINDOW_MS: "60000" }), 60_000);
329
- assert.equal(roomNoticeWindowMs({ ASSURANCE_ROOM_NOTICE_WINDOW_MS: "1000" }), 1000);
330
- // Garbage falls back rather than producing NaN, which would compare false
331
- // against every elapsed time and make the budget decorative.
332
- assert.equal(roomInterimWindowMs({ ASSURANCE_ROOM_INTERIM_WINDOW_MS: "soon" }), DEFAULT_ROOM_INTERIM_WINDOW_MS);
333
- assert.equal(roomNoticeWindowMs(undefined) > 0, true);
334
- });
335
-
336
- test("an env set AFTER this module was first imported still takes effect", () => {
337
- const ROOT = mkdtempSync(join(tmpdir(), "room-env-"));
338
- const prev = process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS;
339
- try {
340
- process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS = "1000";
341
- assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT }).allowed, true);
342
- assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + 999, agentRoot: ROOT }).allowed, false);
343
- assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + 1000, agentRoot: ROOT }).allowed, true,
344
- "the one-second window was honoured, so the value was not folded at import time");
345
- } finally {
346
- if (prev === undefined) delete process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS;
347
- else process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS = prev;
348
- rmSync(ROOT, { recursive: true, force: true });
349
- }
350
- });
351
-
352
- test("the pure decision never reads the environment — it defaults to the CONSTANT", () => {
353
- const prev = process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS;
354
- try {
355
- process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS = "1000";
356
- const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
357
- assert.equal(
358
- interimAllowed({ ledger: led, service: "cohort", channel: "C1", now: T0 + 2000 }).allowed,
359
- false,
360
- "the pure function used the 15-minute constant, not the environment",
361
- );
362
- } finally {
363
- if (prev === undefined) delete process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS;
364
- else process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS = prev;
365
- }
366
- });
367
- });
368
-
369
- // ---------------------------------------------------------------------------
370
- // Identical outcome notices
371
- // ---------------------------------------------------------------------------
372
-
373
- describe("textDigest / noticeKey", () => {
374
- test("the same sentence digests the same however it is spaced", () => {
375
- assert.equal(textDigest("Couldn't finish this — it timed out."), textDigest(" Couldn't finish this — it timed out. "));
376
- });
377
-
378
- test("a different sentence digests differently", () => {
379
- assert.notEqual(textDigest("Hit a problem — it timed out. Retrying now."), textDigest("Couldn't finish this — it timed out."));
380
- });
381
-
382
- test("the key separates room, notice id and sentence", () => {
383
- const base = { service: "cohort", channel: "C1", notice: "failure:final", text: "same" };
384
- assert.notEqual(noticeKey(base), noticeKey({ ...base, channel: "C2" }));
385
- assert.notEqual(noticeKey(base), noticeKey({ ...base, notice: "failure:retrying" }));
386
- assert.notEqual(noticeKey(base), noticeKey({ ...base, text: "different" }));
387
- assert.equal(noticeKey(base), noticeKey({ ...base }));
388
- });
389
- });
390
-
391
- describe("claimRoomNotice — a byte-identical outcome notice is not said twice in one room", () => {
392
- let ROOT;
393
- const STALE = "Couldn't finish this — the work never came back with a result and has now outrun its time limit. I've stopped retrying and flagged it so it isn't lost.";
394
- beforeEach(() => { ROOT = mkdtempSync(join(tmpdir(), "room-notice-")); });
395
-
396
- test("THE SIBLING CASE: twenty debts, one sentence, one message", () => {
397
- // The per-obligation notice ledger cannot reach this: twenty separate asks
398
- // are twenty separate records, and the stale sentence carries nothing about
399
- // which ask it is, so the reader gets the identical line twenty times and
400
- // cannot tell any of them apart.
401
- let said = 0;
402
- for (let i = 0; i < 20; i++) {
403
- if (claimRoomNotice({ service: "cohort", channel: "C-CAPFORM", notice: "failure:final", text: STALE, now: T0 + i * 1000, agentRoot: ROOT }).allowed) said++;
404
- }
405
- assert.equal(said, 1, `twenty identical failure sentences must reach one room once — got ${said}`);
406
- });
407
-
408
- test("a DIFFERENT sentence is never suppressed, at any volume", () => {
409
- // This is the safety argument. Suppressing information a reader does not
410
- // have re-creates the original bug: a person never told their work died.
411
- let said = 0;
412
- for (let i = 0; i < 20; i++) {
413
- const text = `Couldn't finish this — cause number ${i}. I've stopped retrying.`;
414
- if (claimRoomNotice({ service: "cohort", channel: "C-CAPFORM", notice: "failure:final", text, now: T0 + i * 1000, agentRoot: ROOT }).allowed) said++;
415
- }
416
- assert.equal(said, 20, "every distinct fact is still said");
417
- });
418
-
419
- test("'a retry is running' and 'I have stopped' stay separately sayable", () => {
420
- const retrying = "Hit a problem — it timed out. Retrying now; if it fails again I'll come straight back.";
421
- assert.equal(claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:retrying", text: retrying, now: T0, agentRoot: ROOT }).allowed, true);
422
- assert.equal(claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0 + 1, agentRoot: ROOT }).allowed, true);
423
- });
424
-
425
- test("a different ROOM is never gagged by a busy one", () => {
426
- claimRoomNotice({ service: "cohort", channel: "busy", notice: "failure:final", text: STALE, now: T0, agentRoot: ROOT });
427
- assert.equal(claimRoomNotice({ service: "cohort", channel: "quiet", notice: "failure:final", text: STALE, now: T0 + 1, agentRoot: ROOT }).allowed, true);
428
- });
429
-
430
- test("the window reopens, because the same fact a day later is news again", () => {
431
- claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0, agentRoot: ROOT });
432
- assert.equal(claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0 + DEFAULT_ROOM_NOTICE_WINDOW_MS - 1, agentRoot: ROOT }).allowed, false);
433
- assert.equal(claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0 + DEFAULT_ROOM_NOTICE_WINDOW_MS, agentRoot: ROOT }).allowed, true);
434
- });
435
-
436
- test("commit:false asks without spending", () => {
437
- const peek = claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0, agentRoot: ROOT, commit: false });
438
- assert.equal(peek.allowed, true);
439
- assert.equal(peek.committed, false);
440
- assert.equal(claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0 + 1, agentRoot: ROOT }).allowed, true,
441
- "the peek left it unspent — a send that never happened must not silence the next one");
442
- });
443
-
444
- test("FAIL-OPEN, and the OPPOSITE way to the interim: no clock ⇒ say it", () => {
445
- // An interim is a courtesy and is safe to drop. "Your work died" is not.
446
- const v = claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, agentRoot: ROOT });
447
- assert.equal(v.allowed, true);
448
- assert.equal(v.degraded, true);
449
- });
450
-
451
- test("an unreadable ledger lets the notice through, flagged", () => {
452
- mkdirSync(join(ROOT, "state", "obligations"), { recursive: true });
453
- writeFileSync(ledgerPath(ROOT), "{ not json");
454
- const v = claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0, agentRoot: ROOT });
455
- assert.equal(v.allowed, true);
456
- assert.equal(v.degraded, true);
457
- });
458
-
459
- test("interims and notices share one file without evicting each other", () => {
460
- claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT });
461
- claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0 + 1, agentRoot: ROOT });
462
- const led = JSON.parse(readFileSync(ledgerPath(ROOT), "utf-8"));
463
- assert.equal(Object.keys(led.rooms).length, 1, "the interim spend survived the notice write");
464
- assert.equal(Object.keys(led.notices).length, 1);
465
- assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + 2, agentRoot: ROOT }).allowed, false);
466
- assert.equal(claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0 + 3, agentRoot: ROOT }).allowed, false);
467
- });
468
-
469
- test("the notice map is pruned too, so the file cannot grow without bound", () => {
470
- let led = _emptyLedger();
471
- for (let i = 0; i < 50; i++) {
472
- led = recordNotice({ ledger: led, service: "cohort", channel: "C1", notice: "failure:final", text: `old ${i}`, now: T0 });
473
- }
474
- const pruned = pruneLedger({ ledger: led, now: T0 + 3 * DEFAULT_ROOM_NOTICE_WINDOW_MS });
475
- assert.equal(Object.keys(pruned.notices).length, 0);
476
- });
477
- });
478
-
479
- describe("the ledger basename is exported, because its reader has to skip it", () => {
480
- test("LEDGER_BASENAME is the file ledgerPath produces", () => {
481
- // `assurance.listObligations` parses every *.json in this directory as a
482
- // debt record. It skips this one BY NAME, so the name must be one string.
483
- assert.equal(ledgerPath("/tmp/x").endsWith(`/${LEDGER_BASENAME}`), true);
484
- assert.equal(LEDGER_BASENAME, "room-budget.json");
485
- });
486
- });
@@ -1,174 +0,0 @@
1
- /**
2
- * tier.test.mjs — the reply tier, across the whole classifier matrix.
3
- *
4
- * The tier is the ONE decision that says whether a human hears anything before
5
- * the answer. Getting it wrong in the loud direction is the 3,069 generic acks
6
- * and 961 clock-derived nags measured in production on 2026-09-12; getting it
7
- * wrong in the quiet direction is the silence the obligation ledger was built
8
- * to end. So every cell of the matrix is pinned here rather than sampled.
9
- *
10
- * Run: node --test lib/assurance/tier.test.mjs
11
- */
12
-
13
- import { test, describe } from "node:test";
14
- import assert from "node:assert/strict";
15
-
16
- import {
17
- replyTier,
18
- TIERS,
19
- ANSWER_MAX_RUNG,
20
- PLAN_MIN_RUNG,
21
- PLAN_ACTIONS,
22
- PLAN_PRIORITIES,
23
- } from "./tier.mjs";
24
-
25
- /** Every value `classifier.mjs` can emit — the real enums, not a sample. */
26
- const ACTIONS = ["respond", "draft", "research", "queue", "archive", "ignore"];
27
- const PRIORITIES = ["critical", "high", "normal", "ignore"];
28
- /** Every rung in `lib/execution/route.RUNGS`, plus "not routed yet". */
29
- const RUNGS = [0, 1, 2, 3, 4, 5, null];
30
-
31
- describe("replyTier — the three tiers and nothing else", () => {
32
- test("every cell of the matrix returns one of exactly three tiers", () => {
33
- for (const action of ACTIONS) {
34
- for (const priority of PRIORITIES) {
35
- for (const rung of RUNGS) {
36
- for (const answerable of [true, false, null, undefined]) {
37
- for (const willSpawnSession of [true, false]) {
38
- const t = replyTier({ answerable, action, priority, rung, willSpawnSession });
39
- assert.ok(TIERS.includes(t), `{${action}/${priority}/rung:${rung}/answerable:${answerable}/spawn:${willSpawnSession}} → ${t}`);
40
- }
41
- }
42
- }
43
- }
44
- }
45
- });
46
-
47
- test("no session spawning ⇒ answer tier, whatever the classifier said", () => {
48
- // The reply arrives in this turn. An interim in front of an answer the
49
- // human is about to read is the definition of content-free traffic.
50
- for (const action of ACTIONS) {
51
- for (const priority of PRIORITIES) {
52
- for (const rung of RUNGS) {
53
- assert.equal(
54
- replyTier({ answerable: false, action, priority, rung, willSpawnSession: false }),
55
- "answer",
56
- `${action}/${priority}/rung:${rung} with no session must be "answer"`,
57
- );
58
- }
59
- }
60
- }
61
- });
62
-
63
- test("answerable at rung 0 or 1 is the answer tier — the reply IS the acknowledgement", () => {
64
- // `willSpawnSession` UNKNOWN: the caller is asking hypothetically (which is
65
- // how WP-2's effort router will use it), so the classifier verdict decides.
66
- for (const rung of [0, ANSWER_MAX_RUNG]) {
67
- assert.equal(replyTier({ answerable: true, action: "respond", priority: "high", rung }), "answer");
68
- assert.equal(replyTier({ answerable: true, action: "respond", priority: "high", rung, willSpawnSession: false }), "answer");
69
- }
70
- });
71
-
72
- test("REGRESSION: an ASSERTED session disqualifies the answer tier, however answerable the item looked", () => {
73
- // THE QUICK-PATH FALL-THROUGH. agent-daemon.mjs tries a quick reply first;
74
- // when that reply fails transiently or is blocked by validation it falls
75
- // THROUGH to a full session dispatch and asks for a tier with
76
- // willSpawnSession:true while classResult.answerable is still true. The
77
- // answer tier's whole premise — "the reply arrives in this turn" — is
78
- // exactly what the fall-through has falsified.
79
- //
80
- // Latent rather than live only because `rung` is null today (R13). The
81
- // moment WP-2 routes it, that item would have landed in `answer`,
82
- // shouldAcknowledge would have returned ack:false, the sweep would have
83
- // read the durable tier as never-speak, and a 15-45 minute session would
84
- // have run with the human hearing NOTHING at all. Pinned before WP-2 lands
85
- // on top of it.
86
- for (const rung of [0, ANSWER_MAX_RUNG]) {
87
- assert.equal(
88
- replyTier({ answerable: true, action: "respond", priority: "high", rung, willSpawnSession: true }),
89
- "work",
90
- `rung ${rung}: a spawning session speaks once at most — it is never silent`,
91
- );
92
- }
93
- // …and the plan tier still outranks it, because rung 3+ is a project.
94
- assert.equal(replyTier({ answerable: true, action: "respond", priority: "high", rung: 4, willSpawnSession: true }), "plan");
95
- });
96
-
97
- test("a routed rung of 3 or above is the plan tier, whatever the action", () => {
98
- for (const rung of RUNGS.filter((r) => r != null && r >= PLAN_MIN_RUNG)) {
99
- for (const action of ACTIONS) {
100
- assert.equal(
101
- replyTier({ answerable: false, action, priority: "normal", rung, willSpawnSession: true }),
102
- "plan",
103
- `rung ${rung} / ${action} must be "plan"`,
104
- );
105
- }
106
- }
107
- });
108
-
109
- test("research or draft at critical/high priority is the plan tier even with no routed rung", () => {
110
- for (const action of PLAN_ACTIONS) {
111
- for (const priority of PLAN_PRIORITIES) {
112
- assert.equal(replyTier({ answerable: false, action, priority, rung: null, willSpawnSession: true }), "plan");
113
- }
114
- }
115
- });
116
-
117
- test("…and the same actions at normal priority are only work", () => {
118
- for (const action of PLAN_ACTIONS) {
119
- for (const priority of ["normal", "ignore"]) {
120
- assert.equal(replyTier({ answerable: false, action, priority, rung: null, willSpawnSession: true }), "work");
121
- }
122
- }
123
- });
124
-
125
- test("the ordinary directed ask — respond/high, unrouted — is work: silence, then at most one line", () => {
126
- // This is the shape of the overwhelming majority of the 10,667 measured
127
- // agent messages. It must NOT be plan tier, or the flood returns wearing a
128
- // better costume.
129
- assert.equal(replyTier({ answerable: false, action: "respond", priority: "high", rung: null, willSpawnSession: true }), "work");
130
- assert.equal(replyTier({ answerable: false, action: "queue", priority: "critical", rung: null, willSpawnSession: true }), "work");
131
- });
132
-
133
- test("an ABSENT rung is never read as the cheapest rung", () => {
134
- // `lib/backlog` already learned this: absence is not rung 0. A missing rung
135
- // may not buy the answer tier's silence-on-the-strength-of-a-quick-path,
136
- // and may not buy the plan tier's licence to speak either.
137
- assert.equal(replyTier({ answerable: true, action: "respond", priority: "high", rung: null, willSpawnSession: true }), "work");
138
- assert.equal(replyTier({ answerable: true, action: "respond", priority: "high", rung: undefined, willSpawnSession: true }), "work");
139
- });
140
-
141
- test("a non-numeric or out-of-range rung is treated as unrouted, never as a licence to speak", () => {
142
- for (const rung of ["3", "team", NaN, Infinity, -1, 6, {}, []]) {
143
- assert.equal(
144
- replyTier({ answerable: false, action: "respond", priority: "normal", rung, willSpawnSession: true }),
145
- "work",
146
- `rung ${JSON.stringify(rung)} must not be trusted`,
147
- );
148
- }
149
- });
150
-
151
- test("answerable only counts when it is strictly true", () => {
152
- // classifier.mjs coerces a missing/odd field to false for the same reason:
153
- // never guess "answerable".
154
- for (const answerable of ["true", 1, {}, null, undefined]) {
155
- assert.equal(replyTier({ answerable, action: "respond", priority: "high", rung: 0, willSpawnSession: true }), "work");
156
- }
157
- });
158
-
159
- test("garbage in gives work out — the tier never throws and never invents silence", () => {
160
- // Fail-open direction: the tier that can still speak, never the tier that
161
- // cannot. A classifier failure must not silently mute the agent.
162
- assert.equal(replyTier(), "work");
163
- assert.equal(replyTier(null), "work");
164
- assert.equal(replyTier({}), "work");
165
- assert.equal(replyTier({ action: 12, priority: [], rung: "x", willSpawnSession: "yes" }), "work");
166
- });
167
-
168
- test("the tier is a pure function — same input, same answer, no ambient reads", () => {
169
- const args = { answerable: false, action: "research", priority: "critical", rung: null, willSpawnSession: true };
170
- const first = replyTier(args);
171
- for (let i = 0; i < 50; i++) assert.equal(replyTier(args), first);
172
- assert.deepEqual(args, { answerable: false, action: "research", priority: "critical", rung: null, willSpawnSession: true }, "the argument is not mutated");
173
- });
174
- });