@cohortapp/agent-sdk 2.16.0 → 2.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (529) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +23 -7
  3. package/README.md +1 -0
  4. package/bin/maestro.mjs +62 -0
  5. package/docs/guides/billing-console-keys.md +60 -0
  6. package/docs/guides/front-door-session.md +54 -9
  7. package/docs/guides/mac-mini.md +20 -25
  8. package/docs/guides/poller-daemon-setup.md +4 -1
  9. package/docs/guides/setup-wizard.md +1 -1
  10. package/docs/runbooks/fleet-rollout.md +156 -0
  11. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  12. package/lib/action-executor.js +19 -3
  13. package/lib/budget-guard.mjs +279 -3
  14. package/lib/channels/base-adapter.mjs +3 -1
  15. package/lib/channels/contract.mjs +2 -1
  16. package/lib/channels/inbox-item.mjs +8 -0
  17. package/lib/claude-bin.mjs +5 -6
  18. package/lib/cli/doctor-checks.mjs +141 -10
  19. package/lib/cli/global-setup-extras.mjs +5 -1
  20. package/lib/cli/inbox.mjs +100 -15
  21. package/lib/cli/seat-auth.mjs +463 -0
  22. package/lib/cli/session.mjs +80 -12
  23. package/lib/collective/capture.mjs +8 -6
  24. package/lib/collective/global-config.mjs +63 -1
  25. package/lib/collective/presence.mjs +142 -5
  26. package/lib/comms/send-gate.mjs +559 -1
  27. package/lib/context/budget.mjs +327 -0
  28. package/lib/context/history-scope.mjs +138 -0
  29. package/lib/diagnostics/alerts.mjs +49 -0
  30. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  31. package/lib/engine/agents/definitions.mjs +343 -0
  32. package/lib/engine/agents/persist.mjs +275 -0
  33. package/lib/engine/agents/runtime.mjs +748 -0
  34. package/lib/engine/agents/usage.mjs +95 -0
  35. package/lib/engine/auth-status.mjs +139 -0
  36. package/lib/engine/budget.mjs +194 -0
  37. package/lib/engine/cli.mjs +1204 -0
  38. package/lib/engine/commands/index.mjs +269 -0
  39. package/lib/engine/context/budget.mjs +219 -0
  40. package/lib/engine/context/cache.mjs +125 -0
  41. package/lib/engine/context/child-env.mjs +215 -0
  42. package/lib/engine/context/compaction.mjs +342 -0
  43. package/lib/engine/context/images.mjs +90 -0
  44. package/lib/engine/context/instructions.mjs +327 -0
  45. package/lib/engine/context/lazy-instructions.mjs +169 -0
  46. package/lib/engine/context/manager.mjs +182 -0
  47. package/lib/engine/context/real-path.mjs +91 -0
  48. package/lib/engine/context/secret-values.mjs +163 -0
  49. package/lib/engine/context/settings.mjs +274 -0
  50. package/lib/engine/context/stream-input.mjs +159 -0
  51. package/lib/engine/guard.mjs +152 -0
  52. package/lib/engine/hooks.mjs +713 -0
  53. package/lib/engine/loop.mjs +560 -0
  54. package/lib/engine/mcp/client.mjs +254 -0
  55. package/lib/engine/mcp/config.mjs +301 -0
  56. package/lib/engine/mcp/http.mjs +201 -0
  57. package/lib/engine/mcp/index.mjs +146 -0
  58. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  59. package/lib/engine/mcp/naming.mjs +66 -0
  60. package/lib/engine/mcp/resources.mjs +89 -0
  61. package/lib/engine/mcp/results.mjs +133 -0
  62. package/lib/engine/mcp/stdio.mjs +137 -0
  63. package/lib/engine/mcp/supervisor.mjs +116 -0
  64. package/lib/engine/messages.mjs +104 -0
  65. package/lib/engine/output/json.mjs +164 -0
  66. package/lib/engine/output/stream-json.mjs +266 -0
  67. package/lib/engine/permissions.mjs +845 -0
  68. package/lib/engine/process-identity.mjs +164 -0
  69. package/lib/engine/process-tree.mjs +551 -0
  70. package/lib/engine/prompt.mjs +60 -0
  71. package/lib/engine/session/store.mjs +299 -0
  72. package/lib/engine/session-runtime/args.mjs +97 -0
  73. package/lib/engine/session-runtime/host.mjs +143 -0
  74. package/lib/engine/session-runtime/inbox.mjs +122 -0
  75. package/lib/engine/session-runtime/notifications.mjs +129 -0
  76. package/lib/engine/session-runtime/registry.mjs +328 -0
  77. package/lib/engine/session-runtime/runner.mjs +344 -0
  78. package/lib/engine/session-runtime/socket.mjs +212 -0
  79. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  80. package/lib/engine/skills/index.mjs +321 -0
  81. package/lib/engine/tools/bash-background.mjs +533 -0
  82. package/lib/engine/tools/bash.mjs +216 -0
  83. package/lib/engine/tools/edit.mjs +97 -0
  84. package/lib/engine/tools/glob.mjs +81 -0
  85. package/lib/engine/tools/grep.mjs +224 -0
  86. package/lib/engine/tools/index.mjs +84 -0
  87. package/lib/engine/tools/list-agents.mjs +32 -0
  88. package/lib/engine/tools/ls.mjs +127 -0
  89. package/lib/engine/tools/monitor.mjs +82 -0
  90. package/lib/engine/tools/notebook-edit.mjs +218 -0
  91. package/lib/engine/tools/read.mjs +103 -0
  92. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  93. package/lib/engine/tools/schema.mjs +144 -0
  94. package/lib/engine/tools/send-message.mjs +77 -0
  95. package/lib/engine/tools/session.mjs +70 -0
  96. package/lib/engine/tools/todo.mjs +144 -0
  97. package/lib/engine/tools/toolsearch.mjs +217 -0
  98. package/lib/engine/tools/walk.mjs +193 -0
  99. package/lib/engine/tools/web-switch.mjs +31 -0
  100. package/lib/engine/tools/webfetch-html.mjs +387 -0
  101. package/lib/engine/tools/webfetch-net.mjs +340 -0
  102. package/lib/engine/tools/webfetch.mjs +198 -0
  103. package/lib/engine/tools/websearch.mjs +91 -0
  104. package/lib/engine/tools/workflow.mjs +95 -0
  105. package/lib/engine/tools/write.mjs +76 -0
  106. package/lib/engine/tui/line-editor.mjs +137 -0
  107. package/lib/engine/tui/render.mjs +86 -0
  108. package/lib/engine/tui/tui.mjs +274 -0
  109. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  110. package/lib/engine/wire/effort.mjs +36 -0
  111. package/lib/engine/wire/errors.mjs +496 -0
  112. package/lib/engine/wire/http.mjs +441 -0
  113. package/lib/engine/wire/index.mjs +76 -0
  114. package/lib/engine/wire/openai-chat.mjs +332 -0
  115. package/lib/engine/wire/prompt-cache.mjs +79 -0
  116. package/lib/engine/wire/search.mjs +140 -0
  117. package/lib/engine/wire/sse.mjs +114 -0
  118. package/lib/engine/wire/stall.mjs +349 -0
  119. package/lib/engine/wire/token-provider.mjs +175 -0
  120. package/lib/engine/wire/usage.mjs +192 -0
  121. package/lib/engine/workflow/host.mjs +524 -0
  122. package/lib/engine/workflow/journal.mjs +188 -0
  123. package/lib/engine/workflow/json-schema.mjs +171 -0
  124. package/lib/engine/workflow/meta.mjs +329 -0
  125. package/lib/engine/workflow/notifications.mjs +52 -0
  126. package/lib/engine/workflow/runtime.mjs +447 -0
  127. package/lib/engine/workflow/sandbox.mjs +534 -0
  128. package/lib/engine/workflow/worker.mjs +141 -0
  129. package/lib/engine/workflow/worktree.mjs +74 -0
  130. package/lib/execution/disposition.mjs +1 -1
  131. package/lib/execution/intake.mjs +10 -0
  132. package/lib/execution/surface-policy.mjs +15 -0
  133. package/lib/learning/curator.mjs +8 -6
  134. package/lib/learning/reflect.mjs +8 -6
  135. package/lib/model-router/catalog/cohort.yaml +137 -0
  136. package/lib/model-router/catalog.mjs +118 -1
  137. package/lib/model-router/economics.mjs +9 -0
  138. package/lib/model-router/failover.mjs +67 -16
  139. package/lib/model-router/llm-task.mjs +39 -3
  140. package/lib/model-router/resolve.mjs +95 -3
  141. package/lib/model-router/spawn.mjs +46 -47
  142. package/lib/model-router/taxonomy.mjs +126 -4
  143. package/lib/org/cost-sync.mjs +141 -11
  144. package/lib/org/inbound/broadcast.mjs +289 -0
  145. package/lib/org/inbound/collective.mjs +375 -0
  146. package/lib/org/inbound/directedness.mjs +96 -8
  147. package/lib/org/inbound/facts.mjs +82 -4
  148. package/lib/org/inbound/hydrate.mjs +555 -51
  149. package/lib/org/inbound/project.mjs +22 -0
  150. package/lib/org/inbound/surfaces.mjs +14 -0
  151. package/lib/org/llm-token.mjs +879 -0
  152. package/lib/org/mesh.mjs +61 -0
  153. package/lib/org/messaging.mjs +3 -1
  154. package/lib/org/protocol.checksum +1 -1
  155. package/lib/org/protocol.mjs +15 -0
  156. package/lib/org/quota.mjs +520 -0
  157. package/lib/org/tool-surface.mjs +104 -16
  158. package/lib/org/ui-parity.mjs +16 -1
  159. package/lib/org/work-ledger.mjs +37 -6
  160. package/lib/rate-guard.mjs +114 -1
  161. package/lib/resource-governor.mjs +41 -6
  162. package/lib/runtime/adapter.mjs +823 -0
  163. package/lib/runtime/child-env.mjs +191 -0
  164. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  165. package/lib/runtime/seat-engine.mjs +162 -0
  166. package/lib/session/ask-ledger.mjs +271 -0
  167. package/lib/session/current-work.mjs +676 -0
  168. package/lib/session/feed-core.mjs +40 -3
  169. package/lib/session/launch-args.mjs +56 -4
  170. package/lib/session/status-summary.mjs +26 -9
  171. package/lib/session/upgrade-notice.mjs +42 -0
  172. package/lib/setup/claude-probe.mjs +117 -13
  173. package/lib/setup/enrich.mjs +13 -10
  174. package/lib/setup/sections/model.mjs +39 -13
  175. package/lib/telemetry/collect.mjs +208 -9
  176. package/lib/upgrade/ignored-drift.mjs +105 -0
  177. package/lib/voice/post-call-brief.mjs +30 -17
  178. package/package.json +15 -3
  179. package/plugins/maestro-skills/skills/board-work.md +5 -0
  180. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  181. package/plugins/maestro-skills/skills/main-session.md +18 -7
  182. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  183. package/scripts/ci/run-tests.mjs +47 -19
  184. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  185. package/scripts/collective/hook-runner.mjs +29 -2
  186. package/scripts/continuous-monitor.sh +13 -0
  187. package/scripts/cost/track-claude-usage.mjs +15 -0
  188. package/scripts/daemon/agent-daemon.mjs +408 -20
  189. package/scripts/daemon/assurance.mjs +48 -12
  190. package/scripts/daemon/cadence-consumer.mjs +218 -68
  191. package/scripts/daemon/cadence-handlers.mjs +73 -4
  192. package/scripts/daemon/classifier.mjs +75 -26
  193. package/scripts/daemon/context-compiler.mjs +104 -59
  194. package/scripts/daemon/deliver.mjs +30 -1
  195. package/scripts/daemon/dispatcher.mjs +804 -157
  196. package/scripts/daemon/health.mjs +14 -1
  197. package/scripts/daemon/lib/session-router.mjs +310 -42
  198. package/scripts/daemon/maestro-daemon.mjs +11 -0
  199. package/scripts/daemon/prompt-builder.mjs +121 -12
  200. package/scripts/daemon/responder.mjs +315 -146
  201. package/scripts/daemon/sdk-version.mjs +98 -16
  202. package/scripts/eval/probe-gateway.mjs +635 -0
  203. package/scripts/eval/replay/extract.mjs +270 -0
  204. package/scripts/eval/replay/grade.mjs +260 -0
  205. package/scripts/eval/replay/lib/config.mjs +50 -0
  206. package/scripts/eval/replay/lib/effects.mjs +65 -0
  207. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  208. package/scripts/eval/replay/lib/judge.mjs +72 -0
  209. package/scripts/eval/replay/lib/redact.mjs +136 -0
  210. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  211. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  212. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  213. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  214. package/scripts/eval/replay/report.mjs +185 -0
  215. package/scripts/eval/replay/run.mjs +404 -0
  216. package/scripts/fleet/rollout.mjs +1094 -0
  217. package/scripts/hooks/pre-send-audit.sh +36 -245
  218. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  219. package/scripts/hooks/validate-state-yaml.sh +190 -0
  220. package/scripts/huddle/huddle-llm.mjs +361 -0
  221. package/scripts/huddle/huddle-server.mjs +46 -121
  222. package/scripts/local-triggers/autoupdate.sh +448 -78
  223. package/scripts/local-triggers/run-trigger.sh +13 -0
  224. package/scripts/maintenance/pin-integrity.mjs +364 -0
  225. package/scripts/poll-slack-events.sh +41 -9
  226. package/scripts/poller/slack-socket-mode.mjs +28 -3
  227. package/scripts/session/supervisor.mjs +80 -13
  228. package/scripts/spawn-session.sh +13 -0
  229. package/bin/maestro.test.mjs +0 -1574
  230. package/lib/action-executor.test.mjs +0 -871
  231. package/lib/archetype.test.mjs +0 -132
  232. package/lib/assurance/plan-note.test.mjs +0 -234
  233. package/lib/assurance/room-budget.test.mjs +0 -486
  234. package/lib/assurance/tier.test.mjs +0 -174
  235. package/lib/autonomy.test.mjs +0 -66
  236. package/lib/backlog.test.mjs +0 -302
  237. package/lib/backup/policy.test.mjs +0 -305
  238. package/lib/budget-escalate.test.mjs +0 -232
  239. package/lib/budget-guard.envelope.test.mjs +0 -476
  240. package/lib/budget-guard.test.mjs +0 -427
  241. package/lib/cadence-bus-requeue.test.mjs +0 -83
  242. package/lib/cadence-bus-schedule.test.mjs +0 -194
  243. package/lib/cadence-bus.test.mjs +0 -720
  244. package/lib/cadences.test.mjs +0 -230
  245. package/lib/capability/inventory.test.mjs +0 -232
  246. package/lib/capability.test.mjs +0 -78
  247. package/lib/channels/base-adapter.test.mjs +0 -590
  248. package/lib/channels/channels.test.mjs +0 -371
  249. package/lib/channels/contract.test.mjs +0 -162
  250. package/lib/channels/inbox-item.test.mjs +0 -368
  251. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  252. package/lib/channels/pairing.test.mjs +0 -270
  253. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  254. package/lib/channels/slack-adapter.test.mjs +0 -212
  255. package/lib/channels/telegram-adapter.test.mjs +0 -306
  256. package/lib/channels/voice/adapter.test.mjs +0 -278
  257. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  258. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  259. package/lib/charter.test.mjs +0 -89
  260. package/lib/claude-bin.test.mjs +0 -131
  261. package/lib/cli/board.test.mjs +0 -227
  262. package/lib/cli/design.test.mjs +0 -270
  263. package/lib/cli/doctor-checks.test.mjs +0 -336
  264. package/lib/cli/global-setup-extras.test.mjs +0 -462
  265. package/lib/cli/inbox.test.mjs +0 -230
  266. package/lib/cli/session-ack.test.mjs +0 -63
  267. package/lib/cli/session.test.mjs +0 -613
  268. package/lib/collective/capture.test.mjs +0 -121
  269. package/lib/collective/cards.test.mjs +0 -114
  270. package/lib/collective/config.test.mjs +0 -123
  271. package/lib/collective/global-config.test.mjs +0 -220
  272. package/lib/collective/global-skills.test.mjs +0 -126
  273. package/lib/collective/presence.test.mjs +0 -95
  274. package/lib/collective/recall.test.mjs +0 -116
  275. package/lib/collective/vendor-skills.test.mjs +0 -306
  276. package/lib/comms/send-gate.test.mjs +0 -770
  277. package/lib/comms.test.mjs +0 -41
  278. package/lib/cost/ledger-row.test.mjs +0 -183
  279. package/lib/design/design-md.test.mjs +0 -318
  280. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  281. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  282. package/lib/design/fixtures/foundation.json +0 -133
  283. package/lib/design/refresh-gate.test.mjs +0 -144
  284. package/lib/design/write.test.mjs +0 -241
  285. package/lib/diagnostics/alerts.test.mjs +0 -318
  286. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  287. package/lib/diagnostics/counters.test.mjs +0 -206
  288. package/lib/diagnostics/events.test.mjs +0 -290
  289. package/lib/diagnostics/otel.test.mjs +0 -196
  290. package/lib/diagnostics/trace.test.mjs +0 -251
  291. package/lib/env-compat.test.mjs +0 -104
  292. package/lib/execution/disposition.test.mjs +0 -553
  293. package/lib/execution/drive.test.mjs +0 -270
  294. package/lib/execution/effects.test.mjs +0 -344
  295. package/lib/execution/intake.test.mjs +0 -389
  296. package/lib/execution/journal.test.mjs +0 -261
  297. package/lib/execution/match.test.mjs +0 -235
  298. package/lib/execution/pipeline.test.mjs +0 -392
  299. package/lib/execution/route.test.mjs +0 -186
  300. package/lib/execution/surface-policy.test.mjs +0 -162
  301. package/lib/fs-atomic.test.mjs +0 -72
  302. package/lib/fs-ownership.test.mjs +0 -158
  303. package/lib/goals/admission.test.mjs +0 -164
  304. package/lib/goals/classify.test.mjs +0 -167
  305. package/lib/goals/collaborate.test.mjs +0 -336
  306. package/lib/goals/gaps.test.mjs +0 -284
  307. package/lib/goals/loop.test.mjs +0 -845
  308. package/lib/hooks/bus.test.mjs +0 -387
  309. package/lib/identity/persona.test.mjs +0 -142
  310. package/lib/kpi-sensors.test.mjs +0 -278
  311. package/lib/kpi.test.mjs +0 -244
  312. package/lib/learning/config.test.mjs +0 -75
  313. package/lib/learning/counters.test.mjs +0 -69
  314. package/lib/learning/curator-consolidate.test.mjs +0 -238
  315. package/lib/learning/curator.test.mjs +0 -106
  316. package/lib/learning/reflect.test.mjs +0 -0
  317. package/lib/learning/session-index.test.mjs +0 -125
  318. package/lib/learning/skill-writer.test.mjs +0 -210
  319. package/lib/mandate/audit.test.mjs +0 -195
  320. package/lib/mandate/contract.test.mjs +0 -185
  321. package/lib/mandate/derive.test.mjs +0 -274
  322. package/lib/mandate/model.test.mjs +0 -164
  323. package/lib/mandate/refresh.test.mjs +0 -389
  324. package/lib/mcp/server.test.mjs +0 -426
  325. package/lib/model-router/auth-profiles.test.mjs +0 -580
  326. package/lib/model-router/catalog.test.mjs +0 -385
  327. package/lib/model-router/economics.test.mjs +0 -438
  328. package/lib/model-router/failover.test.mjs +0 -439
  329. package/lib/model-router/health.test.mjs +0 -338
  330. package/lib/model-router/integration-coverage.test.mjs +0 -831
  331. package/lib/model-router/integration.test.mjs +0 -564
  332. package/lib/model-router/ledger.test.mjs +0 -415
  333. package/lib/model-router/llm-task.test.mjs +0 -392
  334. package/lib/model-router/org-credentials.test.mjs +0 -265
  335. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  336. package/lib/model-router/reconcile.test.mjs +0 -316
  337. package/lib/model-router/repair.test.mjs +0 -180
  338. package/lib/model-router/spawn.test.mjs +0 -446
  339. package/lib/model-router/taxonomy.test.mjs +0 -410
  340. package/lib/model-router.test.mjs +0 -1207
  341. package/lib/org/activity.test.mjs +0 -134
  342. package/lib/org/approvals.test.mjs +0 -216
  343. package/lib/org/awareness.test.mjs +0 -159
  344. package/lib/org/board-mine-cache.test.mjs +0 -53
  345. package/lib/org/board.test.mjs +0 -187
  346. package/lib/org/bootstrap-context.test.mjs +0 -153
  347. package/lib/org/client.test.mjs +0 -1206
  348. package/lib/org/cohort-client.test.mjs +0 -126
  349. package/lib/org/cost-sync.test.mjs +0 -153
  350. package/lib/org/doctor.test.mjs +0 -346
  351. package/lib/org/engagement-ledger.test.mjs +0 -112
  352. package/lib/org/engagement.test.mjs +0 -739
  353. package/lib/org/handoff.test.mjs +0 -269
  354. package/lib/org/inbound/directedness.test.mjs +0 -668
  355. package/lib/org/inbound/facts.test.mjs +0 -471
  356. package/lib/org/inbound/hydrate.test.mjs +0 -453
  357. package/lib/org/inbound/index.test.mjs +0 -429
  358. package/lib/org/inbound/project.test.mjs +0 -287
  359. package/lib/org/integration-tools.test.mjs +0 -160
  360. package/lib/org/keys.test.mjs +0 -92
  361. package/lib/org/knowledge.test.mjs +0 -326
  362. package/lib/org/leases.test.mjs +0 -235
  363. package/lib/org/mesh-directives.test.mjs +0 -110
  364. package/lib/org/mesh-integration.test.mjs +0 -127
  365. package/lib/org/mesh.test.mjs +0 -400
  366. package/lib/org/messaging.test.mjs +0 -471
  367. package/lib/org/param-contract.test.mjs +0 -477
  368. package/lib/org/policy.test.mjs +0 -237
  369. package/lib/org/protocol.checksum.test.mjs +0 -90
  370. package/lib/org/protocol.test.mjs +0 -323
  371. package/lib/org/push.test.mjs +0 -792
  372. package/lib/org/registry.test.mjs +0 -100
  373. package/lib/org/resource-tools.test.mjs +0 -361
  374. package/lib/org/tool-access.test.mjs +0 -144
  375. package/lib/org/tool-surface-integration.test.mjs +0 -120
  376. package/lib/org/tool-surface.test.mjs +0 -1268
  377. package/lib/org/typing.test.mjs +0 -291
  378. package/lib/org/ui-parity.test.mjs +0 -560
  379. package/lib/org/verify.test.mjs +0 -194
  380. package/lib/org/work-ledger.test.mjs +0 -273
  381. package/lib/plan/adoption-e2e.test.mjs +0 -366
  382. package/lib/plan/budget-enforcement.test.mjs +0 -400
  383. package/lib/plan/compile.test.mjs +0 -382
  384. package/lib/plan/emit.test.mjs +0 -269
  385. package/lib/plan/explain.test.mjs +0 -188
  386. package/lib/prompts/parallelism.test.mjs +0 -177
  387. package/lib/rag/rag.test.mjs +0 -505
  388. package/lib/rate-guard.test.mjs +0 -272
  389. package/lib/reactive-gate.test.mjs +0 -57
  390. package/lib/render.test.mjs +0 -68
  391. package/lib/resource-governor.test.mjs +0 -488
  392. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  393. package/lib/scheduling/jitter.test.mjs +0 -140
  394. package/lib/secrets/broker.test.mjs +0 -280
  395. package/lib/secrets/providers.test.mjs +0 -274
  396. package/lib/security/audit-engine.test.mjs +0 -424
  397. package/lib/security/coerce-args.test.mjs +0 -281
  398. package/lib/security/dangerous-tools.test.mjs +0 -68
  399. package/lib/security/external-content.test.mjs +0 -84
  400. package/lib/security/redact.test.mjs +0 -441
  401. package/lib/security/secret-equal.test.mjs +0 -55
  402. package/lib/session/config.test.mjs +0 -92
  403. package/lib/session/feed-core.test.mjs +0 -198
  404. package/lib/session/first-run.test.mjs +0 -121
  405. package/lib/session/frontdoor.test.mjs +0 -205
  406. package/lib/session/handoffs.test.mjs +0 -183
  407. package/lib/session/identity.test.mjs +0 -180
  408. package/lib/session/inbox-claims.test.mjs +0 -286
  409. package/lib/session/launch-args.test.mjs +0 -157
  410. package/lib/session/liveness.test.mjs +0 -100
  411. package/lib/session/status-summary.test.mjs +0 -118
  412. package/lib/session-permissions.test.mjs +0 -120
  413. package/lib/setup/claude-probe.test.mjs +0 -187
  414. package/lib/setup/completeness.test.mjs +0 -110
  415. package/lib/setup/context-pack.test.mjs +0 -89
  416. package/lib/setup/enrich.test.mjs +0 -115
  417. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  418. package/lib/setup/integration.test.mjs +0 -162
  419. package/lib/setup/io.test.mjs +0 -77
  420. package/lib/setup/runner.test.mjs +0 -132
  421. package/lib/setup/sections/identity.test.mjs +0 -234
  422. package/lib/setup/sections/inventory.test.mjs +0 -198
  423. package/lib/setup/sections/learning.test.mjs +0 -81
  424. package/lib/setup/sections/mandate.test.mjs +0 -388
  425. package/lib/setup/sections/messaging.test.mjs +0 -127
  426. package/lib/setup/sections/model.test.mjs +0 -240
  427. package/lib/setup/sections/org.test.mjs +0 -346
  428. package/lib/setup/sections/orgmail.test.mjs +0 -118
  429. package/lib/setup/sections/recovery.test.mjs +0 -98
  430. package/lib/setup/sections/subagents.test.mjs +0 -429
  431. package/lib/setup/sections/verify.test.mjs +0 -175
  432. package/lib/setup/sot.test.mjs +0 -81
  433. package/lib/setup/state.test.mjs +0 -115
  434. package/lib/singleton.test.mjs +0 -151
  435. package/lib/subagents/cli.test.mjs +0 -389
  436. package/lib/subagents/client.test.mjs +0 -309
  437. package/lib/subagents/gap.test.mjs +0 -234
  438. package/lib/subagents/lock.test.mjs +0 -248
  439. package/lib/subagents/manifest.test.mjs +0 -175
  440. package/lib/subagents/refs.test.mjs +0 -204
  441. package/lib/subagents/resolve.test.mjs +0 -422
  442. package/lib/subagents/schema.test.mjs +0 -328
  443. package/lib/telemetry/alerts.test.mjs +0 -109
  444. package/lib/telemetry/collect.test.mjs +0 -1274
  445. package/lib/tool-definitions-integration.test.mjs +0 -83
  446. package/lib/tool-definitions.test.mjs +0 -437
  447. package/lib/upgrade/global-refresh.test.mjs +0 -65
  448. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  449. package/lib/upgrade/post-steps.test.mjs +0 -200
  450. package/lib/upgrade/verify.test.mjs +0 -164
  451. package/lib/util/fetch-timeout.test.mjs +0 -202
  452. package/lib/util/reconnect.test.mjs +0 -369
  453. package/lib/util/unhandled.test.mjs +0 -216
  454. package/lib/voice/outbound.test.mjs +0 -69
  455. package/lib/voice/session-rotation.test.mjs +0 -114
  456. package/lib/voice/stt.test.mjs +0 -226
  457. package/lib/voice/voice.test.mjs +0 -990
  458. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  459. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  460. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  461. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  462. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  463. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  464. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  465. package/scripts/ci/check.test.mjs +0 -194
  466. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  467. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  468. package/scripts/collective/hook-runner.test.mjs +0 -173
  469. package/scripts/cost/fleet-digest.test.mjs +0 -207
  470. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  471. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  472. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  473. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  474. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  475. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  476. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  477. package/scripts/daemon/assurance.test.mjs +0 -1791
  478. package/scripts/daemon/board-mirror.test.mjs +0 -165
  479. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  480. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  481. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  482. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  483. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  484. package/scripts/daemon/classifier.test.mjs +0 -266
  485. package/scripts/daemon/classify-kind.test.mjs +0 -40
  486. package/scripts/daemon/context-compiler.test.mjs +0 -300
  487. package/scripts/daemon/deliver.test.mjs +0 -564
  488. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  489. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  490. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  491. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  492. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  493. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  494. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  495. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  496. package/scripts/daemon/integration.test.mjs +0 -149
  497. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  498. package/scripts/daemon/lib/session-router.test.mjs +0 -295
  499. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  500. package/scripts/daemon/prompt-builder.test.mjs +0 -344
  501. package/scripts/daemon/responder-cost.test.mjs +0 -68
  502. package/scripts/daemon/responder-history.test.mjs +0 -185
  503. package/scripts/daemon/sdk-version.test.mjs +0 -31
  504. package/scripts/daemon/session-lock.test.mjs +0 -252
  505. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  506. package/scripts/daemon/typing-registry.test.mjs +0 -102
  507. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  508. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  509. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  510. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  511. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  512. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  513. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  514. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  515. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  516. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  517. package/scripts/poller-launchd/install.test.mjs +0 -243
  518. package/scripts/restore-from-backup.test.mjs +0 -181
  519. package/scripts/session/feed.test.mjs +0 -196
  520. package/scripts/session/supervisor-sh.test.mjs +0 -218
  521. package/scripts/session/supervisor.test.mjs +0 -482
  522. package/scripts/setup/configure-macos.test.mjs +0 -306
  523. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  524. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  525. package/scripts/setup/generate-capability.test.mjs +0 -134
  526. package/scripts/setup/init-agent.test.mjs +0 -370
  527. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  528. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  529. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -0,0 +1,288 @@
