@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,133 @@
1
+ /**
2
+ * lib/engine/mcp/results.mjs — MCP results → engine tool results (pure).
3
+ *
4
+ * Each MCP content block becomes text:
5
+ *
6
+ * text → its text
7
+ * image → with `attachImages`, a one-line note and the image itself
8
+ * in `images` (base64 + media type) for the wire to send as
9
+ * an image block — within the caps below; otherwise, or
10
+ * over a cap, a note with the MIME type and size
11
+ * audio → a note (no gateway wire carries audio input)
12
+ * resource → the embedded resource's text, or a note for a blob
13
+ * (an image blob is attached like an image)
14
+ * resource_link → a note naming the URI
15
+ * anything else → a note naming the block type
16
+ *
17
+ * `isError: true` makes an error result carrying the same text. A result with
18
+ * no content but `structuredContent` returns that JSON.
19
+ *
20
+ * @module lib/engine/mcp/results
21
+ */
22
+
23
+ /** Image types every image-capable wire accepts. */
24
+ export const IMAGE_MEDIA_TYPES = Object.freeze(new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]));
25
+ export const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
26
+ export const MAX_IMAGES_PER_RESULT = 8;
27
+ export const MAX_IMAGE_BYTES_PER_RESULT = 20 * 1024 * 1024;
28
+
29
+ /**
30
+ * @typedef {{mediaType:string, data:string}} ImageAttachment
31
+ * @typedef {{ok:true, content:string, images?:ImageAttachment[]} | {ok:false, error:string}} MappedResult
32
+ */
33
+
34
+ /**
35
+ * Attach an image if it fits; the note to put in the text either way.
36
+ * @param {string|undefined} mimeType @param {unknown} data
37
+ * @param {{attachImages:boolean, images:ImageAttachment[], bytes:number}} state
38
+ */
39
+ function imageNote(mimeType, data, state) {
40
+ const type = String(mimeType || "unknown type").toLowerCase();
41
+ const size = approxBytes(data);
42
+ if (!state.attachImages) return `[image content: ${type}, ${size} bytes — not shown]`;
43
+ let why = null;
44
+ if (typeof data !== "string" || data === "") why = "no data";
45
+ else if (!IMAGE_MEDIA_TYPES.has(type)) why = `${type} is not a supported image type`;
46
+ else if (size > MAX_IMAGE_BYTES) why = `larger than ${MAX_IMAGE_BYTES} bytes`;
47
+ else if (state.images.length >= MAX_IMAGES_PER_RESULT) why = `more than ${MAX_IMAGES_PER_RESULT} images in one result`;
48
+ else if (state.bytes + size > MAX_IMAGE_BYTES_PER_RESULT) why = `images in one result over ${MAX_IMAGE_BYTES_PER_RESULT} bytes`;
49
+ if (why) return `[image content: ${type}, ${size} bytes — not shown: ${why}]`;
50
+ state.images.push({ mediaType: type, data: /** @type string */ (data) });
51
+ state.bytes += size;
52
+ return `[image content: ${type}, ${size} bytes]`;
53
+ }
54
+
55
+ /**
56
+ * @param {unknown} result the JSON-RPC `result` of tools/call
57
+ * @param {{attachImages?:boolean}} [o]
58
+ * @returns {MappedResult}
59
+ */
60
+ export function mapCallToolResult(result, { attachImages = false } = {}) {
61
+ if (!result || typeof result !== "object") {
62
+ return { ok: false, error: "The MCP server returned a malformed tool result." };
63
+ }
64
+ const r = /** @type {any} */ (result);
65
+ const state = { attachImages, images: /** @type {ImageAttachment[]} */ ([]), bytes: 0 };
66
+ const parts = Array.isArray(r.content) ? r.content.map((b) => blockText(b, state)).filter((t) => t !== "") : [];
67
+ if (parts.length === 0 && r.structuredContent !== undefined) parts.push(safeJson(r.structuredContent));
68
+ const text = parts.join("\n\n");
69
+ if (r.isError === true) return { ok: false, error: text === "" ? "The MCP tool reported an error with no details." : text };
70
+ const out = /** @type {{ok:true, content:string, images?:ImageAttachment[]}} */ ({ ok: true, content: text === "" ? "(no output)" : text });
71
+ if (state.images.length > 0) out.images = state.images;
72
+ return out;
73
+ }
74
+
75
+ /**
76
+ * `resources/read` result → tool result.
77
+ * @param {unknown} result @param {{attachImages?:boolean}} [o]
78
+ * @returns {MappedResult}
79
+ */
80
+ export function mapReadResourceResult(result, { attachImages = false } = {}) {
81
+ const contents = result && typeof result === "object" && Array.isArray(/** @type any */ (result).contents) ? /** @type any */ (result).contents : null;
82
+ if (!contents) return { ok: false, error: "The MCP server returned a malformed resource." };
83
+ const state = { attachImages, images: /** @type {ImageAttachment[]} */ ([]), bytes: 0 };
84
+ const parts = contents.map((/** @type any */ c) => {
85
+ if (!c || typeof c !== "object") return "";
86
+ if (typeof c.text === "string") return `[resource ${c.uri ?? "(no uri)"}${c.mimeType ? `, ${c.mimeType}` : ""}]\n${c.text}`;
87
+ if (typeof c.mimeType === "string" && c.mimeType.toLowerCase().startsWith("image/")) return `[resource ${c.uri ?? "(no uri)"}] ${imageNote(c.mimeType, c.blob, state)}`;
88
+ return `[resource ${c.uri ?? "(no uri)"}: ${c.mimeType || "binary"}, ${approxBytes(c.blob)} bytes — not shown]`;
89
+ });
90
+ const text = parts.filter((t) => t !== "").join("\n\n");
91
+ const out = /** @type {{ok:true, content:string, images?:ImageAttachment[]}} */ ({ ok: true, content: text === "" ? "(empty resource)" : text });
92
+ if (state.images.length > 0) out.images = state.images;
93
+ return out;
94
+ }
95
+
96
+ /** @param {any} b @param {{attachImages:boolean, images:ImageAttachment[], bytes:number}} state */
97
+ function blockText(b, state) {
98
+ if (!b || typeof b !== "object") return "";
99
+ switch (b.type) {
100
+ case "text":
101
+ return typeof b.text === "string" ? b.text : "";
102
+ case "image":
103
+ return imageNote(b.mimeType, b.data, state);
104
+ case "audio":
105
+ return `[audio content: ${b.mimeType || "unknown type"}, ${approxBytes(b.data)} bytes — not shown]`;
106
+ case "resource": {
107
+ const res = b.resource || {};
108
+ if (typeof res.text === "string") return res.uri ? `[resource ${res.uri}]\n${res.text}` : res.text;
109
+ if (state.attachImages && typeof res.mimeType === "string" && res.mimeType.toLowerCase().startsWith("image/")) return `[resource ${res.uri || "(no uri)"}] ${imageNote(res.mimeType, res.blob, state)}`;
110
+ return `[resource ${res.uri || "(no uri)"}: ${res.mimeType || "binary"}, ${approxBytes(res.blob)} bytes — not shown]`;
111
+ }
112
+ case "resource_link":
113
+ return `[resource link: ${b.uri || "(no uri)"}${b.name ? ` (${b.name})` : ""}]`;
114
+ default:
115
+ return `[unsupported MCP content block "${String(b.type)}"]`;
116
+ }
117
+ }
118
+
119
+ /** @param {unknown} base64 */
120
+ export function approxBytes(base64) {
121
+ if (typeof base64 !== "string") return 0;
122
+ const pad = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
123
+ return Math.max(0, Math.floor((base64.length * 3) / 4) - pad);
124
+ }
125
+
126
+ /** @param {unknown} v */
127
+ function safeJson(v) {
128
+ try {
129
+ return JSON.stringify(v, null, 2);
130
+ } catch {
131
+ return String(v);
132
+ }
133
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * lib/engine/mcp/stdio.mjs — the MCP stdio transport.
3
+ *
4
+ * Per the MCP specification: the client launches the server as a subprocess;
5
+ * messages are JSON-RPC objects, one per line (UTF-8, newline-delimited, no
6
+ * embedded newlines) on the child's stdin/stdout; the child's stderr is log
7
+ * output and is never parsed. Shutdown closes the child's stdin, then sends
8
+ * SIGTERM, then SIGKILL, each after a grace period.
9
+ *
10
+ * The child's stderr is kept as a short tail so a crash can be reported with
11
+ * the server's own last words.
12
+ *
13
+ * @module lib/engine/mcp/stdio
14
+ */
15
+
16
+ import { spawn } from "node:child_process";
17
+
18
+ export const STDERR_TAIL_CHARS = 2_000;
19
+ export const SHUTDOWN_GRACE_MS = 1_500;
20
+
21
+ /**
22
+ * @param {object} p
23
+ * @param {string} p.command
24
+ * @param {string[]} [p.args]
25
+ * @param {Record<string,string|undefined>} [p.env] the full child environment
26
+ * @param {string} [p.cwd]
27
+ * @param {typeof spawn} [p.spawnImpl]
28
+ */
29
+ export function createStdioTransport({ command, args = [], env, cwd, spawnImpl = spawn }) {
30
+ /** @type {import('node:child_process').ChildProcessWithoutNullStreams|null} */
31
+ let child = null;
32
+ let stderrTail = "";
33
+ /** @type {string|null} */
34
+ let exitReason = null;
35
+ /** @type {Promise<void>|null} */
36
+ let exited = null;
37
+
38
+ return {
39
+ /**
40
+ * @param {(msg:unknown) => void} onMessage
41
+ * @param {(reason:string) => void} onClose
42
+ * @returns {{ok:true}|{ok:false, error:string}}
43
+ */
44
+ start(onMessage, onClose) {
45
+ try {
46
+ child = /** @type any */ (spawnImpl(command, args, { cwd, env, stdio: ["pipe", "pipe", "pipe"] }));
47
+ } catch (e) {
48
+ return { ok: false, error: `could not start "${command}": ${e instanceof Error ? e.message : e}` };
49
+ }
50
+ const c = /** @type {import('node:child_process').ChildProcessWithoutNullStreams} */ (child);
51
+ let buffer = "";
52
+ c.stdout.setEncoding("utf8");
53
+ c.stdout.on("data", (chunk) => {
54
+ buffer += chunk;
55
+ let nl;
56
+ while ((nl = buffer.indexOf("\n")) !== -1) {
57
+ const line = buffer.slice(0, nl).trim();
58
+ buffer = buffer.slice(nl + 1);
59
+ if (line === "") continue;
60
+ let msg;
61
+ try {
62
+ msg = JSON.parse(line);
63
+ } catch {
64
+ continue; // a server that logs to stdout: not a frame, not fatal
65
+ }
66
+ onMessage(msg);
67
+ }
68
+ });
69
+ c.stderr.setEncoding("utf8");
70
+ c.stderr.on("data", (chunk) => {
71
+ stderrTail = (stderrTail + chunk).slice(-STDERR_TAIL_CHARS);
72
+ });
73
+ c.stdin.on("error", () => {
74
+ /* EPIPE after the child died; the exit handler reports it */
75
+ });
76
+ exited = new Promise((resolve) => {
77
+ const done = (/** @type string */ reason) => {
78
+ if (exitReason !== null) return;
79
+ exitReason = reason;
80
+ onClose(reason);
81
+ resolve();
82
+ };
83
+ c.on("error", (e) => done(`could not start "${command}": ${e.message}`));
84
+ c.on("exit", (code, signal) => done(code !== null ? `exited with code ${code}` : `killed by ${signal}`));
85
+ });
86
+ return { ok: true };
87
+ },
88
+
89
+ /** @param {object} msg */
90
+ send(msg) {
91
+ if (!child || exitReason !== null) return { ok: false, error: `server process is not running (${exitReason ?? "not started"})` };
92
+ try {
93
+ child.stdin.write(`${JSON.stringify(msg)}\n`);
94
+ return { ok: true };
95
+ } catch (e) {
96
+ return { ok: false, error: `write to server failed: ${e instanceof Error ? e.message : e}` };
97
+ }
98
+ },
99
+
100
+ stderrTail: () => stderrTail.trim(),
101
+
102
+ /** Close stdin, then SIGTERM, then SIGKILL. Resolves once the child is gone. */
103
+ async close() {
104
+ if (!child || exitReason !== null || !exited) return;
105
+ const c = child;
106
+ const wait = (/** @type number */ ms) => {
107
+ /** @type {ReturnType<typeof setTimeout>|undefined} */
108
+ let timer;
109
+ const grace = new Promise((r) => {
110
+ timer = setTimeout(r, ms);
111
+ });
112
+ return Promise.race([exited, grace]).then(() => {
113
+ clearTimeout(timer);
114
+ return exitReason !== null;
115
+ });
116
+ };
117
+ try {
118
+ c.stdin.end();
119
+ } catch {
120
+ /* already closed */
121
+ }
122
+ if (await wait(SHUTDOWN_GRACE_MS)) return;
123
+ try {
124
+ c.kill("SIGTERM");
125
+ } catch {
126
+ /* already gone */
127
+ }
128
+ if (await wait(SHUTDOWN_GRACE_MS)) return;
129
+ try {
130
+ c.kill("SIGKILL");
131
+ } catch {
132
+ /* already gone */
133
+ }
134
+ await wait(SHUTDOWN_GRACE_MS);
135
+ },
136
+ };
137
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * lib/engine/mcp/supervisor.mjs — a crashed stdio server gets one restart per run.
3
+ *
4
+ * A stdio MCP server that dies (a crash, an out-of-memory kill) takes its
5
+ * tools with it. The supervisor wraps a connection with the same interface;
6
+ * on the NEXT call after the server stopped, it starts the server again —
7
+ * once per run, per server — and makes the call on the new connection. The
8
+ * result says the server was restarted. A second death is reported like any
9
+ * crash, with a note that the restart was already used. HTTP servers are not
10
+ * restarted (their process is not ours; each request already reconnects).
11
+ *
12
+ * If the restarted server lists different tools, `toolsChanged` is set so the
13
+ * run re-lists them at its next turn boundary.
14
+ *
15
+ * @module lib/engine/mcp/supervisor
16
+ */
17
+
18
+ import { connectMcpServer } from "./client.mjs";
19
+
20
+ export const MAX_RESTARTS_PER_RUN = 1;
21
+
22
+ /**
23
+ * @param {import('./config.mjs').McpServerConfig} config
24
+ * @param {Parameters<typeof connectMcpServer>[1]} deps
25
+ * @param {import('./client.mjs').McpConnection} first
26
+ * @param {{connect?:typeof connectMcpServer, maxRestarts?:number}} [o]
27
+ */
28
+ export function superviseConnection(config, deps, first, { connect = connectMcpServer, maxRestarts = MAX_RESTARTS_PER_RUN } = {}) {
29
+ let current = first;
30
+ let restarts = 0;
31
+ let closed = false;
32
+ let pendingRefresh = false;
33
+ /** @type {Promise<{ok:true, restarted:boolean, exhausted:boolean}|{ok:false, error:string}>|null} */
34
+ let restarting = null;
35
+ const toolNames = (/** @type any */ c) => JSON.stringify(c.tools.map((/** @type any */ t) => t.name));
36
+
37
+ /** @returns {Promise<{ok:true, restarted:boolean, exhausted:boolean}|{ok:false, error:string}>} */
38
+ const ensure = async () => {
39
+ if (closed || current.isAlive() || config.transport !== "stdio") return { ok: true, restarted: false, exhausted: false };
40
+ if (restarts >= maxRestarts) return { ok: true, restarted: false, exhausted: true };
41
+ if (!restarting) {
42
+ restarting = (async () => {
43
+ restarts++;
44
+ const before = toolNames(current);
45
+ const r = await connect(config, deps);
46
+ if (!r.ok) return { ok: /** @type {false} */ (false), error: `MCP server "${config.name}" had stopped, and restarting it failed: ${r.error}` };
47
+ await current.close().catch(() => {});
48
+ current = r.connection;
49
+ if (toolNames(current) !== before) pendingRefresh = true;
50
+ return { ok: /** @type {true} */ (true), restarted: true, exhausted: false };
51
+ })().finally(() => {
52
+ restarting = null;
53
+ });
54
+ }
55
+ return restarting;
56
+ };
57
+
58
+ const note = `(The MCP server "${config.name}" had stopped; it was restarted for this call.)`;
59
+ const exhaustedNote = ` It was already restarted once in this run and is not restarted again.`;
60
+
61
+ /**
62
+ * @template {{ok:boolean, content?:string, error?:string}} R
63
+ * @param {() => Promise<R>} op
64
+ * @returns {Promise<R|{ok:false, error:string}>}
65
+ */
66
+ const withRestart = async (op) => {
67
+ const e = await ensure();
68
+ if (!e.ok) return { ok: false, error: e.error };
69
+ const r = await op();
70
+ if (e.restarted) return r.ok ? { ...r, content: `${r.content}\n\n${note}` } : { ...r, error: `${r.error}\n\n${note}` };
71
+ if (e.exhausted && !r.ok) return { ...r, error: `${r.error}${exhaustedNote}` };
72
+ return r;
73
+ };
74
+
75
+ return {
76
+ get name() {
77
+ return current.name;
78
+ },
79
+ get protocolVersion() {
80
+ return current.protocolVersion;
81
+ },
82
+ get serverInfo() {
83
+ return current.serverInfo;
84
+ },
85
+ get instructions() {
86
+ return current.instructions;
87
+ },
88
+ get capabilities() {
89
+ return current.capabilities;
90
+ },
91
+ get tools() {
92
+ return current.tools;
93
+ },
94
+ get toolsChanged() {
95
+ return pendingRefresh || current.toolsChanged;
96
+ },
97
+ get restarts() {
98
+ return restarts;
99
+ },
100
+ isAlive: () => current.isAlive(),
101
+ async refreshTools() {
102
+ pendingRefresh = false;
103
+ return current.refreshTools();
104
+ },
105
+ /** @param {string} tool @param {object} args @param {{signal?:AbortSignal, timeoutMs?:number}} [o] */
106
+ callTool: (tool, args, o) => withRestart(() => current.callTool(tool, args, o)),
107
+ /** @param {{signal?:AbortSignal}} [o] */
108
+ listResources: (o) => withRestart(() => current.listResources(o)),
109
+ /** @param {string} uri @param {{signal?:AbortSignal}} [o] */
110
+ readResource: (uri, o) => withRestart(() => current.readResource(uri, o)),
111
+ async close() {
112
+ closed = true;
113
+ await current.close();
114
+ },
115
+ };
116
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * lib/engine/messages.mjs — the engine's canonical conversation shape (pure).
3
+ *
4
+ * The loop, the session store and both wires speak ONE message shape, so a
5
+ * transcript written while talking OpenAI Chat can be resumed on Anthropic
6
+ * Messages and vice versa. It is deliberately block-structured (the richer of
7
+ * the two wire formats); the OpenAI wire flattens it on the way out.
8
+ *
9
+ * { role: 'user', content: Array<TextBlock | ToolResultBlock> }
10
+ * { role: 'assistant', content: Array<TextBlock | ToolUseBlock | ThinkingBlock> }
11
+ *
12
+ * TextBlock { type:'text', text }
13
+ * ToolUseBlock { type:'tool_use', id, name, input, inputError?, rawInput? }
14
+ * ToolResultBlock { type:'tool_result', tool_use_id, content: string, is_error? }
15
+ * ThinkingBlock { type:'thinking', thinking, signature? }
16
+ *
17
+ * `inputError` / `rawInput` mark a tool call whose argument JSON did not parse;
18
+ * the loop attempts a repair and the wires never send those two fields.
19
+ *
20
+ * @module lib/engine/messages
21
+ */
22
+
23
+ /** @param {string} text */
24
+ export function userText(text) {
25
+ return { role: "user", content: [{ type: "text", text: String(text) }] };
26
+ }
27
+
28
+ /**
29
+ * Concatenate the text blocks of a message.
30
+ * @param {{content: any[]}|null|undefined} message
31
+ */
32
+ export function messageText(message) {
33
+ if (!message || !Array.isArray(message.content)) return "";
34
+ return message.content
35
+ .filter((b) => b && b.type === "text" && typeof b.text === "string")
36
+ .map((b) => b.text)
37
+ .join("");
38
+ }
39
+
40
+ /** @param {{content:any[]}} message */
41
+ export function toolUses(message) {
42
+ return Array.isArray(message?.content) ? message.content.filter((b) => b && b.type === "tool_use") : [];
43
+ }
44
+
45
+ /**
46
+ * Make a history safe to send:
47
+ * · consecutive same-role messages are merged (a resumed session whose last
48
+ * record is a user turn, followed by the new prompt, is one user turn);
49
+ * · an assistant `tool_use` with no matching `tool_result` in the next user
50
+ * turn (a run killed mid-tool) gets a synthetic error result, because both
51
+ * wires reject an unanswered tool call;
52
+ * · empty messages are dropped.
53
+ * Returns a new array; the input is not mutated.
54
+ *
55
+ * @param {Array<{role:string, content:any[]}>} messages
56
+ */
57
+ export function normaliseHistory(messages) {
58
+ /** @type {Array<{role:string, content:any[]}>} */
59
+ const merged = [];
60
+ for (const m of messages || []) {
61
+ if (!m || (m.role !== "user" && m.role !== "assistant") || !Array.isArray(m.content)) continue;
62
+ if (m.content.length === 0) continue;
63
+ const last = merged[merged.length - 1];
64
+ if (last && last.role === m.role) last.content = [...last.content, ...m.content];
65
+ else merged.push({ role: m.role, content: [...m.content] });
66
+ }
67
+ for (let i = 0; i < merged.length; i++) {
68
+ const m = merged[i];
69
+ if (m.role !== "assistant") continue;
70
+ const uses = toolUses(m);
71
+ if (uses.length === 0) continue;
72
+ let next = merged[i + 1];
73
+ if (!next || next.role !== "user") {
74
+ next = { role: "user", content: [] };
75
+ merged.splice(i + 1, 0, next);
76
+ }
77
+ const answered = new Set(next.content.filter((b) => b.type === "tool_result").map((b) => b.tool_use_id));
78
+ const missing = uses
79
+ .filter((u) => !answered.has(u.id))
80
+ .map((u) => ({
81
+ type: "tool_result",
82
+ tool_use_id: u.id,
83
+ content: "The run was interrupted before this tool finished; no result was recorded.",
84
+ is_error: true,
85
+ }));
86
+ // Tool results lead the user turn: the OpenAI wire requires tool messages
87
+ // to follow the assistant message directly.
88
+ if (missing.length > 0) next.content = [...missing, ...next.content];
89
+ }
90
+ return merged;
91
+ }
92
+
93
+ /**
94
+ * Deterministic JSON (sorted keys) — used for loop-detection signatures, where
95
+ * `{a,b}` and `{b,a}` are the same call.
96
+ * @param {unknown} v
97
+ * @returns {string}
98
+ */
99
+ export function stableStringify(v) {
100
+ if (v === null || typeof v !== "object") return JSON.stringify(v) ?? "null";
101
+ if (Array.isArray(v)) return `[${v.map(stableStringify).join(",")}]`;
102
+ const keys = Object.keys(v).sort();
103
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(/** @type any */ (v)[k])}`).join(",")}}`;
104
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * lib/engine/output/json.mjs — the headless result object (pure).
3
+ *
4
+ * maestro's dispatcher, responder and cost ledger already parse the JSON the
5
+ * `claude --print --output-format json` lane emits. The engine's result is a
6
+ * SUPERSET of that shape, so those readers work unchanged:
7
+ *
8
+ * type:'result', subtype, is_error, result, session_id, num_turns,
9
+ * duration_ms, duration_api_ms, total_cost_usd,
10
+ * usage{ input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens },
11
+ * permission_denials
12
+ *
13
+ * plus a `cohort` block with what only the gateway knows: request ids, the
14
+ * tier that served, integer micro-USD cost, quota state, the normalised usage
15
+ * record (design §4.5), and why the run stopped.
16
+ *
17
+ * `total_cost_usd` comes from the gateway's `x-cohort-cost-micros` and nothing
18
+ * else. If ANY request in the run lacked the header the figure is `null` —
19
+ * a partial sum would under-report, and a local price guess is exactly the
20
+ * pricer bug the design removes (C7). `cohort.costMicros` keeps the exact
21
+ * integer as a string; the float exists only at this display boundary.
22
+ *
23
+ * @module lib/engine/output/json
24
+ */
25
+
26
+ import { toResultUsage, emptyUsage } from "../wire/usage.mjs";
27
+ import { refusalFromWireError } from "../wire/errors.mjs";
28
+
29
+ /**
30
+ * @param {import('../loop.mjs').LoopStop} stop
31
+ * @returns {{ subtype: 'success'|'error_max_turns'|'error_max_budget_usd'|'error_during_execution', isError: boolean, message: string|null }}
32
+ */
33
+ export function classifyStop(stop) {
34
+ switch (stop.kind) {
35
+ case "completed":
36
+ return { subtype: "success", isError: false, message: null };
37
+ case "max_turns":
38
+ return { subtype: "error_max_turns", isError: true, message: `Reached the maximum number of turns (${stop.maxTurns}).` };
39
+ case "wall_clock":
40
+ return { subtype: "error_during_execution", isError: true, message: `Stopped after the ${stop.wallClockMs} ms time limit.` };
41
+ case "aborted":
42
+ return { subtype: "error_during_execution", isError: true, message: "The run was aborted." };
43
+ case "wire_error":
44
+ return {
45
+ subtype: "error_during_execution",
46
+ isError: true,
47
+ message: `Model request failed (${stop.error?.code ?? "unknown"}): ${stop.error?.message ?? ""}`.trim(),
48
+ };
49
+ case "loop_detected":
50
+ return {
51
+ subtype: "error_during_execution",
52
+ isError: true,
53
+ message: `Stopped: ${stop.tool} was called with identical input ${stop.repeats} turns in a row.`,
54
+ };
55
+ case "hook_stopped":
56
+ return { subtype: "error_during_execution", isError: true, message: `Stopped by a hook: ${stop.reason}` };
57
+ case "max_budget":
58
+ // W4-E1 (row 24): --max-budget-usd reached; no further model call was made.
59
+ return {
60
+ subtype: "error_max_budget_usd",
61
+ isError: true,
62
+ message: `Reached the maximum budget ($${stop.maxBudgetUsd}); spent $${Number(BigInt(stop.spentMicros)) / 1e6}.`,
63
+ };
64
+ default:
65
+ throw new Error(`classifyStop: unhandled stop kind ${JSON.stringify(/** @type any */ (stop).kind)}`);
66
+ }
67
+ }
68
+
69
+ /** @param {string|null} micros */
70
+ export function microsToUsd(micros) {
71
+ if (micros == null) return null;
72
+ return Number(BigInt(micros)) / 1e6;
73
+ }
74
+
75
+ /**
76
+ * @param {object} p
77
+ * @param {Awaited<ReturnType<typeof import('../loop.mjs').runLoop>>} p.outcome
78
+ * @param {string} p.sessionId
79
+ * @param {number} p.durationMs
80
+ * @param {string} p.wire
81
+ * @param {{ path:string, writeFailures:number, skipped:number }|null} [p.session]
82
+ */
83
+ export function buildResult({ outcome, sessionId, durationMs, wire, session = null }) {
84
+ const c = classifyStop(outcome.stop);
85
+ return {
86
+ type: "result",
87
+ subtype: c.subtype,
88
+ is_error: c.isError,
89
+ result: c.isError ? c.message : outcome.resultText,
90
+ session_id: sessionId,
91
+ num_turns: outcome.numTurns,
92
+ duration_ms: durationMs,
93
+ duration_api_ms: outcome.apiMs,
94
+ total_cost_usd: microsToUsd(outcome.costMicros),
95
+ usage: toResultUsage(outcome.usage),
96
+ permission_denials: [],
97
+ cohort: {
98
+ wire,
99
+ model: outcome.model,
100
+ modelTier: outcome.modelTier,
101
+ requestIds: outcome.requestIds,
102
+ costMicros: outcome.costMicros,
103
+ quota: outcome.quota,
104
+ usage: outcome.usage,
105
+ // CF-158: the gateway's own view of the same turns, off the terminal
106
+ // cohort frame — a DISTINCT key from `usage` above, which is the engine's
107
+ // accumulated count from the wire. The two are near-equal but not the
108
+ // same number, and naming one after the other is exactly the silent
109
+ // divergence CF-158 exists to stop. null when no frame reported usage.
110
+ //
111
+ // SCOPE, which differs from `usage` above: this is the PARENT loop's own
112
+ // requests. cli.mjs replaces `usage` with the parent+children aggregate
113
+ // when a run has subagents (aggregateUsage), and child frame usage is not
114
+ // threaded through that record — it is counted run-wide only by the
115
+ // shared budget meter, as `cohort.budget.observedTokens`. Two adjacent
116
+ // keys with different scopes is worth one comment; silently differing
117
+ // scopes is the same trap as silently differing numbers.
118
+ gatewayUsage: outcome.gatewayUsage ?? null,
119
+ stop: outcome.stop.kind === "wire_error" ? { kind: "wire_error" } : outcome.stop,
120
+ error: outcome.stop.kind === "wire_error" ? outcome.stop.error : null,
121
+ // A run that ended on a gateway refusal carries it in the §4.8 shape;
122
+ // maestro's rate-guard classifyCohortRefusal reads exactly this.
123
+ refusal: outcome.stop.kind === "wire_error" ? refusalFromWireError(outcome.stop.error) : null,
124
+ lastText: c.isError ? outcome.resultText : undefined,
125
+ session,
126
+ },
127
+ };
128
+ }
129
+
130
+ /**
131
+ * A result for a run that failed before the loop started (bad flags, no token,
132
+ * unknown session). Same shape, so a caller never special-cases it.
133
+ * @param {{ sessionId:string|null, code:string, message:string, durationMs:number, wire?:string|null }} p
134
+ */
135
+ export function buildSetupErrorResult({ sessionId, code, message, durationMs, wire = null }) {
136
+ return {
137
+ type: "result",
138
+ subtype: "error_during_execution",
139
+ is_error: true,
140
+ result: message,
141
+ session_id: sessionId,
142
+ num_turns: 0,
143
+ duration_ms: durationMs,
144
+ duration_api_ms: 0,
145
+ total_cost_usd: null,
146
+ usage: toResultUsage(emptyUsage()),
147
+ permission_denials: [],
148
+ cohort: {
149
+ wire,
150
+ model: null,
151
+ modelTier: null,
152
+ requestIds: [],
153
+ costMicros: null,
154
+ quota: null,
155
+ usage: emptyUsage("estimated"),
156
+ // No request was made, so the gateway reported nothing (CF-158).
157
+ gatewayUsage: null,
158
+ stop: { kind: "setup_error" },
159
+ error: { kind: "setup", code, message },
160
+ refusal: null,
161
+ session: null,
162
+ },
163
+ };
164
+ }