@cohortapp/agent-sdk 2.17.0 → 2.18.5

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