@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
@@ -1,165 +0,0 @@
1
- import { test } from "node:test";
2
- import assert from "node:assert/strict";
3
-
4
- import { boardPriority, boardTitle, closeStageFor, closeBoardItem } from "./board-mirror.mjs";
5
-
6
- // ---------------------------------------------------------------------------
7
- // This file used to open AND close a board row of its own through
8
- // `board.create` + `board.claim` + `board.complete`. That was a second
9
- // implementation of a lifecycle hq and this repo already share exactly one of
10
- // (`lib/org/work-ledger.mjs` → `board.track` → hq `server/work/ledger.ts`), and
11
- // the daemon called BOTH on the same item — two cards per ask on one board, one
12
- // of them carrying no requester, no comments and no provenance. Worse,
13
- // `board.create` gives a channel-less row ORG-WIDE visibility and the mirror
14
- // sent no channel for Slack/email/voice, so a private ask's topic was published
15
- // to the whole workspace.
16
- //
17
- // What survives is the CLOSE hook, because `closeObligation` is the funnel every
18
- // terminal path goes through and only two of them reach the daemon's onClose.
19
- // These tests pin that it goes through the SHARED ledger, that it never
20
- // overstates, and that it still fails open.
21
- // ---------------------------------------------------------------------------
22
-
23
- const CFG = { org: { cohort: { enabled: true, base: "https://hq.example", token: "t" } } };
24
- const okCfg = { loadOrgConfig: () => CFG };
25
-
26
- /** A recording ledger seam. Answers the way `work-ledger.mjs` does. */
27
- function ledger(over = {}) {
28
- const seen = { calls: [] };
29
- return {
30
- seen,
31
- deps: {
32
- ...okCfg,
33
- recordWorkStep: async (a) => {
34
- seen.calls.push(a);
35
- if (over.impl) return over.impl(a);
36
- return { tracked: true, reason: "ok", taskId: "task_1", col: a.stage === "done" ? "done" : "blocked" };
37
- },
38
- },
39
- };
40
- }
41
-
42
- const REC = {
43
- key: "cohort:dm:C1:9",
44
- state: "answered",
45
- summary: "compiling the regulatory filing calendar",
46
- item: {
47
- id: "cohort-msg_1",
48
- raw_ref: "cohort:messaging:msg_1:1",
49
- service: "cohort",
50
- channel_id: "chan_1",
51
- content: "can you compile the regulatory filing calendar",
52
- },
53
- };
54
-
55
- test("the title is the topic the acknowledgement already speaks aloud", () => {
56
- assert.equal(
57
- boardTitle({ summary: "compiling the regulatory filing calendar." }),
58
- "Compiling the regulatory filing calendar"
59
- );
60
- // Falls back through the item, then to something honest.
61
- assert.equal(boardTitle({ item: { subject: "Q3 numbers" } }), "Q3 numbers");
62
- assert.equal(boardTitle({}), "Responding to an inbound request");
63
- assert.ok(boardTitle({ summary: "x".repeat(400) }).length <= 120);
64
- });
65
-
66
- test("the classifier's priority words are mapped onto hq's P-scale, which is all it accepts", () => {
67
- // hq validates priority against z.enum(["P0".."P4"]); "critical" 400s the
68
- // whole call, so the obligation vocabulary has to be translated here.
69
- assert.equal(boardPriority("critical"), "P0");
70
- assert.equal(boardPriority("high"), "P1");
71
- assert.equal(boardPriority("normal"), "P2");
72
- assert.equal(boardPriority("ignore"), "P3");
73
- assert.equal(boardPriority("P1"), "P1");
74
- // No mapping → omit, and let hq's own default (P2) answer.
75
- assert.equal(boardPriority("whatever"), undefined);
76
- assert.equal(boardPriority(undefined), undefined);
77
- });
78
-
79
- test("only `answered` closes the row as done — every other verdict blocks it", () => {
80
- assert.equal(closeStageFor("answered"), "done");
81
- assert.equal(closeStageFor(undefined), "done");
82
- for (const bad of ["failed", "undeliverable", "stale", "anything-else"]) {
83
- assert.equal(closeStageFor(bad), "failed", `${bad} must not read as delivered`);
84
- }
85
- });
86
-
87
- test("the close goes through the SHARED ledger, not a second board writer", async () => {
88
- const { seen, deps } = ledger();
89
- const r = await closeBoardItem({ rec: REC, agentRoot: "/x", deps });
90
-
91
- assert.equal(r.mirrored, true);
92
- assert.equal(r.taskId, "task_1");
93
- assert.equal(seen.calls.length, 1);
94
- const call = seen.calls[0];
95
- // The ledger identifies the ask from the ITEM (service + channel + message),
96
- // and derives the dedupe key server-side — which is what makes this land on
97
- // the SAME row the daemon's `accepted`/`working` steps opened, rather than a
98
- // second card beside it.
99
- assert.equal(call.item, REC.item);
100
- assert.equal(call.stage, "done");
101
- assert.equal(call.deferred, true);
102
- assert.equal(call.cfg, CFG);
103
- assert.match(call.note, /Answered in the conversation/);
104
- });
105
-
106
- test("a failed obligation lands the row blocked, and says why", async () => {
107
- const { seen, deps } = ledger();
108
- await closeBoardItem({ rec: { ...REC, state: "failed", closeNote: "exit 1" }, deps });
109
- assert.equal(seen.calls[0].stage, "failed");
110
- assert.match(seen.calls[0].note, /exit 1/);
111
- assert.match(seen.calls[0].note, /blocked/);
112
- });
113
-
114
- test("an undeliverable obligation says so, and still does not read as delivered", async () => {
115
- const { seen, deps } = ledger();
116
- await closeBoardItem({ rec: { ...REC, state: "undeliverable" }, deps });
117
- assert.equal(seen.calls[0].stage, "failed");
118
- assert.match(seen.calls[0].note, /no way to deliver/);
119
- });
120
-
121
- test("an explicit outcome overrides the record's own state", async () => {
122
- const { seen, deps } = ledger();
123
- await closeBoardItem({ rec: REC, outcome: "failed", deps });
124
- assert.equal(seen.calls[0].stage, "failed");
125
- });
126
-
127
- test("a non-Cohort obligation writes NO row — a private ask is not published org-wide", async () => {
128
- // THE LEAK THIS REPLACED: `board.create` with no channelId produces a row
129
- // every member of the org can read, titled after the ask. The daemon polls
130
- // Slack, two Gmail accounts, telegram, whatsapp and voice. The shared gate
131
- // refuses all of them (`not-a-cohort-channel`) — this test pins that the
132
- // refusal reaches us as a benign no-op rather than being worked around.
133
- const { seen, deps } = ledger({ impl: async () => ({ tracked: false, reason: "not-a-cohort-channel" }) });
134
- const r = await closeBoardItem({
135
- rec: { ...REC, item: { ...REC.item, service: "slack", channel_id: "D0123" } },
136
- deps,
137
- });
138
- assert.equal(r.mirrored, false);
139
- assert.equal(r.reason, "not-a-cohort-channel");
140
- assert.equal(seen.calls.length, 1, "the decision is the ledger's, not a rule re-implemented here");
141
- });
142
-
143
- test("an obligation with no item snapshot is a no-op, not a throw", async () => {
144
- const r = await closeBoardItem({
145
- rec: { key: "k1" },
146
- deps: { ...okCfg, recordWorkStep: async () => { throw new Error("must not be called"); } },
147
- });
148
- assert.equal(r.mirrored, false);
149
- assert.equal(r.reason, "no-item");
150
- });
151
-
152
- test("the close is fail-open: a refusal, a throw and a missing credential all resolve", async () => {
153
- for (const [label, impl] of [
154
- ["refused", async () => ({ tracked: false, reason: "FORBIDDEN_SCOPE: no" })],
155
- ["threw", async () => { throw new Error("socket hang up"); }],
156
- ["disabled", async () => ({ tracked: false, reason: "org-disabled" })],
157
- ]) {
158
- const r = await closeBoardItem({
159
- rec: REC,
160
- deps: { ...okCfg, recordWorkStep: impl },
161
- });
162
- assert.equal(r.mirrored, false, label);
163
- assert.ok(r.reason, `${label} must report why`);
164
- }
165
- });
@@ -1,393 +0,0 @@
1
- /**
2
- * cadence-consumer-frontdoor.test.mjs — the consumer with a live main session
3
- * (design §3.4) and the F8/F9 cadence fixes.
4
- *
5
- * - an escalate/guarded tick is HANDED to a live session (handoff file +
6
- * rendered prompt on disk + tick processed as "handed-to-session"), never
7
- * spawned; inline ticks keep running in the consumer
8
- * - a session that is not live → legacy spawn, unchanged
9
- * - a handoff past its deadline is re-enqueued with metadata.handoffTimedOut
10
- * and that tick is NOT handed off again (legacy spawn)
11
- * - per-cadence in-flight guard: a second tick for a cadence that already
12
- * has an open handoff / running sub-session is deferred, not spawned twice
13
- * - a deferred tick is re-keyed to the tail and tickOnce keeps scanning
14
- * past the deferral instead of breaking (head-of-line block)
15
- *
16
- * Every test drives tickOnce() by hand (pollMs is an hour) with an injected
17
- * spawner, front-door state and clock. No real process is ever launched.
18
- */
19
-
20
- import { test } from "node:test";
21
- import assert from "node:assert/strict";
22
- import { promises as fsp } from "node:fs";
23
- import { writeFileSync, mkdirSync, readFileSync, existsSync, readdirSync, chmodSync } from "node:fs";
24
- import { tmpdir } from "node:os";
25
- import { join, dirname } from "node:path";
26
-
27
- import { enqueueTick, listInbox, busDepth, getBusPaths } from "../../lib/cadence-bus.mjs";
28
- import { listHandoffs, handoffPaths, writeHandoff, ackHandoff, DEFAULT_HANDOFF_DEADLINE_MS, DEFAULT_HANDOFF_RETENTION_MS } from "../../lib/session/handoffs.mjs";
29
- import { startConsumer as startConsumerRaw, renderCadencePromptBody } from "./cadence-consumer.mjs";
30
- import { withParallelism, countParallelism, PARALLELISM_DIRECTIVE } from "../../lib/prompts/parallelism.mjs";
31
-
32
- const ADMIT_ALL = { admit: () => ({ decision: "ADMIT", reason: "test", snapshot: {} }), defaultDeps: () => ({}) };
33
- const RATE_OK = { checkRateLimit: () => ({ allowed: true, retryAt: null }), classifyStderr: () => false, recordRateLimit: () => {}, recordSuccess: () => {} };
34
- const BUDGET_OK = { dailyStatus: () => ({ essentialOnly: false }) };
35
- const LIVE = () => ({ frontDoor: "session", sessionLive: true });
36
- const NOT_LIVE = () => ({ frontDoor: "session", sessionLive: false });
37
-
38
- function startConsumer(opts = {}) {
39
- return startConsumerRaw({ governor: ADMIT_ALL, rateGuard: RATE_OK, budgetGuard: BUDGET_OK, pollMs: 3_600_000, heartbeatMs: 3_600_000, recoveryMs: 3_600_000, budgetEscalateMs: 0, ...opts });
40
- }
41
- async function makeAgentRoot() {
42
- const p = join(tmpdir(), `cadence-frontdoor-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
43
- await fsp.mkdir(p, { recursive: true });
44
- writeFileSync(join(p, "config.json"), "{}");
45
- mkdirSync(join(p, "config"), { recursive: true });
46
- writeFileSync(join(p, "config", "agent.json"), JSON.stringify({ firstName: "Nova", lastName: "Reyes", name: "Nova Reyes" }));
47
- return p;
48
- }
49
- async function rmRoot(p) { try { await fsp.rm(p, { recursive: true, force: true }); } catch { /* */ } }
50
- function plantPrompt(root, cadence, body = `# ${cadence} for {{agent.firstName}}`) {
51
- const path = join(root, "schedules/triggers", `${cadence}.md`);
52
- mkdirSync(dirname(path), { recursive: true });
53
- writeFileSync(path, body);
54
- return path;
55
- }
56
- function processedEvents(root) {
57
- const dir = getBusPaths(root).processed;
58
- const out = [];
59
- if (!existsSync(dir)) return out;
60
- for (const day of readdirSync(dir)) {
61
- for (const f of readdirSync(join(dir, day))) {
62
- if (f.endsWith(".json")) out.push(JSON.parse(readFileSync(join(dir, day, f), "utf-8")));
63
- }
64
- }
65
- return out;
66
- }
67
-
68
- test("live session: an escalate tick is handed to the session — handoff + rendered prompt on disk, no spawn", async () => {
69
- const root = await makeAgentRoot();
70
- plantPrompt(root, "daily-morning-brief");
71
- const spawns = [];
72
- const consumer = startConsumer({
73
- agentRoot: root,
74
- frontDoorState: LIVE,
75
- spawnSession: async (o) => { spawns.push(o); return { ok: true, exit_code: 0, duration_ms: 1 }; },
76
- });
77
- try {
78
- const t = enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
79
- const r = await consumer.tickOnce();
80
- assert.equal(r.processed, 1);
81
- assert.equal(spawns.length, 0, "must not spawn a sub-session while the session is live");
82
- const open = listHandoffs(root);
83
- assert.equal(open.length, 1);
84
- assert.equal(open[0].tickId, t.id);
85
- assert.equal(open[0].cadence, "daily-morning-brief");
86
- assert.equal(open[0].mode, "escalate");
87
- assert.ok(open[0].deadlineAt, "carries a deadline");
88
- assert.ok(existsSync(open[0].promptPath), "the rendered prompt is on disk");
89
- assert.equal(
90
- readFileSync(open[0].promptPath, "utf-8"),
91
- withParallelism("# daily-morning-brief for Nova"),
92
- "identity tokens rendered, and the handed prompt is byte-identical to what a sub-session would have been given",
93
- );
94
- assert.equal(busDepth(root).inbox, 0);
95
- assert.equal(busDepth(root).claimed, 0);
96
- const done = processedEvents(root).find((e) => e.id === t.id);
97
- assert.equal(done.result.decision, "handed-to-session");
98
- assert.equal(consumer.getStats().handed_off, 1);
99
- assert.equal(consumer.getStats().last_decision, "handed-to-session");
100
- } finally { await consumer.stop(); await rmRoot(root); }
101
- });
102
-
103
- test("live session: inline ticks still run in the consumer; not-live → legacy spawn", async () => {
104
- const root = await makeAgentRoot();
105
- plantPrompt(root, "daily-morning-brief");
106
- const spawns = [];
107
- const live = { on: true };
108
- const consumer = startConsumer({
109
- agentRoot: root,
110
- frontDoorState: () => ({ frontDoor: "session", sessionLive: live.on }),
111
- spawnSession: async (o) => { spawns.push(o); return { ok: true, exit_code: 0, duration_ms: 1 }; },
112
- });
113
- try {
114
- enqueueTick({ cadence: "cadence-bus-heartbeat", source: "manual", agentRoot: root });
115
- await consumer.tickOnce();
116
- assert.equal(consumer.getStats().inline, 1);
117
- assert.deepEqual(listHandoffs(root), [], "inline never hands off");
118
-
119
- live.on = false;
120
- enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
121
- await consumer.tickOnce();
122
- assert.equal(spawns.length, 1, "session not live → the legacy lane spawns");
123
- assert.deepEqual(listHandoffs(root), []);
124
- } finally { await consumer.stop(); await rmRoot(root); }
125
- });
126
-
127
- test("handoff timeout: a handoff past its deadline is re-enqueued with handoffTimedOut and then spawned, not handed off again", async () => {
128
- const root = await makeAgentRoot();
129
- plantPrompt(root, "daily-morning-brief");
130
- const spawns = [];
131
- let now = Date.parse("2026-09-08T10:00:00.000Z");
132
- const consumer = startConsumer({
133
- agentRoot: root,
134
- now: () => now,
135
- frontDoorState: LIVE,
136
- spawnSession: async (o) => { spawns.push(o); return { ok: true, exit_code: 0, duration_ms: 1 }; },
137
- });
138
- try {
139
- writeHandoff(root, { tickId: "evt-stuck", cadence: "daily-morning-brief", mode: "escalate", promptPath: "/x", metadata: { reason: "guard:escalate" } }, { now });
140
- now += DEFAULT_HANDOFF_DEADLINE_MS + 1;
141
- await consumer.tickOnce();
142
- assert.equal(consumer.getStats().handoff_timeouts, 1);
143
- assert.deepEqual(listHandoffs(root), [], "the stuck handoff is retired");
144
- assert.equal(JSON.parse(readFileSync(join(handoffPaths(root).done, "evt-stuck.json"), "utf-8")).status, "expired");
145
- // The re-enqueued tick ran on the legacy lane in that same tick (or the next).
146
- if (spawns.length === 0) await consumer.tickOnce();
147
- assert.equal(spawns.length, 1, "timed-out handoff → legacy spawn even though the session is live");
148
- assert.equal(spawns[0].cadence, "daily-morning-brief");
149
- const ev = processedEvents(root).find((e) => e.source === "handoff-timeout");
150
- assert.equal(ev.metadata.handoffTimedOut, true);
151
- assert.equal(ev.metadata.handoffTickId, "evt-stuck");
152
- assert.equal(ev.result.decision, "escalated");
153
- assert.deepEqual(listHandoffs(root), [], "never handed off a second time");
154
- } finally { await consumer.stop(); await rmRoot(root); }
155
- });
156
-
157
- test("per-cadence in-flight guard: a cadence with an open handoff is deferred (re-keyed), a different cadence proceeds", async () => {
158
- const root = await makeAgentRoot();
159
- plantPrompt(root, "daily-morning-brief");
160
- plantPrompt(root, "meeting-action-capture");
161
- const consumer = startConsumer({ agentRoot: root, frontDoorState: LIVE, spawnSession: async () => ({ ok: true, exit_code: 0 }) });
162
- try {
163
- const a = enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
164
- await consumer.tickOnce();
165
- assert.equal(listHandoffs(root).length, 1);
166
- await new Promise((r) => setTimeout(r, 2));
167
- const a2 = enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
168
- await new Promise((r) => setTimeout(r, 2));
169
- enqueueTick({ cadence: "meeting-action-capture", source: "launchd", agentRoot: root });
170
- await consumer.tickOnce();
171
- const open = listHandoffs(root);
172
- assert.deepEqual(open.map((h) => h.cadence).sort(), ["daily-morning-brief", "meeting-action-capture"], "second cadence handed off; first not duplicated");
173
- assert.equal(open.filter((h) => h.cadence === "daily-morning-brief").length, 1, "the same cadence is never in flight twice");
174
- const inbox = listInbox(root);
175
- assert.equal(inbox.length, 1, "the duplicate tick waits at the tail");
176
- const waiting = JSON.parse(readFileSync(join(getBusPaths(root).inbox, `${inbox[0]}.json`), "utf-8"));
177
- assert.equal(waiting.cadence, "daily-morning-brief");
178
- assert.equal(waiting.metadata.originalId, a2.id);
179
- assert.equal(waiting.metadata.deferrals, 1);
180
- assert.notEqual(inbox[0], a2.id, "re-keyed, not requeued under its own id");
181
- void a;
182
- } finally { await consumer.stop(); await rmRoot(root); }
183
- });
184
-
185
- test("per-cadence in-flight guard: a running sub-session for a cadence blocks a second spawn of the same cadence", async () => {
186
- const root = await makeAgentRoot();
187
- plantPrompt(root, "daily-morning-brief");
188
- let release;
189
- const gate = new Promise((r) => { release = r; });
190
- const spawns = [];
191
- const consumer = startConsumer({
192
- agentRoot: root,
193
- frontDoorState: NOT_LIVE,
194
- spawnSession: async (o) => { spawns.push(o.cadence); await gate; return { ok: true, exit_code: 0 }; },
195
- });
196
- try {
197
- enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
198
- const first = consumer.tickOnce(); // holds the spawn open
199
- await new Promise((r) => setTimeout(r, 30));
200
- assert.equal(spawns.length, 1);
201
- await new Promise((r) => setTimeout(r, 2));
202
- enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
203
- await consumer.tickOnce();
204
- assert.equal(spawns.length, 1, "no second spawn while the first is running");
205
- assert.equal(listInbox(root).length, 1, "the second tick is deferred to the tail");
206
- release();
207
- await first;
208
- } finally { await consumer.stop(); await rmRoot(root); }
209
- });
210
-
211
- test("head-of-line: a circuit-open deferral is re-keyed to the tail and tickOnce keeps scanning to the next cadence", async () => {
212
- const root = await makeAgentRoot();
213
- plantPrompt(root, "weekly-strategic-memo");
214
- plantPrompt(root, "daily-morning-brief");
215
- const spawns = [];
216
- const consumer = startConsumer({
217
- agentRoot: root,
218
- frontDoorState: NOT_LIVE,
219
- backoffSchedule: [0, 0, 0],
220
- circuitThreshold: 1,
221
- circuitDurationMs: 60_000,
222
- maxAttempts: 1,
223
- spawnSession: async (o) => {
224
- spawns.push(o.cadence);
225
- if (o.cadence === "weekly-strategic-memo") return { ok: false, exit_code: 1, error: "fail", duration_ms: 1 };
226
- return { ok: true, exit_code: 0, duration_ms: 1 };
227
- },
228
- });
229
- try {
230
- enqueueTick({ cadence: "weekly-strategic-memo", source: "launchd", agentRoot: root });
231
- await consumer.tickOnce(); // fails once → circuit opens for weekly-strategic-memo
232
- assert.equal(consumer.getStats().spawn_failures, 1);
233
-
234
- const held = enqueueTick({ cadence: "weekly-strategic-memo", source: "launchd", agentRoot: root });
235
- await new Promise((r) => setTimeout(r, 2));
236
- enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
237
- const r = await consumer.tickOnce();
238
- // Before: the circuit skip broke the loop and daily-morning-brief waited a
239
- // whole poll behind a tick that could not run. Now both are visited.
240
- assert.equal(r.processed, 2, `both ticks visited in one tick (stats=${JSON.stringify(consumer.getStats())})`);
241
- assert.deepEqual(spawns, ["weekly-strategic-memo", "daily-morning-brief"]);
242
- assert.equal(consumer.getStats().skipped_circuit_open, 1);
243
- const inbox = listInbox(root);
244
- assert.equal(inbox.length, 1, "the held tick is back in the inbox, once");
245
- assert.notEqual(inbox[0], held.id, "under a NEW id (tail), not its own");
246
- const on = JSON.parse(readFileSync(join(getBusPaths(root).inbox, `${inbox[0]}.json`), "utf-8"));
247
- assert.equal(on.metadata.originalId, held.id);
248
- assert.equal(on.metadata.deferrals, 1);
249
- // A further tick with ONLY the held cadence queued does not spin: one visit, one re-key, then it stops.
250
- const r2 = await consumer.tickOnce();
251
- assert.equal(r2.processed, 1);
252
- assert.equal(listInbox(root).length, 1);
253
- assert.equal(JSON.parse(readFileSync(join(getBusPaths(root).inbox, `${listInbox(root)[0]}.json`), "utf-8")).metadata.deferrals, 2);
254
- } finally { await consumer.stop(); await rmRoot(root); }
255
- });
256
-
257
- test("a broken front-door reader fails open to the legacy lane", async () => {
258
- const root = await makeAgentRoot();
259
- plantPrompt(root, "daily-morning-brief");
260
- const spawns = [];
261
- const consumer = startConsumer({
262
- agentRoot: root,
263
- frontDoorState: () => { throw new Error("boom"); },
264
- spawnSession: async (o) => { spawns.push(o); return { ok: true, exit_code: 0 }; },
265
- });
266
- try {
267
- enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
268
- await consumer.tickOnce();
269
- assert.equal(spawns.length, 1);
270
- assert.deepEqual(listHandoffs(root), []);
271
- } finally { await consumer.stop(); await rmRoot(root); }
272
- });
273
-
274
- test("handoff timeout: when the re-enqueue fails the handoff stays OPEN and is retried by the next sweep — the cadence run is never lost", async () => {
275
- if (process.getuid && process.getuid() === 0) return; // root ignores directory modes
276
- const root = await makeAgentRoot();
277
- plantPrompt(root, "daily-morning-brief");
278
- let now = Date.parse("2026-09-08T10:00:00.000Z");
279
- const consumer = startConsumer({ agentRoot: root, now: () => now, frontDoorState: LIVE, spawnSession: async () => ({ ok: true, exit_code: 0, duration_ms: 1 }) });
280
- try {
281
- writeHandoff(root, { tickId: "evt-stuck", cadence: "daily-morning-brief", mode: "escalate", promptPath: "/x" }, { now });
282
- now += DEFAULT_HANDOFF_DEADLINE_MS + 1;
283
- const inbox = getBusPaths(root).inbox;
284
- chmodSync(inbox, 0o500); // the bus inbox is unwritable for a minute
285
- try {
286
- await consumer.tickOnce();
287
- assert.deepEqual(listHandoffs(root).map((h) => h.tickId), ["evt-stuck"], "not retired while the tick could not be re-enqueued");
288
- assert.equal(existsSync(join(handoffPaths(root).done, "evt-stuck.json")), false);
289
- assert.equal(consumer.getStats().handoff_timeouts, 0);
290
- } finally { chmodSync(inbox, 0o755); }
291
- await consumer.tickOnce();
292
- assert.deepEqual(listHandoffs(root), [], "retired once the re-enqueue succeeded");
293
- assert.equal(consumer.getStats().handoff_timeouts, 1);
294
- assert.equal(JSON.parse(readFileSync(join(handoffPaths(root).done, "evt-stuck.json"), "utf-8")).status, "expired");
295
- } finally { await consumer.stop(); await rmRoot(root); }
296
- });
297
-
298
- test("circuit/backoff skip: re-keyed to the tail but NOT counted as a retry or a deferral in the health stats", async () => {
299
- const root = await makeAgentRoot();
300
- plantPrompt(root, "weekly-strategic-memo");
301
- const consumer = startConsumer({
302
- agentRoot: root,
303
- frontDoorState: NOT_LIVE,
304
- backoffSchedule: [0, 0, 0],
305
- circuitThreshold: 1,
306
- circuitDurationMs: 60_000,
307
- maxAttempts: 1,
308
- spawnSession: async () => ({ ok: false, exit_code: 1, error: "fail", duration_ms: 1 }),
309
- });
310
- try {
311
- enqueueTick({ cadence: "weekly-strategic-memo", source: "launchd", agentRoot: root });
312
- await consumer.tickOnce(); // opens the circuit
313
- const before = consumer.getStats();
314
- enqueueTick({ cadence: "weekly-strategic-memo", source: "launchd", agentRoot: root });
315
- for (let i = 0; i < 5; i++) await consumer.tickOnce(); // five polls inside the circuit window
316
- const after = consumer.getStats();
317
- assert.equal(after.skipped_circuit_open, 5);
318
- assert.equal(after.retries, before.retries, "a held-back cadence was never attempted — not a retry");
319
- assert.equal(after.deferred, before.deferred, "nor a governance deferral");
320
- assert.equal(listInbox(root).length, 1, "the held tick is still waiting, once");
321
- } finally { await consumer.stop(); await rmRoot(root); }
322
- });
323
-
324
- test("handoff ledger hygiene: the sweep prunes done/ records and prompts past retention, at most once an hour", async () => {
325
- const root = await makeAgentRoot();
326
- let now = Date.parse("2026-09-08T10:00:00.000Z");
327
- const consumer = startConsumer({ agentRoot: root, now: () => now, frontDoorState: LIVE, spawnSession: async () => ({ ok: true, exit_code: 0 }) });
328
- try {
329
- const paths = handoffPaths(root);
330
- mkdirSync(paths.prompts, { recursive: true });
331
- writeFileSync(join(paths.prompts, "evt-done.md"), "# p");
332
- writeHandoff(root, { tickId: "evt-done", cadence: "a", promptPath: join(paths.prompts, "evt-done.md") }, { now });
333
- ackHandoff(root, "evt-done", { now });
334
- now += DEFAULT_HANDOFF_RETENTION_MS + 1000;
335
- await consumer.tickOnce();
336
- assert.equal(existsSync(join(paths.done, "evt-done.json")), false, "pruned by the sweep");
337
- assert.equal(existsSync(join(paths.prompts, "evt-done.md")), false);
338
- } finally { await consumer.stop(); await rmRoot(root); }
339
- });
340
-
341
- // ── the parallelism directive on the escalate/guarded lane (WP-M7 mechanic 5) ──
342
- //
343
- // The spec puts the directive on "every escalate/guarded sub-session prompt".
344
- // Cadence trigger prompts never pass through `prompt-builder.mjs#buildPrompt`
345
- // — they are read straight off schedules/triggers/<name>.md — so this lane has
346
- // its own seam (`renderCadencePromptBody`) and needs its own pins. Without it
347
- // an `escalate` tick whose trigger asks for six independent audits spawns a
348
- // sub-session with no standing permission to fan out.
349
-
350
- test("escalate: the spawned sub-session prompt leads with the parallelism directive, exactly once", async () => {
351
- const root = await makeAgentRoot();
352
- plantPrompt(root, "daily-morning-brief", "# daily-morning-brief for {{agent.firstName}}\n\nAudit six things.");
353
- const spawns = [];
354
- const consumer = startConsumer({
355
- agentRoot: root,
356
- frontDoorState: NOT_LIVE,
357
- spawnSession: async (o) => { spawns.push(o); return { ok: true, exit_code: 0, duration_ms: 1 }; },
358
- });
359
- try {
360
- enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
361
- await consumer.tickOnce();
362
- assert.equal(spawns.length, 1);
363
- // The injected spawner is handed the promptPath, not the body — so assert
364
- // on the seam the real spawner uses to turn that path into argv.
365
- const body = renderCadencePromptBody(root, readFileSync(join(root, spawns[0].promptPath), "utf-8"));
366
- assert.ok(body.startsWith(PARALLELISM_DIRECTIVE), "the directive leads");
367
- assert.equal(countParallelism(body), 1);
368
- assert.ok(body.endsWith("# daily-morning-brief for Nova\n\nAudit six things."), "and the trigger body follows, tokens rendered");
369
- } finally { await consumer.stop(); await rmRoot(root); }
370
- });
371
-
372
- test("a trigger that already carries the directive is not given a second copy", () => {
373
- const body = renderCadencePromptBody("/nonexistent-agent-root", withParallelism("# already-primed"));
374
- assert.equal(countParallelism(body), 1, "idempotent — a repeated directive reads as emphasis and erodes the file-scope rule");
375
- assert.equal(body, withParallelism("# already-primed"));
376
- });
377
-
378
- test("the handed-off prompt and the spawned prompt are the same bytes", async () => {
379
- const root = await makeAgentRoot();
380
- const raw = "# weekly-strategic-memo for {{agent.firstName}}";
381
- plantPrompt(root, "weekly-strategic-memo", raw);
382
- const consumer = startConsumer({ agentRoot: root, frontDoorState: LIVE, spawnSession: async () => ({ ok: true, exit_code: 0 }) });
383
- try {
384
- enqueueTick({ cadence: "weekly-strategic-memo", source: "launchd", agentRoot: root });
385
- await consumer.tickOnce();
386
- const open = listHandoffs(root);
387
- assert.equal(open.length, 1);
388
- const handed = readFileSync(open[0].promptPath, "utf-8");
389
- assert.equal(handed, renderCadencePromptBody(root, raw), "one seam, two lanes");
390
- assert.equal(countParallelism(handed), 1);
391
- assert.ok(handed.startsWith(PARALLELISM_DIRECTIVE));
392
- } finally { await consumer.stop(); await rmRoot(root); }
393
- });