@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,470 +0,0 @@
1
- /**
2
- * scripts/daemon/execution-ladder.test.mjs — the daemon actually ENTERS
3
- * lib/execution/**.
4
- *
5
- * Run: node --test scripts/daemon/execution-ladder.test.mjs
6
- *
7
- * `lib/execution/**` had exactly one production importer (lib/goals/loop.mjs,
8
- * for `routeRung`); `processItem` went from ITEM_RECEIVED straight to the Haiku
9
- * classifier and never entered the ladder. These tests drive the daemon's OWN
10
- * entry point — `processItem` — with the network/subprocess collaborators
11
- * injected, and assert on what lands in `state/execution/journal.jsonl`,
12
- * `state/queues/inbound.yaml` and the fakes. Nothing about intake, match, route,
13
- * disposition, drive or journal is faked.
14
- *
15
- * The two regression tests at the bottom encode bugs a live run of this wiring
16
- * actually produced:
17
- * - with no `need` estimate every DM routed to rung 3, which vetoed the quick
18
- * reply and turned a 4-second answer into a spawned Claude session;
19
- * - with the rule-based directedness override applied to ALL ignore reasons,
20
- * a DM could never be `duplicate` (a DM is always "directed"), so the agent
21
- * answered every redelivery twice.
22
- */
23
- import { test, before, beforeEach } from "node:test";
24
- import assert from "node:assert/strict";
25
- import { register } from "node:module";
26
- import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, rmSync, readdirSync } from "node:fs";
27
- import { join } from "node:path";
28
- import { tmpdir } from "node:os";
29
-
30
- // --- stub the agent-only `imapflow` dep so the daemon module can be imported ---
31
- register(
32
- "data:text/javascript," +
33
- encodeURIComponent(`
34
- export async function resolve(s, c, next) {
35
- if (s === "imapflow") return { url: "imapflow-stub:main", shortCircuit: true };
36
- return next(s, c);
37
- }
38
- export async function load(u, c, next) {
39
- if (u === "imapflow-stub:main") return { format: "module", shortCircuit: true, source: "export class ImapFlow {}" };
40
- return next(u, c);
41
- }
42
- `),
43
- );
44
-
45
- const AGENT_DIR = mkdtempSync(join(tmpdir(), "maestro-exec-ladder-"));
46
- process.env.AGENT_DIR = AGENT_DIR;
47
- process.env.MAESTRO_DAEMON_NO_AUTOSTART = "1";
48
- process.env.COHORT_AGENT_ID = "mem_self_0001";
49
- process.env.ANTHROPIC_API_KEY = "";
50
-
51
- const PLAN = `version: 1
52
- enforcement: enforce
53
- obligations:
54
- - key: react.message.dm
55
- kind: REACT
56
- status: active
57
- trigger: { topic: message, kind: null, predicate: null }
58
- uses: []
59
- allowed_tools: []
60
- action_classes: []
61
- offline_safe: true
62
- - key: react.task.assigned
63
- kind: REACT
64
- status: active
65
- trigger: { topic: task, kind: assigned, predicate: null }
66
- uses: []
67
- allowed_tools: []
68
- action_classes: []
69
- offline_safe: true
70
- `;
71
-
72
- let daemon;
73
- let responder;
74
-
75
- before(async () => {
76
- mkdirSync(join(AGENT_DIR, "config"), { recursive: true });
77
- writeFileSync(
78
- join(AGENT_DIR, "config", "agent.json"),
79
- JSON.stringify({ firstName: "Nova", lastName: "Vale", slackMemberId: "U09NOVA" }),
80
- );
81
- writeFileSync(join(AGENT_DIR, "config", "plan.yaml"), PLAN);
82
- daemon = await import("./agent-daemon.mjs");
83
- responder = await import("./responder.mjs");
84
- });
85
-
86
- beforeEach(() => {
87
- for (const rel of [["state", "execution"], ["state", "queues"], ["state", "inbox"], ["state", "locks"], ["logs", "daemon"]]) {
88
- rmSync(join(AGENT_DIR, ...rel), { recursive: true, force: true });
89
- }
90
- if (daemon) daemon._resetLadderCaches();
91
- delete process.env.DAEMON_EXECUTION_LADDER;
92
- });
93
-
94
- // --- helpers ---------------------------------------------------------------
95
-
96
- function writeLive(item, service) {
97
- const dir = join(AGENT_DIR, "state", "inbox", service);
98
- mkdirSync(dir, { recursive: true });
99
- writeFileSync(join(dir, `${item.id}.json`), JSON.stringify(item));
100
- }
101
-
102
- /** Fakes for exactly the network / subprocess boundary. Nothing else. */
103
- function spy(classResult) {
104
- const seen = { classify: 0, quick: [], dispatch: [], holding: 0 };
105
- return {
106
- seen,
107
- deps: {
108
- classify: async () => {
109
- seen.classify += 1;
110
- return classResult;
111
- },
112
- sendQuickResponse: async (item, _cr, routed) => {
113
- seen.quick.push({ id: item.id, rung: routed ? routed.rung : null });
114
- return { sent: true, text: "ack", via: "fake" };
115
- },
116
- sendHoldingMessage: async () => {
117
- seen.holding += 1;
118
- return { sent: false, holdingText: null };
119
- },
120
- dispatch: (_p, item, _cr, _s, opts) => {
121
- seen.dispatch.push({ id: item.id, execution: item.execution || null });
122
- opts.onClose({ ok: true, code: 0 });
123
- },
124
- },
125
- };
126
- }
127
-
128
- function journal() {
129
- const p = join(AGENT_DIR, "state", "execution", "journal.jsonl");
130
- if (!existsSync(p)) return [];
131
- return readFileSync(p, "utf-8").trim().split("\n").filter(Boolean).map((l) => JSON.parse(l));
132
- }
133
-
134
- const decisions = () => journal().filter((r) => r.event === "decision");
135
- const outcomes = () => journal().filter((r) => r.event === "outcome");
136
-
137
- function dm(over = {}) {
138
- return {
139
- id: "MSG-DM",
140
- raw_ref: "slack:D77:1",
141
- channel: "dm/priya",
142
- channel_id: "D77",
143
- sender: "Priya Raman",
144
- sender_id: "U_PRIYA",
145
- // A work-request ("review") so this shared fixture takes the full classify →
146
- // route path — the fast-path (fastPathClassify) deliberately skips the
147
- // classifier only for PLAIN short questions; that path has its own test below.
148
- content: "can you review and confirm the deadline for the launch plan?",
149
- timestamp: new Date().toISOString(),
150
- ...over,
151
- };
152
- }
153
-
154
- const RESPOND = {
155
- priority: "normal",
156
- action: "respond",
157
- model: "sonnet",
158
- summary: "deadline question",
159
- category: "action_required",
160
- directed_at_agent: true,
161
- };
162
-
163
- // ---------------------------------------------------------------------------
164
- // 1) A DM goes THROUGH the ladder — and is still answered exactly as before.
165
- // ---------------------------------------------------------------------------
166
-
167
- test("LADDER: a DM is journalled intake→match→route→outcome AND still quick-replies", async () => {
168
- const item = dm();
169
- writeLive(item, "slack");
170
- const s = spy(RESPOND);
171
- await daemon.processItem(item, "slack", s.deps);
172
-
173
- const d = decisions();
174
- assert.equal(d.length, 1, "one decision row");
175
- assert.equal(d[0].surface, "dm");
176
- assert.equal(d[0].disposition, "react_now");
177
- assert.equal(d[0].reason, "directed_now");
178
- assert.equal(d[0].obligationKey, "react.message.dm", "match bound the DM to the plan's REACT obligation");
179
- assert.equal(d[0].rung, 0, "route chose rung 0 — one protocol method with the params in hand");
180
- assert.equal(d[0].mechanism, "tool-surface");
181
- assert.ok(d[0].why.some((w) => w.includes("single method messaging.send")), "the why records the rung reasoning");
182
-
183
- const o = outcomes();
184
- assert.equal(o.length, 1);
185
- assert.equal(o[0].effect, "react", "the react effect ran");
186
- assert.equal(o[0].ok, true);
187
- assert.equal(o[0].spoke, true, "the agent spoke, so the ping-pong chain deepens");
188
- assert.equal(o[0].ref.path, "quick_reply", "the effect drove the daemon's real quick-reply path");
189
-
190
- // NO REGRESSION: the classifier ran and the reply was sent.
191
- assert.equal(s.seen.classify, 1, "the Haiku classifier still ran for a directed DM");
192
- assert.deepEqual(s.seen.quick, [{ id: "MSG-DM", rung: 0 }], "the quick reply was sent, carrying its rung");
193
- assert.equal(s.seen.dispatch.length, 0, "no session spawned for a rung-0 reply");
194
- });
195
-
196
- test("LADDER: a plain short question DM FAST-PATHS — quick-replies WITHOUT a classifier call", async () => {
197
- const item = dm({ id: "MSG-DM-FAST", raw_ref: "slack:D91:1", channel_id: "D91", content: "what's the deadline?" });
198
- writeLive(item, "slack");
199
- const s = spy(RESPOND);
200
- await daemon.processItem(item, "slack", s.deps);
201
-
202
- // The whole point: the ~5-8s Haiku classify call is SKIPPED for a plain question.
203
- assert.equal(s.seen.classify, 0, "fast-path: a plain directed question skips the classifier");
204
- // ...but the DM is still answered via the quick-reply path, still journalled.
205
- assert.deepEqual(s.seen.quick, [{ id: "MSG-DM-FAST", rung: 0 }], "the quick reply still went out at rung 0");
206
- assert.equal(s.seen.dispatch.length, 0, "no session spawned");
207
- assert.ok(decisions().some((r) => r.surface === "dm" && r.disposition === "react_now"), "still journalled intake→route→outcome");
208
- });
209
-
210
- // ---------------------------------------------------------------------------
211
- // 2) A board task is WORK: it queues, and never reaches the classifier.
212
- // ---------------------------------------------------------------------------
213
-
214
- test("LADDER: an assigned board task is queued to state/queues/inbound.yaml, not answered", async () => {
215
- const task = {
216
- id: "cohort-task_assigned-149",
217
- raw_ref: "cohort:task_assigned:cmql3j5j301jwhhv2ipp8gmir:149",
218
- service: "cohort",
219
- kind: "task_assigned",
220
- sender: "Priya Raman",
221
- sender_id: "mem_priya_0002",
222
- content: "Ship the Q3 revenue reconciliation",
223
- channel_id: "board_main",
224
- timestamp: new Date().toISOString(),
225
- };
226
- writeLive(task, "cohort");
227
- const s = spy(RESPOND);
228
- await daemon.processItem(task, "cohort", s.deps);
229
-
230
- const d = decisions()[0];
231
- assert.equal(d.surface, "task_assigned");
232
- assert.equal(d.disposition, "schedule");
233
- assert.equal(d.reason, "not_respondable", "a board assignment is work, not conversation");
234
- assert.equal(d.obligationKey, "react.task.assigned");
235
- assert.equal(d.rung, 3, "no lower predicate holds for open work → a bounded session");
236
-
237
- const o = outcomes()[0];
238
- assert.equal(o.effect, "schedule");
239
- assert.equal(o.ok, true);
240
-
241
- // The queue row is real, and it is the file `sweepBacklog` already reads.
242
- const q = readFileSync(join(AGENT_DIR, "state", "queues", "inbound.yaml"), "utf-8");
243
- assert.match(q, /id: "inb-board\.task_assigned#149"/);
244
- assert.match(q, /rung: 3/);
245
- assert.match(q, /obligation_key: "react\.task\.assigned"/);
246
- assert.match(q, /why: ".*not a conversational surface/, "the reasoning travels with the work");
247
-
248
- assert.equal(s.seen.classify, 0, "no Haiku call — the ladder placed it without one");
249
- assert.equal(s.seen.quick.length, 0);
250
- assert.equal(s.seen.dispatch.length, 0);
251
- });
252
-
253
- // ---------------------------------------------------------------------------
254
- // 3) `ignore` is first-class: journalled, cheap, and it does not call Haiku.
255
- // ---------------------------------------------------------------------------
256
-
257
- test("LADDER: ambient channel chatter is IGNORED, with a journalled reason, and costs no classifier call", async () => {
258
- const ambient = {
259
- id: "MSG-AMBIENT",
260
- raw_ref: "slack:C90:1",
261
- channel: "#engineering",
262
- channel_id: "C90",
263
- sender: "Tomas Lind",
264
- sender_id: "U_TOMAS",
265
- content: "lunch anyone",
266
- timestamp: new Date().toISOString(),
267
- };
268
- writeLive(ambient, "slack");
269
- const s = spy(RESPOND);
270
- await daemon.processItem(ambient, "slack", s.deps);
271
-
272
- const d = decisions()[0];
273
- assert.equal(d.disposition, "ignore");
274
- assert.equal(d.reason, "ambient_channel");
275
- assert.ok(d.why.some((w) => w.includes("park for the ambient sweep")), "the ignore explains itself");
276
-
277
- const o = outcomes()[0];
278
- assert.equal(o.effect, "none", "an ignore is a completed decision with no effect");
279
- assert.equal(o.ok, true, "an ignore is a SUCCESS, not a failure");
280
- assert.equal(o.spoke, false);
281
-
282
- assert.equal(s.seen.classify, 0, "an ambient message no longer costs a Haiku call");
283
- assert.equal(s.seen.quick.length, 0);
284
- assert.equal(s.seen.dispatch.length, 0);
285
-
286
- // It is finished with, not left to be re-polled forever.
287
- const files = readdirSync(join(AGENT_DIR, "state", "inbox", "slack"));
288
- assert.deepEqual(files, ["MSG-AMBIENT.json.processed"]);
289
- });
290
-
291
- // ---------------------------------------------------------------------------
292
- // 4) REQUIREMENT: an event no obligation covers still reaches today's path.
293
- // ---------------------------------------------------------------------------
294
-
295
- test("LADDER: an event no obligation covers is still answered, capped at rung ≤1, and proposes the missing obligation", async () => {
296
- rmSync(join(AGENT_DIR, "config", "plan.yaml"), { force: true });
297
- daemon._resetLadderCaches();
298
- try {
299
- const item = dm({ id: "MSG-DM-UNCOVERED", raw_ref: "slack:D78:1", channel_id: "D78" });
300
- writeLive(item, "slack");
301
- const s = spy(RESPOND);
302
- await daemon.processItem(item, "slack", s.deps);
303
-
304
- const d = decisions()[0];
305
- assert.equal(d.disposition, "react_now", "an uncovered event is still answered");
306
- assert.equal(d.obligationKey, null, "…but it is recorded as uncovered");
307
- assert.ok(d.rung <= 1, `uncovered work is capped at rung ≤1, got ${d.rung}`);
308
- assert.ok(
309
- d.why.some((w) => w.includes("no obligation in the compiled plan covers this event")),
310
- "the plan gap is stated in the reasoning",
311
- );
312
- assert.equal(s.seen.quick.length, 1, "the sender still got an answer");
313
-
314
- // §6.1's other half: the gap becomes a proposal, not just a complaint.
315
- const proposed = join(AGENT_DIR, "state", "plan", "proposed.jsonl");
316
- assert.ok(existsSync(proposed), "an obligation was proposed to cover the gap");
317
- assert.match(readFileSync(proposed, "utf-8"), /react\.message\./);
318
- } finally {
319
- writeFileSync(join(AGENT_DIR, "config", "plan.yaml"), PLAN);
320
- daemon._resetLadderCaches();
321
- }
322
- });
323
-
324
- // ---------------------------------------------------------------------------
325
- // 5) REGRESSION (live run): the rule override must not defeat the GUARDS.
326
- // ---------------------------------------------------------------------------
327
-
328
- test("LADDER REGRESSION: a redelivered DM is dropped as `duplicate` and is NOT answered twice", async () => {
329
- const item = dm({ id: "MSG-DM-DUP", raw_ref: "slack:D79:1", channel_id: "D79" });
330
- writeLive(item, "slack");
331
-
332
- const first = spy(RESPOND);
333
- await daemon.processItem(item, "slack", first.deps);
334
- assert.equal(first.seen.quick.length, 1, "the first delivery is answered");
335
-
336
- // The SSE push and the poll backstop converge — that convergence is the design.
337
- const again = { ...item };
338
- writeLive(again, "slack");
339
- const second = spy(RESPOND);
340
- await daemon.processItem(again, "slack", second.deps);
341
-
342
- const d = decisions();
343
- assert.equal(d.length, 2);
344
- assert.equal(d[1].disposition, "ignore");
345
- assert.equal(d[1].reason, "duplicate", "the dedupe guard fired");
346
-
347
- // The bug this encodes: `isDirectedAtAgent` returns true for ANY DM, so
348
- // consulting it on every ignore reason made `duplicate` unreachable and the
349
- // agent answered both deliveries.
350
- assert.equal(second.seen.quick.length, 0, "the redelivery was NOT answered");
351
- assert.equal(second.seen.classify, 0, "…and did not even reach the classifier");
352
- });
353
-
354
- // ---------------------------------------------------------------------------
355
- // 6) …but a directedness MISS must still fall through. Silence is the expensive
356
- // failure, so the ladder never has the last word on "is this mine".
357
- // ---------------------------------------------------------------------------
358
-
359
- test("LADDER: a directedness disagreement falls through to the classifier path rather than gagging the agent", async () => {
360
- // Ambient by intake's rules (no mention flag, not a DM, not a thread reply),
361
- // but the agent's own name is in the text — which `isDirectedAtAgent` catches
362
- // and intake does not.
363
- const item = {
364
- id: "MSG-NAMED",
365
- raw_ref: "slack:C91:1",
366
- channel: "#engineering",
367
- channel_id: "C91",
368
- sender: "Tomas Lind",
369
- sender_id: "U_TOMAS",
370
- content: "nova can you take the reconciliation?",
371
- timestamp: new Date().toISOString(),
372
- };
373
- writeLive(item, "slack");
374
- const s = spy(RESPOND);
375
- const gate = await daemon.runExecutionLadder(item, "slack", item.raw_ref, "trace-x", s.deps);
376
-
377
- assert.equal(gate.handled, false, "the ladder did not take responsibility");
378
- assert.equal(gate.reason, "rule_override");
379
- assert.equal(decisions()[0].disposition, "ignore", "the ignore is still journalled — the disagreement is on record");
380
- });
381
-
382
- // ---------------------------------------------------------------------------
383
- // 7) The rung is a VETO on the quick path, never a promotion.
384
- // ---------------------------------------------------------------------------
385
-
386
- test("RUNG: the quick-reply responder accepts rungs 0-1 and refuses 2+", async () => {
387
- assert.equal(responder.rungPermitsQuickReply(null), true, "no routing decision → today's behaviour");
388
- assert.equal(responder.rungPermitsQuickReply({ rung: 0 }), true);
389
- assert.equal(responder.rungPermitsQuickReply({ rung: 1 }), true);
390
- assert.equal(responder.rungPermitsQuickReply({ rung: 2 }), false);
391
- assert.equal(responder.rungPermitsQuickReply({ rung: 3 }), false);
392
- assert.equal(responder.rungPermitsQuickReply({ rung: 5 }), false, "no subagent-fanout mechanism exists here");
393
-
394
- // A veto, not a promotion: rung 0 does not make research quick-repliable.
395
- assert.equal(responder.isQuickReply({ action: "research", priority: "normal", model: "sonnet" }, { rung: 0 }), false);
396
- assert.equal(responder.isQuickReply({ action: "respond", priority: "normal", model: "sonnet" }, { rung: 0 }), true);
397
- assert.equal(
398
- responder.isQuickReply({ action: "respond", priority: "normal", model: "sonnet" }, { rung: 3 }),
399
- false,
400
- "a rung-3 answer needs a session even when the classifier called it simple",
401
- );
402
- });
403
-
404
- test("ANSWERABLE: a directly-answerable question is quick-replied, not sessioned + acked", () => {
405
- // "what machine are you running on?" from the CEO classifies critical/opus,
406
- // which would otherwise force a session and a robotic holding ack. Flagged
407
- // answerable, it is answered in the turn instead — the defect being fixed.
408
- assert.equal(
409
- responder.isQuickReply({ action: "respond", priority: "critical", model: "opus", answerable: true }),
410
- true,
411
- "an answerable question skips the session even at critical/opus",
412
- );
413
- // The flag only fast-paths a `respond` — a draft/research item stays on the
414
- // session path even if the classifier also (contradictorily) flags it.
415
- assert.equal(responder.isQuickReply({ action: "draft", priority: "high", model: "opus", answerable: true }), false);
416
- assert.equal(responder.isQuickReply({ action: "research", priority: "normal", model: "sonnet", answerable: true }), false);
417
- // And the rung veto still wins over answerable.
418
- assert.equal(
419
- responder.isQuickReply({ action: "respond", priority: "critical", model: "opus", answerable: true }, { rung: 3 }),
420
- false,
421
- "a rung-3 answer needs a session even when the classifier called it answerable",
422
- );
423
- // Without the flag, a critical/opus item still takes the session path.
424
- assert.equal(responder.isQuickReply({ action: "respond", priority: "critical", model: "opus" }), false);
425
- });
426
-
427
- // ---------------------------------------------------------------------------
428
- // 8) REGRESSION (live run): with no need estimate every DM routed to rung 3,
429
- // which vetoed the quick reply and spawned a session for a two-line message.
430
- // ---------------------------------------------------------------------------
431
-
432
- test("LADDER REGRESSION: the need estimate marks a resolvable reply target as params-known", () => {
433
- assert.equal(daemon.ladderNeed({ channel_id: "D1" }).paramsKnown, true, "a resolvable channel → rung 0 is available");
434
- assert.equal(daemon.ladderNeed({ raw_ref: "cohort:dm:x:1" }).paramsKnown, true);
435
- assert.equal(
436
- daemon.ladderNeed({ sender: "someone" }).paramsKnown,
437
- false,
438
- "no resolvable target → params are NOT known and the ladder walks up to a session that can find it",
439
- );
440
- assert.equal(daemon.ladderNeed({ channel_id: "D1" }).steps, 1);
441
- });
442
-
443
- // ---------------------------------------------------------------------------
444
- // 9) The escape hatch preserves the old behaviour exactly — and says so.
445
- // ---------------------------------------------------------------------------
446
-
447
- test("LADDER: DAEMON_EXECUTION_LADDER=0 restores the pre-ladder path and warns about what goes dark", async () => {
448
- process.env.DAEMON_EXECUTION_LADDER = "0";
449
- daemon._resetLadderCaches();
450
- const warnings = [];
451
- const realWarn = console.warn;
452
- console.warn = (...a) => warnings.push(a.join(" "));
453
- try {
454
- const item = dm({ id: "MSG-DM-OFF", raw_ref: "slack:D80:1", channel_id: "D80" });
455
- writeLive(item, "slack");
456
- const s = spy(RESPOND);
457
- await daemon.processItem(item, "slack", s.deps);
458
-
459
- assert.equal(journal().length, 0, "nothing is journalled with the ladder off");
460
- assert.equal(s.seen.classify, 1, "the classifier path is untouched");
461
- assert.equal(s.seen.quick.length, 1, "the DM is still answered");
462
- assert.ok(
463
- warnings.some((w) => w.includes("DAEMON_EXECUTION_LADDER=0") && w.includes("no record of what was ignored")),
464
- "turning it off is never silent",
465
- );
466
- } finally {
467
- console.warn = realWarn;
468
- delete process.env.DAEMON_EXECUTION_LADDER;
469
- }
470
- });