@cohortapp/agent-sdk 2.17.0 → 2.18.4

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