1
+ /**
2
+ * lib/diagnostics/cadence-output-freshness.mjs — did the cadence actually PRODUCE
3
+ * anything, or did it merely finish?
4
+ *
5
+ * WHY THIS EXISTS
6
+ *
7
+ * The daily evening wrap — which its own trigger calls the most important
8
+ * maintenance workflow — ran on 4 of the 9 days from 26 Aug to 3 Sep 2026. It
9
+ * missed 25, 29, 30, 31 Aug and 1 Sep, and NOTHING NOTICED, because the five
10
+ * misses failed four different ways and only two of them look like failure from
11
+ * the cadence bus:
12
+ *
13
+ * 29 Aug enqueued, claimed 17x, escalated 2x, subsession_timeout,
14
+ * subsession_failed, retry_requeued, 15x skipped_backoff — and then
15
+ * stage "processed". THE BUS RECORDED SUCCESS FOR A RUN THAT PRODUCED
16
+ * NO BRIEF AND NO WORKFLOW LOG.
17
+ * 30 Aug attempts=2, routed to the DLQ. Honest — but nothing reads the DLQ.
18
+ * 31 Aug ditto.
19
+ * 1 Sep one line in the bus log, `enqueued:1`, and nothing further. Never
20
+ * claimed at all.
21
+ *
22
+ * That first mode is the one that makes this module necessary. Any alert keyed
23
+ * on the bus's own decision is GREEN during the outage, because the bus cannot
24
+ * distinguish a completed wrap from a timed-out one — `stage=processed` fired on
25
+ * a run that wrote nothing. A guard that reads the same field the lane writes
26
+ * agrees with that lane forever.
27
+ *
28
+ * So this check is deliberately derived from a DIFFERENT SOURCE than the lane it
29
+ * judges: the OUTPUT ARTEFACTS on disk. It asks one question — for the day in
30
+ * question, do the files this workflow is supposed to leave behind exist, and are
31
+ * they non-trivial? That was the only signal that caught all four modes, and it
32
+ * is independent by construction: the bus can say whatever it likes, a brief that
33
+ * was never written is still absent.
34
+ *
35
+ * WHAT "NON-TRIVIAL" MEANS, AND WHY IT IS NOT `run_end`
36
+ *
37
+ * The tempting discriminator is a terminal event — assert the workflow log ends
38
+ * with `{"event":"run_end","status":"completed"}`. MEASURED, THAT IS WRONG: of
39
+ * the five wraps that genuinely ran and produced full briefs, only ONE
40
+ * (2026-09-02) carries a `run_end` line at all. Pinning on it would have fired on
41
+ * 4 of the 5 healthy days — an 80% false-positive rate on the exact signal this
42
+ * module exists to make trustworthy. A checker that cries wolf on most good days
43
+ * gets muted, and then it is worth less than nothing.
44
+ *
45
+ * The same trap caught a second candidate. Pinning the JSONL on its `workflow`
46
+ * FIELD VALUE (`workflow == "daily-evening-wrap"`) looks like the careful choice —
47
+ * it reads a value rather than grepping a token. Replayed over the real corpus it
48
+ * failed on THREE of the five healthy days: 26 Aug writes `"workflow":
49
+ * "evening-wrap"` and 27-28 Aug carry no `workflow` key at all, only `step`. The
50
+ * workflow log is hand-authored per run and HAS NO STABLE INTERNAL SCHEMA. No
51
+ * assertion about a named event, status or cadence field survives the corpus, so
52
+ * none of them belongs in a guard.
53
+ *
54
+ * What does survive, measured across all five logs: every one of the 61 lines
55
+ * parses as a JSON object and every one carries a `ts`. So the honest bar is size
56
+ * plus that schema-agnostic shape — the file must be a JSONL of timestamped
57
+ * records, not merely a file. Floors are calibrated against what real runs
58
+ * actually produced (briefs 5115-10906 bytes, logs 8-17 lines / 2722-5459 bytes)
59
+ * with roughly an order of magnitude of headroom beneath the observed minimum. A
60
+ * healthy run clears it comfortably; an absent file, an empty file, a truncated
61
+ * file or a plain-text stub from a session that died mid-run does not.
62
+ *
63
+ * The general rule, learned twice here: VALIDATE A CANDIDATE DISCRIMINATOR AGAINST
64
+ * THE KNOWN-GOOD DAYS BEFORE SHIPPING IT. A check is only as good as its false
65
+ * positive rate on healthy input, and both of the discriminators that read like
66
+ * the rigorous choice would have fired on most of the days the workflow worked.
67
+ *
68
+ * WHICH DAY, AND WHY UTC
69
+ *
70
+ * Artefact filenames are keyed by UTC date. The wrap fires at 18:00 GST = 14:00
71
+ * UTC, so a run always writes the file for its own UTC day with ~10h of clearance
72
+ * on either side of the boundary — there is no hour at which "yesterday, UTC" is
73
+ * racing a run in progress. Checking YESTERDAY rather than today is what keeps
74
+ * this quiet: today's wrap has not happened yet for most of the day, and its
75
+ * absence at 09:00 is not a fault.
76
+ *
77
+ * `notBefore` exists so a widened lookback cannot claim a miss for a day on which
78
+ * the cadence did not yet exist. It is a declared constant, NOT derived from the
79
+ * artefacts — deriving it from the oldest file present would make the check agree
80
+ * with itself.
81
+ *
82
+ * Invariant: NEVER throws. A missing directory, an unreadable file, a bad clock
83
+ * all degrade to a verdict. Diagnostics must not be able to crash the path that
84
+ * observes them. But the fail-open direction is chosen per case: when a file
85
+ * cannot be read at all we report it MISSING, because the bug being fixed is
86
+ * silence, and the worst case of a wrong alert is one redundant message.
87
+ *
88
+ * Constraints (CLAUDE.md): ESM, Node built-ins only, injectable clock + paths so
89
+ * tests are hermetic and need no real outputs/ or logs/ on disk.
90
+ *
91
+ * @module lib/diagnostics/cadence-output-freshness
92
+ */
93
+
94
+ import { existsSync, readFileSync, statSync } from "node:fs";
95
+ import { join, resolve } from "node:path";
96
+
97
+ const MS_PER_DAY = 86_400_000;
98
+
99
+ /**
100
+ * What each watched cadence must leave on disk for a day it ran.
101
+ *
102
+ * Floors are calibrated against observed healthy runs with ~10x headroom (see the
103
+ * module header). `jsonLineField` makes the check read a FIELD VALUE rather than
104
+ * grep for a token, so an empty or corrupt log cannot pass on a coincidental
105
+ * substring.
106
+ *
107
+ * @type {Readonly<object>}
108
+ */
109
+ export const DEFAULT_OUTPUT_CONTRACTS = Object.freeze({
110
+ "daily-evening-wrap": Object.freeze({
111
+ label: "evening wrap",
112
+ // The cadence's first completed run. Declared, not derived from disk.
113
+ notBefore: "2026-08-26",
114
+ artefacts: Object.freeze([
115
+ Object.freeze({
116
+ // Written by the brief-writing step.
117
+ path: "outputs/briefs/{date}-evening-wrap.md",
118
+ minBytes: 500,
119
+ minLines: 5,
120
+ }),
121
+ Object.freeze({
122
+ // Written by the run-logging step — a DIFFERENT step, so the two
123
+ // artefacts corroborate rather than restate one another.
124
+ path: "logs/workflows/{date}-evening-wrap.jsonl",
125
+ minBytes: 200,
126
+ minLines: 3,
127
+ // Schema-agnostic on purpose: assert only that it IS a JSONL of
128
+ // timestamped records. Every field richer than this varies run to run
129
+ // (see header) and would fire on healthy days.
130
+ minJsonRecords: Object.freeze({ count: 3, requireField: "ts" }),
131
+ }),
132
+ ]),
133
+ }),
134
+ });
135
+
136
+ /** How many days back to check. 1 = yesterday only (see header: today is not yet due). */
137
+ export const DEFAULT_LOOKBACK_DAYS = 1;
138
+
139
+ /**
140
+ * Resolve the agent root the same way every other maestro module does.
141
+ * @param {object} [cfg]
142
+ * @returns {string}
143
+ */
144
+ function agentRoot(cfg = {}) {
145
+ return resolve(
146
+ cfg.agentRoot ||
147
+ process.env.AGENT_ROOT ||
148
+ process.env.AGENT_DIR ||
149
+ process.cwd()
150
+ );
151
+ }
152
+
153
+ /**
154
+ * UTC day key `YYYY-MM-DD`, `back` days before `now`. UTC because artefact
155
+ * filenames are UTC-keyed (module header).
156
+ * @param {number} now epoch ms
157
+ * @param {number} back days back
158
+ * @returns {string}
159
+ */
160
+ export function dayKeyUTC(now, back = 0) {
161
+ return new Date(now - back * MS_PER_DAY).toISOString().slice(0, 10);
162
+ }
163
+
164
+ /**
165
+ * Inspect one artefact. Returns `null` when it satisfies its contract, else a
166
+ * reason record. Never throws: an unreadable file is reported as a fault, not
167
+ * swallowed.
168
+ * @param {string} root
169
+ * @param {object} spec
170
+ * @param {string} day
171
+ * @returns {null|{path:string, reason:string, bytes:number|null, lines:number|null}}
172
+ */
173
+ function inspectArtefact(root, spec, day) {
174
+ const rel = String(spec.path || "").replace(/\{date\}/g, day);
175
+ const abs = join(root, rel);
176
+ const fault = (reason, bytes = null, lines = null) => ({ path: rel, reason, bytes, lines });
177
+
178
+ if (!existsSync(abs)) return fault("absent");
179
+
180
+ let bytes;
181
+ try {
182
+ bytes = statSync(abs).size;
183
+ } catch (err) {
184
+ return fault(`unstatable: ${err && err.message ? err.message : String(err)}`);
185
+ }
186
+
187
+ const minBytes = Number.isFinite(spec.minBytes) ? spec.minBytes : 0;
188
+ if (bytes < minBytes) return fault(`stub: ${bytes} bytes < ${minBytes} floor`, bytes);
189
+
190
+ let body;
191
+ try {
192
+ body = readFileSync(abs, "utf-8");
193
+ } catch (err) {
194
+ return fault(`unreadable: ${err && err.message ? err.message : String(err)}`, bytes);
195
+ }
196
+
197
+ const lines = body.split("\n").filter((l) => l.trim().length > 0);
198
+ const minLines = Number.isFinite(spec.minLines) ? spec.minLines : 0;
199
+ if (lines.length < minLines) {
200
+ return fault(`stub: ${lines.length} non-empty lines < ${minLines} floor`, bytes, lines.length);
201
+ }
202
+
203
+ // Structural bar for JSONL: enough PARSEABLE objects each carrying the required
204
+ // field. Tests parseability and a field's PRESENCE, never a field's name as a
205
+ // substring and never a value that varies between healthy runs.
206
+ const jr = spec.minJsonRecords;
207
+ if (jr && Number.isFinite(jr.count)) {
208
+ let records = 0;
209
+ for (const l of lines) {
210
+ let o;
211
+ try { o = JSON.parse(l); } catch { continue; }
212
+ if (!o || typeof o !== "object" || Array.isArray(o)) continue;
213
+ if (jr.requireField && o[jr.requireField] === undefined) continue;
214
+ records++;
215
+ }
216
+ if (records < jr.count) {
217
+ const q = jr.requireField ? ` carrying "${jr.requireField}"` : "";
218
+ return fault(
219
+ `only ${records} parseable JSON record(s)${q} < ${jr.count} floor`,
220
+ bytes,
221
+ lines.length
222
+ );
223
+ }
224
+ }
225
+
226
+ return null;
227
+ }
228
+
229
+ /**
230
+ * Did the watched cadences produce their output artefacts?
231
+ *
232
+ * @param {object} [cfg]
233
+ * @param {string} [cfg.agentRoot] agent root (default resolved as usual)
234
+ * @param {object} [cfg.contracts] override DEFAULT_OUTPUT_CONTRACTS
235
+ * @param {number} [cfg.lookbackDays] days back to check (default 1 = yesterday)
236
+ * @param {number} [cfg.now] clock in epoch ms (default Date.now())
237
+ * @returns {{ok:boolean, days:string[], checked:number,
238
+ * missing:Array<{cadence:string,label:string,day:string,path:string,reason:string}>}}
239
+ */
240
+ export function cadenceOutputFreshness(cfg = {}) {
241
+ const missing = [];
242
+ const days = [];
243
+ let checked = 0;
244
+ try {
245
+ const root = agentRoot(cfg);
246
+ const contracts = cfg.contracts && typeof cfg.contracts === "object"
247
+ ? cfg.contracts
248
+ : DEFAULT_OUTPUT_CONTRACTS;
249
+ const lookback = Number.isFinite(cfg.lookbackDays) && cfg.lookbackDays > 0
250
+ ? Math.floor(cfg.lookbackDays)
251
+ : DEFAULT_LOOKBACK_DAYS;
252
+ const now = Number.isFinite(cfg.now) ? cfg.now : Date.now();
253
+
254
+ for (let back = 1; back <= lookback; back++) days.push(dayKeyUTC(now, back));
255
+
256
+ for (const [cadence, contract] of Object.entries(contracts)) {
257
+ if (!contract || !Array.isArray(contract.artefacts)) continue;
258
+ for (const day of days) {
259
+ // A day before the cadence existed cannot be a miss.
260
+ if (contract.notBefore && day < contract.notBefore) continue;
261
+ for (const spec of contract.artefacts) {
262
+ checked++;
263
+ const fault = inspectArtefact(root, spec, day);
264
+ if (fault) {
265
+ missing.push({
266
+ cadence,
267
+ label: contract.label || cadence,
268
+ day,
269
+ path: fault.path,
270
+ reason: fault.reason,
271
+ });
272
+ }
273
+ }
274
+ }
275
+ }
276
+ } catch {
277
+ // Never throw. Whatever was accumulated before the failure is still valid.
278
+ }
279
+
280
+ // Deterministic order so an upstream de-dupe can key on the list.
281
+ missing.sort((a, b) =>
282
+ a.day < b.day ? -1 : a.day > b.day ? 1 : a.path < b.path ? -1 : a.path > b.path ? 1 : 0
283
+ );
284
+
285
+ return { ok: missing.length === 0, days, checked, missing };
286
+ }
287
+
288
+ export default { cadenceOutputFreshness, dayKeyUTC, DEFAULT_OUTPUT_CONTRACTS, DEFAULT_LOOKBACK_DAYS };
@@ -0,0 +1,343 @@
1
+ /**
2
+ * lib/engine/agents/definitions.mjs — subagent definitions: parsing, discovery,
3
+ * and the pure policy a child run is built from.
4
+ *
5
+ * A subagent is a Markdown file with YAML frontmatter (the publicly documented
6
+ * subagent file format):
7
+ *
8
+ * ---
9
+ * name: code-reader
10
+ * description: When to hand work to this agent.
11
+ * tools: Read, Grep, Glob (optional; absent = every tool the parent has)
12
+ * disallowedTools: Bash (optional)
13
+ * model: haiku (optional; absent or "inherit" = the parent's tier)
14
+ * permissionMode: plan (optional; never looser than the parent's)
15
+ * maxTurns: 20 (optional)
16
+ * ---
17
+ * The body is the subagent's own instructions.
18
+ *
19
+ * Unknown frontmatter keys are ignored. `tools` may be a comma/space separated
20
+ * string or a YAML list (maestro's `agents/<id>/agent.md` uses a JSON-style list).
21
+ *
22
+ * Where definitions come from, in precedence order (the first with a name wins):
23
+ * cli --agents '{"name":{"description","prompt","tools","model"}}'
24
+ * project <cwd>/.claude/agents/*.md
25
+ * seat $AGENT_ROOT/agents/<id>/agent.md (maestro; read only when AGENT_ROOT is set)
26
+ * user ~/.claude/agents/*.md
27
+ * plugin <plugin root>/agents/*.md, named `<plugin>:<name>`
28
+ * builtin general-purpose
29
+ * With --bare only cli, plugin (explicit --plugin-dir) and builtin are read.
30
+ *
31
+ * Everything here is pure except `discoverAgents`, which reads through an
32
+ * injected fs.
33
+ *
34
+ * @module lib/engine/agents/definitions
35
+ */
36
+
37
+ import path from "node:path";
38
+ import { parseSkillFile } from "../skills/index.mjs";
39
+ import { PERMISSION_MODES } from "../permissions.mjs";
40
+
41
+ /** The tool names that start or manage subagents. */
42
+ export const AGENT_TOOL_NAMES = Object.freeze(["Task", "Agent", "TaskOutput", "TaskStop"]);
43
+
44
+ /** The workflow tools (W4-A2): the main run's only, never a subagent's. */
45
+ export const WORKFLOW_TOOL_NAMES = Object.freeze(["Workflow", "WorkflowStatus"]);
46
+
47
+ /**
48
+ * Anthropic-slot aliases → Cohort tiers. Pinned equal to
49
+ * lib/runtime/adapter.mjs DEFAULT_COHORT_TIER_MAP (and so to
50
+ * lib/model-router/catalog/cohort.yaml `tier_map`) by agents.test.mjs.
51
+ */
52
+ export const DEFAULT_TIER_MAP = Object.freeze({ fable: "cohort-reason", opus: "cohort-agentic", sonnet: "cohort-agentic", haiku: "cohort-fast" });
53
+
54
+ /**
55
+ * @typedef {Object} AgentDefinition
56
+ * @property {string} name
57
+ * @property {string} description
58
+ * @property {string} prompt
59
+ * @property {string[]|null} tools null = inherit every parent tool
60
+ * @property {string[]} disallowedTools
61
+ * @property {string|null} model null = the parent's tier
62
+ * @property {string|null} permissionMode
63
+ * @property {number|null} maxTurns
64
+ * @property {'cli'|'project'|'seat'|'user'|'plugin'|'builtin'} source
65
+ * @property {string|null} file
66
+ */
67
+
68
+ /** Authorship: written for Cohort Engine by the Cohort team, 2026-09-14. */
69
+ export const GENERAL_PURPOSE_PROMPT =
70
+ "Work the task you were given through to a result using the tools you have. Search widely when you are not sure where something is, and read before you conclude. " +
71
+ "Your final message is the only thing the agent that started you will see, so make it complete on its own: what you found or did, with file paths and specifics.";
72
+
73
+ /** @type {AgentDefinition} */
74
+ export const GENERAL_PURPOSE_AGENT = Object.freeze({
75
+ name: "general-purpose",
76
+ description: "A general agent for multi-step research and implementation work, with every tool the caller has.",
77
+ prompt: GENERAL_PURPOSE_PROMPT,
78
+ tools: null,
79
+ disallowedTools: [],
80
+ model: null,
81
+ permissionMode: null,
82
+ maxTurns: null,
83
+ source: "builtin",
84
+ file: null,
85
+ });
86
+
87
+ /** @param {unknown} v @returns {string[]|null} */
88
+ export function toolList(v) {
89
+ if (v === undefined || v === null) return null;
90
+ if (Array.isArray(v)) return v.map((s) => String(s).trim()).filter(Boolean);
91
+ if (typeof v === "string") {
92
+ const s = v.trim();
93
+ if (s === "" || s === "*") return s === "*" ? null : [];
94
+ if (s.startsWith("[")) {
95
+ try {
96
+ const arr = JSON.parse(s);
97
+ if (Array.isArray(arr)) return arr.map((x) => String(x).trim()).filter(Boolean);
98
+ } catch {
99
+ /* not JSON after all: split below */
100
+ }
101
+ }
102
+ return s.split(/[,\s]+/).map((x) => x.trim()).filter(Boolean);
103
+ }
104
+ return null;
105
+ }
106
+
107
+ /**
108
+ * Build a definition from already-parsed fields. Returns an error for a
109
+ * definition that cannot be offered (no name or no description).
110
+ * @param {Record<string, unknown>} fm
111
+ * @param {{body:string, source:AgentDefinition['source'], file:string|null, fallbackName:string, namespace?:string|null}} o
112
+ * @returns {{ok:true, agent:AgentDefinition}|{ok:false, error:string}}
113
+ */
114
+ export function definitionFrom(fm, { body, source, file, fallbackName, namespace = null }) {
115
+ const base = typeof fm.name === "string" && fm.name.trim() !== "" ? fm.name.trim() : fallbackName;
116
+ if (!base) return { ok: false, error: `${file ?? source}: a subagent needs a name` };
117
+ const description = typeof fm.description === "string" ? fm.description.trim() : "";
118
+ if (description === "") return { ok: false, error: `${file ?? source}: subagent "${base}" needs a description` };
119
+ const model = typeof fm.model === "string" && fm.model.trim() !== "" && fm.model.trim().toLowerCase() !== "inherit" ? fm.model.trim() : null;
120
+ const mode = typeof fm.permissionMode === "string" && PERMISSION_MODES.includes(fm.permissionMode) ? fm.permissionMode : null;
121
+ const turns = Number(fm.maxTurns);
122
+ return {
123
+ ok: true,
124
+ agent: {
125
+ name: namespace ? `${namespace}:${base}` : base,
126
+ description,
127
+ prompt: String(body ?? "").trim(),
128
+ tools: toolList(fm.tools),
129
+ disallowedTools: toolList(fm.disallowedTools ?? fm["disallowed-tools"]) ?? [],
130
+ model,
131
+ permissionMode: mode,
132
+ maxTurns: Number.isInteger(turns) && turns > 0 ? turns : null,
133
+ source,
134
+ file,
135
+ },
136
+ };
137
+ }
138
+
139
+ /**
140
+ * @param {{file:string, text:string, source:AgentDefinition['source'], fallbackName:string, namespace?:string|null}} p
141
+ */
142
+ export function parseAgentFile({ file, text, source, fallbackName, namespace = null }) {
143
+ const { frontmatter, body } = parseSkillFile(text);
144
+ return definitionFrom(frontmatter, { body, source, file, fallbackName, namespace });
145
+ }
146
+
147
+ /**
148
+ * Parse the `--agents` flag: a JSON object of name → {description, prompt?,
149
+ * tools?, disallowedTools?, model?, permissionMode?, maxTurns?}.
150
+ * @param {string} text
151
+ * @returns {{ok:true, agents:AgentDefinition[]}|{ok:false, error:string}}
152
+ */
153
+ export function parseAgentsJson(text) {
154
+ let doc;
155
+ try {
156
+ doc = JSON.parse(String(text));
157
+ } catch (e) {
158
+ return { ok: false, error: `--agents is not valid JSON (${e instanceof Error ? e.message : e})` };
159
+ }
160
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) return { ok: false, error: "--agents must be a JSON object of name → definition" };
161
+ const agents = [];
162
+ for (const [name, spec] of Object.entries(doc)) {
163
+ if (!spec || typeof spec !== "object" || Array.isArray(spec)) return { ok: false, error: `--agents: "${name}" must be an object` };
164
+ if (spec.prompt !== undefined && typeof spec.prompt !== "string") return { ok: false, error: `--agents: "${name}".prompt must be a string` };
165
+ const r = definitionFrom({ ...spec, name }, { body: spec.prompt ?? "", source: "cli", file: null, fallbackName: name });
166
+ if (!r.ok) return { ok: false, error: `--agents: ${r.error.replace(/^cli: /, "")}` };
167
+ agents.push(r.agent);
168
+ }
169
+ return { ok: true, agents };
170
+ }
171
+
172
+ /**
173
+ * @typedef {Object} AgentFs
174
+ * @property {(p:string)=>string} readFile
175
+ * @property {(p:string)=>string[]} readdir
176
+ * @property {(p:string)=>boolean} isFile
177
+ * @property {(p:string)=>boolean} isDir
178
+ */
179
+
180
+ /**
181
+ * @param {object} p
182
+ * @param {AgentDefinition[]} [p.cliAgents]
183
+ * @param {string|null} p.userDir ~/.claude
184
+ * @param {string} p.cwd
185
+ * @param {string|null} [p.seatRoot] maestro agent root (agents/<id>/agent.md); null = no seat source
186
+ * @param {Array<{dir:string}>} [p.pluginDirs]
187
+ * @param {boolean} [p.bare]
188
+ * @param {AgentFs} p.fs
189
+ * @returns {{agents: AgentDefinition[], errors: string[]}}
190
+ */
191
+ export function discoverAgents({ cliAgents = [], userDir, cwd, seatRoot = null, pluginDirs = [], bare = false, fs }) {
192
+ /** @type {AgentDefinition[]} */
193
+ const agents = [];
194
+ const errors = [];
195
+ const byName = new Map();
196
+ const names = (/** @type string */ dir) => {
197
+ try {
198
+ return fs.readdir(dir).sort();
199
+ } catch {
200
+ return [];
201
+ }
202
+ };
203
+ const add = (/** @type {AgentDefinition} */ a) => {
204
+ if (byName.has(a.name)) {
205
+ if (a.source !== "builtin") errors.push(`subagent "${a.name}"${a.file ? ` at ${a.file}` : ""} is shadowed by the ${byName.get(a.name).source} definition`);
206
+ return;
207
+ }
208
+ byName.set(a.name, a);
209
+ agents.push(a);
210
+ };
211
+ const fromFile = (/** @type string */ file, /** @type {AgentDefinition['source']} */ source, /** @type string */ fallbackName, /** @type {string|null} */ namespace) => {
212
+ let text;
213
+ try {
214
+ text = fs.readFile(file);
215
+ } catch (e) {
216
+ errors.push(`subagent ${file}: cannot read (${e instanceof Error ? e.message : e})`);
217
+ return;
218
+ }
219
+ const r = parseAgentFile({ file, text, source, fallbackName, namespace });
220
+ if (r.ok) add(r.agent);
221
+ else errors.push(r.error);
222
+ };
223
+ const flatDir = (/** @type string */ dir, /** @type {AgentDefinition['source']} */ source, /** @type {string|null} */ namespace) => {
224
+ if (!fs.isDir(dir)) return;
225
+ for (const entry of names(dir)) {
226
+ const full = path.join(dir, entry);
227
+ if (entry.endsWith(".md") && fs.isFile(full)) fromFile(full, source, entry.slice(0, -3), namespace);
228
+ }
229
+ };
230
+
231
+ for (const a of cliAgents) add(a);
232
+ if (!bare) {
233
+ flatDir(path.join(cwd, ".claude", "agents"), "project", null);
234
+ if (seatRoot) {
235
+ const dir = path.join(seatRoot, "agents");
236
+ if (fs.isDir(dir)) {
237
+ for (const id of names(dir)) {
238
+ const file = path.join(dir, id, "agent.md");
239
+ if (fs.isFile(file)) fromFile(file, "seat", id, null);
240
+ }
241
+ }
242
+ }
243
+ if (userDir) flatDir(path.join(userDir, "agents"), "user", null);
244
+ }
245
+ for (const { dir } of pluginDirs) {
246
+ if (!fs.isDir(dir)) continue;
247
+ let plugin = path.basename(dir);
248
+ const manifest = path.join(dir, "plugin.json");
249
+ if (fs.isFile(manifest)) {
250
+ try {
251
+ const doc = JSON.parse(fs.readFile(manifest));
252
+ if (typeof doc?.name === "string" && doc.name.trim() !== "") plugin = doc.name.trim();
253
+ } catch {
254
+ /* the skills loader reports a bad manifest; the directory name stands in */
255
+ }
256
+ }
257
+ flatDir(path.join(dir, "agents"), "plugin", plugin);
258
+ }
259
+ add(GENERAL_PURPOSE_AGENT);
260
+ return { agents, errors };
261
+ }
262
+
263
+ /**
264
+ * Find a definition by the name the model passed (trimmed): exact, then a
265
+ * unique plugin agent whose unqualified name matches. Permission rules see the
266
+ * same spellings (permissions.mjs `subagentTypeMatches`), and the Task tool
267
+ * re-checks the resolved name.
268
+ * @param {AgentDefinition[]} agents @param {unknown} requested
269
+ * @returns {{ok:true, agent:AgentDefinition}|{ok:false, error:string}}
270
+ */
271
+ export function findAgent(agents, requested) {
272
+ const want = String(requested ?? "").trim();
273
+ const exact = agents.find((a) => a.name === want);
274
+ if (exact) return { ok: true, agent: exact };
275
+ const suffix = want.includes(":") ? [] : agents.filter((a) => a.name.endsWith(`:${want}`));
276
+ if (suffix.length === 1) return { ok: true, agent: suffix[0] };
277
+ return { ok: false, error: `There is no subagent type "${want}". Available: ${agents.map((a) => a.name).join(", ")}.` };
278
+ }
279
+
280
+ /**
281
+ * The tier a child runs on: a cohort-* tier as given; an Anthropic-slot alias
282
+ * (or a model id containing one, e.g. claude-sonnet-4-6) through the tier map;
283
+ * anything else — and no model — runs on the parent's tier.
284
+ * @param {string|null} model @param {string} parentModel @param {Record<string,string>} [tierMap]
285
+ * @returns {{model:string, note:string|null}}
286
+ */
287
+ export function resolveAgentModel(model, parentModel, tierMap = DEFAULT_TIER_MAP) {
288
+ const m = typeof model === "string" ? model.trim().toLowerCase() : "";
289
+ if (m === "" || m === "inherit") return { model: parentModel, note: null };
290
+ if (m.startsWith("cohort-")) return { model: m, note: null };
291
+ for (const slot of ["fable", "opus", "sonnet", "haiku"]) {
292
+ if (m.includes(slot) && tierMap[slot]) return { model: tierMap[slot], note: null };
293
+ }
294
+ return { model: parentModel, note: `model "${model}" has no Cohort tier; running on ${parentModel}` };
295
+ }
296
+
297
+ /** Permission modes from loosest to strictest. */
298
+ const MODE_RANK = Object.freeze({ bypassPermissions: 0, acceptEdits: 1, default: 2, dontAsk: 3, plan: 4 });
299
+
300
+ /**
301
+ * A child never runs looser than its parent: the stricter of the two modes.
302
+ * @param {string} parentMode @param {string|null} requested
303
+ */
304
+ export function clampPermissionMode(parentMode, requested) {
305
+ if (!requested || !(requested in MODE_RANK)) return parentMode;
306
+ const p = MODE_RANK[/** @type {keyof MODE_RANK} */ (parentMode)] ?? MODE_RANK.default;
307
+ return MODE_RANK[/** @type {keyof MODE_RANK} */ (requested)] > p ? requested : parentMode;
308
+ }
309
+
310
+ /**
311
+ * Does a name in a definition's tool list cover this tool? Exact names;
312
+ * `mcp__server` / `mcp__server__*` for a whole server; Task and Agent are one tool.
313
+ * @param {string} entry @param {string} toolName
314
+ */
315
+ export function toolEntryMatches(entry, toolName) {
316
+ const e = entry.replace(/\(.*\)$/, "");
317
+ if (e === toolName) return true;
318
+ if ((e === "Task" || e === "Agent") && (toolName === "Task" || toolName === "Agent")) return true;
319
+ if (e.startsWith("mcp__")) {
320
+ const rest = e.slice(5).replace(/__\*$/, "");
321
+ if (!rest.includes("__")) return toolName.startsWith(`mcp__${rest}__`);
322
+ }
323
+ return false;
324
+ }
325
+
326
+ /**
327
+ * The tool names a child gets: the definition's list (or every parent tool)
328
+ * intersected with the parent's tools, minus its disallowed tools, minus the
329
+ * subagent tools when the depth limit is reached. Pure.
330
+ * @param {string[]} parentToolNames
331
+ * @param {AgentDefinition} def
332
+ * @param {{canSpawn:boolean}} o
333
+ * @returns {{names:string[], missing:string[]}} `missing`: listed tools the parent does not have
334
+ */
335
+ export function childToolNames(parentToolNames, def, { canSpawn }) {
336
+ const listed = def.tools;
337
+ let names = listed === null ? [...parentToolNames] : parentToolNames.filter((n) => listed.some((e) => toolEntryMatches(e, n)));
338
+ names = names.filter((n) => !def.disallowedTools.some((e) => toolEntryMatches(e, n)) && !WORKFLOW_TOOL_NAMES.includes(n));
339
+ // Subagent tools travel together: TaskOutput/TaskStop only with Task or Agent.
340
+ if (!canSpawn || !names.some((n) => n === "Task" || n === "Agent")) names = names.filter((n) => !AGENT_TOOL_NAMES.includes(n));
341
+ const missing = listed === null ? [] : listed.filter((e) => !parentToolNames.some((n) => toolEntryMatches(e, n)));
342
+ return { names, missing };
343
+ }