@relayflows/sdk 2.0.9 → 2.0.11

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 (485) hide show
  1. package/dist/adapters/base.d.ts +46 -0
  2. package/dist/adapters/base.d.ts.map +1 -0
  3. package/dist/adapters/base.js +15 -0
  4. package/dist/adapters/base.js.map +1 -0
  5. package/dist/adapters/claude.d.ts +5 -0
  6. package/dist/adapters/claude.d.ts.map +1 -0
  7. package/dist/adapters/claude.js +44 -0
  8. package/dist/adapters/claude.js.map +1 -0
  9. package/dist/adapters/codex.d.ts +5 -0
  10. package/dist/adapters/codex.d.ts.map +1 -0
  11. package/dist/adapters/codex.js +45 -0
  12. package/dist/adapters/codex.js.map +1 -0
  13. package/dist/adapters/index.d.ts +14 -0
  14. package/dist/adapters/index.d.ts.map +1 -0
  15. package/dist/adapters/index.js +29 -0
  16. package/dist/adapters/index.js.map +1 -0
  17. package/dist/adapters/wrapper.d.ts +12 -0
  18. package/dist/adapters/wrapper.d.ts.map +1 -0
  19. package/dist/adapters/wrapper.js +31 -0
  20. package/dist/adapters/wrapper.js.map +1 -0
  21. package/dist/agent-relay-receipt.d.ts +24 -0
  22. package/dist/agent-relay-receipt.d.ts.map +1 -0
  23. package/dist/agent-relay-receipt.js +66 -0
  24. package/dist/agent-relay-receipt.js.map +1 -0
  25. package/dist/agent-relay-state.d.ts +18 -0
  26. package/dist/agent-relay-state.d.ts.map +1 -0
  27. package/dist/agent-relay-state.js +58 -0
  28. package/dist/agent-relay-state.js.map +1 -0
  29. package/dist/agent-relay-transport.d.ts +31 -0
  30. package/dist/agent-relay-transport.d.ts.map +1 -0
  31. package/dist/agent-relay-transport.js +199 -0
  32. package/dist/agent-relay-transport.js.map +1 -0
  33. package/dist/authored-budget.d.ts +13 -0
  34. package/dist/authored-budget.d.ts.map +1 -0
  35. package/dist/authored-budget.js +78 -0
  36. package/dist/authored-budget.js.map +1 -0
  37. package/dist/authored-flow-error.d.ts +2 -21
  38. package/dist/authored-flow-error.d.ts.map +1 -1
  39. package/dist/authored-flow-error.js +1 -12
  40. package/dist/authored-flow-error.js.map +1 -1
  41. package/dist/authored-flow-executor.d.ts +13 -7
  42. package/dist/authored-flow-executor.d.ts.map +1 -1
  43. package/dist/authored-flow-executor.js +116 -235
  44. package/dist/authored-flow-executor.js.map +1 -1
  45. package/dist/authored-flow-loader.d.ts +10 -1
  46. package/dist/authored-flow-loader.d.ts.map +1 -1
  47. package/dist/authored-flow-loader.js +56 -3
  48. package/dist/authored-flow-loader.js.map +1 -1
  49. package/dist/authored-flow-operation.d.ts +9 -1
  50. package/dist/authored-flow-operation.d.ts.map +1 -1
  51. package/dist/authored-flow-operation.js +35 -4
  52. package/dist/authored-flow-operation.js.map +1 -1
  53. package/dist/authored-helper-effect.d.ts +12 -0
  54. package/dist/authored-helper-effect.d.ts.map +1 -0
  55. package/dist/authored-helper-effect.js +144 -0
  56. package/dist/authored-helper-effect.js.map +1 -0
  57. package/dist/authored-mcp.d.ts +17 -0
  58. package/dist/authored-mcp.d.ts.map +1 -0
  59. package/dist/authored-mcp.js +171 -0
  60. package/dist/authored-mcp.js.map +1 -0
  61. package/dist/authored-memory.d.ts +7 -0
  62. package/dist/authored-memory.d.ts.map +1 -0
  63. package/dist/authored-memory.js +60 -0
  64. package/dist/authored-memory.js.map +1 -0
  65. package/dist/authored-plugin-effect.d.ts +16 -0
  66. package/dist/authored-plugin-effect.d.ts.map +1 -0
  67. package/dist/authored-plugin-effect.js +163 -0
  68. package/dist/authored-plugin-effect.js.map +1 -0
  69. package/dist/authored-step-output.d.ts +23 -0
  70. package/dist/authored-step-output.d.ts.map +1 -0
  71. package/dist/authored-step-output.js +67 -0
  72. package/dist/authored-step-output.js.map +1 -0
  73. package/dist/authored-worker-step.d.ts +15 -0
  74. package/dist/authored-worker-step.d.ts.map +1 -0
  75. package/dist/authored-worker-step.js +136 -0
  76. package/dist/authored-worker-step.js.map +1 -0
  77. package/dist/budget-preflight.d.ts +5 -0
  78. package/dist/budget-preflight.d.ts.map +1 -0
  79. package/dist/budget-preflight.js +33 -0
  80. package/dist/budget-preflight.js.map +1 -0
  81. package/dist/budget.d.ts +14 -0
  82. package/dist/budget.d.ts.map +1 -0
  83. package/dist/budget.js +52 -0
  84. package/dist/budget.js.map +1 -0
  85. package/dist/bundle-transport.d.ts +18 -0
  86. package/dist/bundle-transport.d.ts.map +1 -0
  87. package/dist/bundle-transport.js +111 -0
  88. package/dist/bundle-transport.js.map +1 -0
  89. package/dist/bundle-typescript.d.ts +8 -0
  90. package/dist/bundle-typescript.d.ts.map +1 -0
  91. package/dist/bundle-typescript.js +108 -0
  92. package/dist/bundle-typescript.js.map +1 -0
  93. package/dist/bundle.d.ts +25 -0
  94. package/dist/bundle.d.ts.map +1 -0
  95. package/dist/bundle.js +199 -0
  96. package/dist/bundle.js.map +1 -0
  97. package/dist/child-stop.d.ts +59 -0
  98. package/dist/child-stop.d.ts.map +1 -0
  99. package/dist/child-stop.js +94 -0
  100. package/dist/child-stop.js.map +1 -0
  101. package/dist/cli/add.d.ts +6 -0
  102. package/dist/cli/add.d.ts.map +1 -0
  103. package/dist/cli/add.js +47 -0
  104. package/dist/cli/add.js.map +1 -0
  105. package/dist/cli/build.d.ts +13 -0
  106. package/dist/cli/build.d.ts.map +1 -0
  107. package/dist/cli/build.js +224 -0
  108. package/dist/cli/build.js.map +1 -0
  109. package/dist/cli/bundle-preflight.d.ts +5 -0
  110. package/dist/cli/bundle-preflight.d.ts.map +1 -0
  111. package/dist/cli/bundle-preflight.js +28 -0
  112. package/dist/cli/bundle-preflight.js.map +1 -0
  113. package/dist/cli/check-helper-body.d.ts +4 -0
  114. package/dist/cli/check-helper-body.d.ts.map +1 -0
  115. package/dist/cli/check-helper-body.js +15 -0
  116. package/dist/cli/check-helper-body.js.map +1 -0
  117. package/dist/cli/check-triggers.d.ts +14 -0
  118. package/dist/cli/check-triggers.d.ts.map +1 -0
  119. package/dist/cli/check-triggers.js +37 -0
  120. package/dist/cli/check-triggers.js.map +1 -0
  121. package/dist/cli/check-typescript.d.ts +20 -0
  122. package/dist/cli/check-typescript.d.ts.map +1 -0
  123. package/dist/cli/check-typescript.js +54 -0
  124. package/dist/cli/check-typescript.js.map +1 -0
  125. package/dist/cli/check.d.ts +36 -3
  126. package/dist/cli/check.d.ts.map +1 -1
  127. package/dist/cli/check.js +122 -11
  128. package/dist/cli/check.js.map +1 -1
  129. package/dist/cli/cloud-run.d.ts +8 -0
  130. package/dist/cli/cloud-run.d.ts.map +1 -0
  131. package/dist/cli/cloud-run.js +50 -0
  132. package/dist/cli/cloud-run.js.map +1 -0
  133. package/dist/cli/deploy.d.ts +9 -0
  134. package/dist/cli/deploy.d.ts.map +1 -0
  135. package/dist/cli/deploy.js +58 -0
  136. package/dist/cli/deploy.js.map +1 -0
  137. package/dist/cli/deterministic-failure.d.ts +5 -0
  138. package/dist/cli/deterministic-failure.d.ts.map +1 -0
  139. package/dist/cli/deterministic-failure.js +66 -0
  140. package/dist/cli/deterministic-failure.js.map +1 -0
  141. package/dist/cli/direct-run.d.ts.map +1 -1
  142. package/dist/cli/direct-run.js +83 -18
  143. package/dist/cli/direct-run.js.map +1 -1
  144. package/dist/cli/hn-monitor.d.ts.map +1 -1
  145. package/dist/cli/hn-monitor.js +2 -1
  146. package/dist/cli/hn-monitor.js.map +1 -1
  147. package/dist/cli/replay.d.ts +12 -0
  148. package/dist/cli/replay.d.ts.map +1 -0
  149. package/dist/cli/replay.js +82 -0
  150. package/dist/cli/replay.js.map +1 -0
  151. package/dist/cli/reuse.d.ts +8 -0
  152. package/dist/cli/reuse.d.ts.map +1 -0
  153. package/dist/cli/reuse.js +25 -0
  154. package/dist/cli/reuse.js.map +1 -0
  155. package/dist/cli/run-digest.d.ts +4 -0
  156. package/dist/cli/run-digest.d.ts.map +1 -0
  157. package/dist/cli/run-digest.js +24 -0
  158. package/dist/cli/run-digest.js.map +1 -0
  159. package/dist/cli/run.d.ts +16 -5
  160. package/dist/cli/run.d.ts.map +1 -1
  161. package/dist/cli/run.js +99 -17
  162. package/dist/cli/run.js.map +1 -1
  163. package/dist/cli/serve-webhook.d.ts +40 -0
  164. package/dist/cli/serve-webhook.d.ts.map +1 -0
  165. package/dist/cli/serve-webhook.js +233 -0
  166. package/dist/cli/serve-webhook.js.map +1 -0
  167. package/dist/cli/tick-runner.d.ts.map +1 -1
  168. package/dist/cli/tick-runner.js +2 -1
  169. package/dist/cli/tick-runner.js.map +1 -1
  170. package/dist/cli-adapter.d.ts +8 -14
  171. package/dist/cli-adapter.d.ts.map +1 -1
  172. package/dist/cli-adapter.js +19 -64
  173. package/dist/cli-adapter.js.map +1 -1
  174. package/dist/cli-watch.d.ts +13 -0
  175. package/dist/cli-watch.d.ts.map +1 -0
  176. package/dist/cli-watch.js +185 -0
  177. package/dist/cli-watch.js.map +1 -0
  178. package/dist/cli.d.ts +4 -0
  179. package/dist/cli.d.ts.map +1 -1
  180. package/dist/cli.js +387 -14
  181. package/dist/cli.js.map +1 -1
  182. package/dist/cloud-http.d.ts +22 -0
  183. package/dist/cloud-http.d.ts.map +1 -0
  184. package/dist/cloud-http.js +84 -0
  185. package/dist/cloud-http.js.map +1 -0
  186. package/dist/cloud-run.d.ts +38 -0
  187. package/dist/cloud-run.d.ts.map +1 -0
  188. package/dist/cloud-run.js +112 -0
  189. package/dist/cloud-run.js.map +1 -0
  190. package/dist/compile.d.ts +21 -3
  191. package/dist/compile.d.ts.map +1 -1
  192. package/dist/compile.js +97 -23
  193. package/dist/compile.js.map +1 -1
  194. package/dist/create-flow.d.ts +17 -0
  195. package/dist/create-flow.d.ts.map +1 -0
  196. package/dist/create-flow.js +72 -0
  197. package/dist/create-flow.js.map +1 -0
  198. package/dist/daemon-connection.d.ts +14 -0
  199. package/dist/daemon-connection.d.ts.map +1 -1
  200. package/dist/daemon-connection.js +33 -1
  201. package/dist/daemon-connection.js.map +1 -1
  202. package/dist/demo-hn-monitor.js +2 -1
  203. package/dist/demo-hn-monitor.js.map +1 -1
  204. package/dist/effect-channel.d.ts +30 -0
  205. package/dist/effect-channel.d.ts.map +1 -0
  206. package/dist/effect-channel.js +71 -0
  207. package/dist/effect-channel.js.map +1 -0
  208. package/dist/failure-kinds.d.ts +14 -3
  209. package/dist/failure-kinds.d.ts.map +1 -1
  210. package/dist/failure-kinds.js +27 -0
  211. package/dist/failure-kinds.js.map +1 -1
  212. package/dist/gate-contract.d.ts.map +1 -1
  213. package/dist/gate-contract.js +9 -0
  214. package/dist/gate-contract.js.map +1 -1
  215. package/dist/helper-preflight.d.ts +18 -0
  216. package/dist/helper-preflight.d.ts.map +1 -0
  217. package/dist/helper-preflight.js +32 -0
  218. package/dist/helper-preflight.js.map +1 -0
  219. package/dist/helper-storage.d.ts +5 -0
  220. package/dist/helper-storage.d.ts.map +1 -0
  221. package/dist/helper-storage.js +31 -0
  222. package/dist/helper-storage.js.map +1 -0
  223. package/dist/helper-writeback.d.ts +12 -0
  224. package/dist/helper-writeback.d.ts.map +1 -0
  225. package/dist/helper-writeback.js +95 -0
  226. package/dist/helper-writeback.js.map +1 -0
  227. package/dist/index.d.ts +10 -2
  228. package/dist/index.d.ts.map +1 -1
  229. package/dist/index.js +7 -1
  230. package/dist/index.js.map +1 -1
  231. package/dist/input-binding.d.ts +4 -0
  232. package/dist/input-binding.d.ts.map +1 -0
  233. package/dist/input-binding.js +78 -0
  234. package/dist/input-binding.js.map +1 -0
  235. package/dist/journal-client.d.ts +12 -4
  236. package/dist/journal-client.d.ts.map +1 -1
  237. package/dist/journal-client.js +17 -6
  238. package/dist/journal-client.js.map +1 -1
  239. package/dist/journal-offset.d.ts +3 -0
  240. package/dist/journal-offset.d.ts.map +1 -0
  241. package/dist/journal-offset.js +69 -0
  242. package/dist/journal-offset.js.map +1 -0
  243. package/dist/journal-reader.d.ts +30 -0
  244. package/dist/journal-reader.d.ts.map +1 -0
  245. package/dist/journal-reader.js +156 -0
  246. package/dist/journal-reader.js.map +1 -0
  247. package/dist/json-schema.d.ts +2 -0
  248. package/dist/json-schema.d.ts.map +1 -1
  249. package/dist/json-schema.js +38 -22
  250. package/dist/json-schema.js.map +1 -1
  251. package/dist/llm-worker.d.ts +16 -0
  252. package/dist/llm-worker.d.ts.map +1 -0
  253. package/dist/llm-worker.js +79 -0
  254. package/dist/llm-worker.js.map +1 -0
  255. package/dist/local-agent.d.ts +8 -0
  256. package/dist/local-agent.d.ts.map +1 -0
  257. package/dist/local-agent.js +25 -0
  258. package/dist/local-agent.js.map +1 -0
  259. package/dist/mcp-client.d.ts +15 -0
  260. package/dist/mcp-client.d.ts.map +1 -0
  261. package/dist/mcp-client.js +104 -0
  262. package/dist/mcp-client.js.map +1 -0
  263. package/dist/mcp-config.d.ts +4 -0
  264. package/dist/mcp-config.d.ts.map +1 -0
  265. package/dist/mcp-config.js +61 -0
  266. package/dist/mcp-config.js.map +1 -0
  267. package/dist/mcp-stdio.d.ts +24 -0
  268. package/dist/mcp-stdio.d.ts.map +1 -0
  269. package/dist/mcp-stdio.js +89 -0
  270. package/dist/mcp-stdio.js.map +1 -0
  271. package/dist/model-pricing.d.ts +22 -0
  272. package/dist/model-pricing.d.ts.map +1 -0
  273. package/dist/model-pricing.js +30 -0
  274. package/dist/model-pricing.js.map +1 -0
  275. package/dist/mount-registry.d.ts +9 -0
  276. package/dist/mount-registry.d.ts.map +1 -0
  277. package/dist/mount-registry.js +47 -0
  278. package/dist/mount-registry.js.map +1 -0
  279. package/dist/named-gate-lowering.d.ts +9 -0
  280. package/dist/named-gate-lowering.d.ts.map +1 -0
  281. package/dist/named-gate-lowering.js +152 -0
  282. package/dist/named-gate-lowering.js.map +1 -0
  283. package/dist/named-gates.d.ts +10 -0
  284. package/dist/named-gates.d.ts.map +1 -0
  285. package/dist/named-gates.js +69 -0
  286. package/dist/named-gates.js.map +1 -0
  287. package/dist/observer-link.d.ts +129 -0
  288. package/dist/observer-link.d.ts.map +1 -0
  289. package/dist/observer-link.js +274 -0
  290. package/dist/observer-link.js.map +1 -0
  291. package/dist/plugin-loader.d.ts +13 -0
  292. package/dist/plugin-loader.d.ts.map +1 -0
  293. package/dist/plugin-loader.js +110 -0
  294. package/dist/plugin-loader.js.map +1 -0
  295. package/dist/plugin-manifest.d.ts +27 -0
  296. package/dist/plugin-manifest.d.ts.map +1 -0
  297. package/dist/plugin-manifest.js +89 -0
  298. package/dist/plugin-manifest.js.map +1 -0
  299. package/dist/preflight.d.ts +26 -2
  300. package/dist/preflight.d.ts.map +1 -1
  301. package/dist/preflight.js +175 -1
  302. package/dist/preflight.js.map +1 -1
  303. package/dist/progress.d.ts +14 -0
  304. package/dist/progress.d.ts.map +1 -0
  305. package/dist/progress.js +39 -0
  306. package/dist/progress.js.map +1 -0
  307. package/dist/protocol.d.ts +11 -0
  308. package/dist/protocol.d.ts.map +1 -1
  309. package/dist/pty-sidechannel.d.ts +14 -0
  310. package/dist/pty-sidechannel.d.ts.map +1 -0
  311. package/dist/pty-sidechannel.js +103 -0
  312. package/dist/pty-sidechannel.js.map +1 -0
  313. package/dist/scope-compiler.d.ts +52 -0
  314. package/dist/scope-compiler.d.ts.map +1 -0
  315. package/dist/scope-compiler.js +62 -0
  316. package/dist/scope-compiler.js.map +1 -0
  317. package/dist/slack-preflight.d.ts +6 -0
  318. package/dist/slack-preflight.d.ts.map +1 -0
  319. package/dist/slack-preflight.js +28 -0
  320. package/dist/slack-preflight.js.map +1 -0
  321. package/dist/slack-writeback.d.ts +40 -0
  322. package/dist/slack-writeback.d.ts.map +1 -0
  323. package/dist/slack-writeback.js +60 -0
  324. package/dist/slack-writeback.js.map +1 -0
  325. package/dist/spec.d.ts +134 -4
  326. package/dist/spec.d.ts.map +1 -1
  327. package/dist/spec.js.map +1 -1
  328. package/dist/step-dependencies.d.ts.map +1 -1
  329. package/dist/step-dependencies.js +2 -1
  330. package/dist/step-dependencies.js.map +1 -1
  331. package/dist/step-fields.d.ts +4 -4
  332. package/dist/step-fields.d.ts.map +1 -1
  333. package/dist/step-fields.js +5 -2
  334. package/dist/step-fields.js.map +1 -1
  335. package/dist/trigger-executor.d.ts +7 -0
  336. package/dist/trigger-executor.d.ts.map +1 -0
  337. package/dist/trigger-executor.js +35 -0
  338. package/dist/trigger-executor.js.map +1 -0
  339. package/dist/validate.d.ts.map +1 -1
  340. package/dist/validate.js +53 -6
  341. package/dist/validate.js.map +1 -1
  342. package/dist/webhook-rate-limit.d.ts +29 -0
  343. package/dist/webhook-rate-limit.d.ts.map +1 -0
  344. package/dist/webhook-rate-limit.js +38 -0
  345. package/dist/webhook-rate-limit.js.map +1 -0
  346. package/dist/webhook-signature.d.ts +22 -0
  347. package/dist/webhook-signature.d.ts.map +1 -0
  348. package/dist/webhook-signature.js +53 -0
  349. package/dist/webhook-signature.js.map +1 -0
  350. package/dist/worker-cli.d.ts +16 -1
  351. package/dist/worker-cli.d.ts.map +1 -1
  352. package/dist/worker-cli.js +129 -12
  353. package/dist/worker-cli.js.map +1 -1
  354. package/dist/worker-input.d.ts +4 -0
  355. package/dist/worker-input.d.ts.map +1 -0
  356. package/dist/worker-input.js +14 -0
  357. package/dist/worker-input.js.map +1 -0
  358. package/dist/worker-lease.d.ts +5 -0
  359. package/dist/worker-lease.d.ts.map +1 -0
  360. package/dist/worker-lease.js +80 -0
  361. package/dist/worker-lease.js.map +1 -0
  362. package/dist/worker-spend.d.ts +19 -0
  363. package/dist/worker-spend.d.ts.map +1 -0
  364. package/dist/worker-spend.js +22 -0
  365. package/dist/worker-spend.js.map +1 -0
  366. package/dist/worker-usage.d.ts +7 -0
  367. package/dist/worker-usage.d.ts.map +1 -0
  368. package/dist/worker-usage.js +54 -0
  369. package/dist/worker-usage.js.map +1 -0
  370. package/dist/worker.d.ts +2 -0
  371. package/dist/worker.d.ts.map +1 -1
  372. package/dist/worker.js +29 -9
  373. package/dist/worker.js.map +1 -1
  374. package/dist/wrapper-session.d.ts +1 -1
  375. package/dist/wrapper-session.d.ts.map +1 -1
  376. package/dist/wrapper-session.js +56 -19
  377. package/dist/wrapper-session.js.map +1 -1
  378. package/dist/yaml-helper-effect.d.ts +8 -0
  379. package/dist/yaml-helper-effect.d.ts.map +1 -0
  380. package/dist/yaml-helper-effect.js +79 -0
  381. package/dist/yaml-helper-effect.js.map +1 -0
  382. package/dist/yaml-helpers.d.ts +18 -0
  383. package/dist/yaml-helpers.d.ts.map +1 -0
  384. package/dist/yaml-helpers.js +116 -0
  385. package/dist/yaml-helpers.js.map +1 -0
  386. package/package.json +19 -2
  387. package/src/adapters/base.ts +53 -0
  388. package/src/adapters/claude.ts +55 -0
  389. package/src/adapters/codex.ts +56 -0
  390. package/src/adapters/index.ts +36 -0
  391. package/src/adapters/wrapper.ts +42 -0
  392. package/src/agent-relay-receipt.ts +120 -0
  393. package/src/agent-relay-state.ts +73 -0
  394. package/src/agent-relay-transport.ts +273 -0
  395. package/src/authored-budget.ts +69 -0
  396. package/src/authored-flow-error.ts +10 -10
  397. package/src/authored-flow-executor.ts +171 -347
  398. package/src/authored-flow-loader.ts +60 -3
  399. package/src/authored-flow-operation.ts +38 -5
  400. package/src/authored-helper-effect.ts +143 -0
  401. package/src/authored-mcp.ts +166 -0
  402. package/src/authored-memory.ts +55 -0
  403. package/src/authored-plugin-effect.ts +152 -0
  404. package/src/authored-step-output.ts +107 -0
  405. package/src/authored-worker-step.ts +181 -0
  406. package/src/budget-preflight.ts +31 -0
  407. package/src/budget.ts +49 -0
  408. package/src/bundle-transport.ts +79 -0
  409. package/src/bundle-typescript.ts +98 -0
  410. package/src/bundle.ts +182 -0
  411. package/src/child-stop.ts +138 -0
  412. package/src/cli/add.ts +45 -0
  413. package/src/cli/build.ts +214 -0
  414. package/src/cli/bundle-preflight.ts +28 -0
  415. package/src/cli/check-helper-body.ts +14 -0
  416. package/src/cli/check-triggers.ts +41 -0
  417. package/src/cli/check-typescript.ts +63 -0
  418. package/src/cli/check.ts +132 -14
  419. package/src/cli/cloud-run.ts +47 -0
  420. package/src/cli/deploy.ts +52 -0
  421. package/src/cli/deterministic-failure.ts +69 -0
  422. package/src/cli/direct-run.ts +79 -18
  423. package/src/cli/hn-monitor.ts +2 -1
  424. package/src/cli/replay.ts +81 -0
  425. package/src/cli/reuse.ts +23 -0
  426. package/src/cli/run-digest.ts +24 -0
  427. package/src/cli/run.ts +104 -21
  428. package/src/cli/serve-webhook.ts +246 -0
  429. package/src/cli/tick-runner.ts +2 -1
  430. package/src/cli-adapter.ts +31 -74
  431. package/src/cli-watch.ts +174 -0
  432. package/src/cli.ts +396 -17
  433. package/src/cloud-http.ts +100 -0
  434. package/src/cloud-run.ts +137 -0
  435. package/src/compile.ts +102 -23
  436. package/src/create-flow.ts +82 -0
  437. package/src/daemon-connection.ts +32 -1
  438. package/src/demo-hn-monitor.ts +2 -1
  439. package/src/effect-channel.ts +97 -0
  440. package/src/failure-kinds.ts +38 -0
  441. package/src/gate-contract.ts +7 -0
  442. package/src/helper-preflight.ts +34 -0
  443. package/src/helper-storage.ts +23 -0
  444. package/src/helper-writeback.ts +83 -0
  445. package/src/index.ts +26 -0
  446. package/src/input-binding.ts +76 -0
  447. package/src/journal-client.ts +20 -7
  448. package/src/journal-offset.ts +60 -0
  449. package/src/journal-reader.ts +165 -0
  450. package/src/json-schema.ts +34 -18
  451. package/src/llm-worker.ts +82 -0
  452. package/src/local-agent.ts +30 -0
  453. package/src/mcp-client.ts +97 -0
  454. package/src/mcp-config.ts +42 -0
  455. package/src/mcp-stdio.ts +84 -0
  456. package/src/model-pricing.ts +32 -0
  457. package/src/mount-registry.ts +44 -0
  458. package/src/named-gate-lowering.ts +147 -0
  459. package/src/named-gates.ts +76 -0
  460. package/src/observer-link.ts +368 -0
  461. package/src/plugin-loader.ts +88 -0
  462. package/src/plugin-manifest.ts +76 -0
  463. package/src/preflight.ts +178 -4
  464. package/src/progress.ts +51 -0
  465. package/src/protocol.ts +12 -0
  466. package/src/pty-sidechannel.ts +92 -0
  467. package/src/scope-compiler.ts +105 -0
  468. package/src/slack-preflight.ts +25 -0
  469. package/src/slack-writeback.ts +63 -0
  470. package/src/spec.ts +112 -4
  471. package/src/step-dependencies.ts +3 -1
  472. package/src/step-fields.ts +5 -2
  473. package/src/trigger-executor.ts +34 -0
  474. package/src/validate.ts +47 -6
  475. package/src/webhook-rate-limit.ts +69 -0
  476. package/src/webhook-signature.ts +74 -0
  477. package/src/worker-cli.ts +150 -11
  478. package/src/worker-input.ts +15 -0
  479. package/src/worker-lease.ts +85 -0
  480. package/src/worker-spend.ts +23 -0
  481. package/src/worker-usage.ts +48 -0
  482. package/src/worker.ts +34 -10
  483. package/src/wrapper-session.ts +50 -15
  484. package/src/yaml-helper-effect.ts +78 -0
  485. package/src/yaml-helpers.ts +131 -0
@@ -0,0 +1,165 @@
1
+ import { constants } from 'node:fs';
2
+ import { copyFile, mkdtemp, open, rm, stat } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { createRequire } from 'node:module';
5
+ import { join, resolve } from 'node:path';
6
+ import { journalRecordOffset } from './journal-offset.js';
7
+
8
+ // Journal version 1's closed vocabulary (relayflowd-core/src/entry.rs).
9
+ const ENTRY_TYPES = new Set([
10
+ 'run.spawned', 'run.cancel.requested', 'event.received', 'subscription.registered',
11
+ 'subscription.matched', 'subscription.stale', 'step.routed', 'step.attempt.started',
12
+ 'step.completed', 'wait.event', 'wait.human', 'sleep.until', 'wait.completed',
13
+ 'stream.appended', 'memory.injected', 'channel.appended', 'channel.delivered',
14
+ 'channel.acknowledged', 'effect.recorded', 'effect.confirmed', 'epoch.summary',
15
+ 'segment.closed', 'run.completed',
16
+ ]);
17
+
18
+ /** The persisted envelope from relayflowd-core/src/entry.rs. */
19
+ export interface JournalEvent {
20
+ seq: number;
21
+ segment_id: number;
22
+ entry_type: string;
23
+ run_id: string;
24
+ step_id: string | null;
25
+ attempt: number | null;
26
+ at_ms: number;
27
+ payload: unknown;
28
+ }
29
+
30
+ export type JournalReadFailure =
31
+ | 'invalid_run_id' | 'run_not_found' | 'step_not_found' | 'journal_read_failed';
32
+
33
+ export class JournalReadError extends Error {
34
+ constructor(readonly code: JournalReadFailure, message: string) {
35
+ super(message);
36
+ this.name = 'JournalReadError';
37
+ }
38
+ }
39
+
40
+ async function fingerprint(path: string): Promise<string | undefined> {
41
+ try {
42
+ const info = await stat(path, { bigint: true });
43
+ if (!info.isFile()) throw new Error(`Journal path is not a regular file: ${path}`);
44
+ return [info.dev, info.ino, info.size, info.mtimeNs, info.ctimeNs].join(':');
45
+ } catch (error) {
46
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Walk a stable on-disk journal in sequence order, across every segment.
53
+ * `at` includes the step's terminal completion, or its last journaled event
54
+ * if it has not terminated. Retries are included.
55
+ *
56
+ * SQLite read-only connections can still create/change WAL shared-memory files.
57
+ * Copy the database and its WAL into private scratch space before opening it,
58
+ * and reject concurrent source changes instead of returning a torn snapshot.
59
+ * Only scratch files are written; the run, registry and daemon are untouched.
60
+ */
61
+ export async function* walkJournal(
62
+ runId: string,
63
+ dataDir: string,
64
+ options: { at?: string } = {},
65
+ ): AsyncIterable<JournalEvent> {
66
+ // Run ids are path components, never paths. Permit imported run names as
67
+ // well as the ULIDs produced by the kernel.
68
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(runId)) {
69
+ throw new JournalReadError('invalid_run_id', 'Run id must contain only letters, digits, underscores or hyphens, starting with a letter or digit.');
70
+ }
71
+ const source = join(resolve(dataDir), 'runs', `${runId}.sqlite3`);
72
+ let scratch: string | undefined;
73
+ let database: import('node:sqlite').DatabaseSync | undefined;
74
+ try {
75
+ const before = await Promise.all([fingerprint(source), fingerprint(`${source}-wal`)]);
76
+ if (before[0] === undefined) {
77
+ throw new JournalReadError('run_not_found', `Run "${runId}" does not exist in "${dataDir}".`);
78
+ }
79
+ scratch = await mkdtemp(join(tmpdir(), 'flows-replay-'));
80
+ const snapshot = join(scratch, 'journal.sqlite3');
81
+ await copyFile(source, snapshot, constants.COPYFILE_EXCL);
82
+ if (before[1] !== undefined) await copyFile(`${source}-wal`, `${snapshot}-wal`, constants.COPYFILE_EXCL);
83
+ const after = await Promise.all([fingerprint(source), fingerprint(`${source}-wal`)]);
84
+ if (before.some((value, index) => value !== after[index])) {
85
+ throw new Error('Journal changed while taking the replay snapshot; retry replay.');
86
+ }
87
+ const file = await open(snapshot, 'r');
88
+ try {
89
+ const header = Buffer.alloc(16);
90
+ const { bytesRead } = await file.read(header, 0, 16, 0);
91
+ const expected = Buffer.from('SQLite format 3\0');
92
+ for (let offset = 0; offset < expected.length; offset += 1) {
93
+ if (offset >= bytesRead || header[offset] !== expected[offset]) {
94
+ throw new Error(`Invalid SQLite header at journal byte offset ${offset}.`);
95
+ }
96
+ }
97
+ } finally { await file.close(); }
98
+
99
+ // Lazy loading keeps all existing CLI verbs independent of node:sqlite.
100
+ const { DatabaseSync } = createRequire(import.meta.url)('node:sqlite') as typeof import('node:sqlite');
101
+ database = new DatabaseSync(snapshot, { readOnly: true });
102
+ const metadata = database.prepare("SELECT value FROM meta WHERE key = 'run_id'").get();
103
+ if (metadata?.['value'] !== runId) throw new Error('Journal run id does not match the requested run.');
104
+ const version = database.prepare("SELECT value FROM meta WHERE key = 'journal_version'").get();
105
+ if (version?.['value'] !== '1') throw new Error('Unsupported journal version.');
106
+ const integrity = database.prepare('PRAGMA quick_check').all();
107
+ if (integrity.length !== 1 || integrity[0]?.['quick_check'] !== 'ok') throw new Error('Journal integrity check failed.');
108
+
109
+ let through: number | undefined;
110
+ if (options.at !== undefined) {
111
+ const row = database.prepare(`
112
+ SELECT COALESCE(MAX(CASE WHEN entry_type = 'step.completed'
113
+ AND json_valid(payload) AND json_extract(payload, '$.disposition') = 'step_done'
114
+ THEN seq END), MAX(seq)) AS seq FROM entries WHERE step_id = ?
115
+ `).get(options.at);
116
+ if (row?.['seq'] === null || row === undefined) {
117
+ throw new JournalReadError('step_not_found', `Step "${options.at}" is not journaled in run "${runId}".`);
118
+ }
119
+ through = integer(row['seq'], 'seq');
120
+ }
121
+ const rows = database.prepare(
122
+ 'SELECT seq, segment_id, entry_type, step_id, attempt, at_ms, payload FROM entries'
123
+ + (through === undefined ? '' : ' WHERE seq <= ?') + ' ORDER BY seq',
124
+ ).iterate(...(through === undefined ? [] : [through]));
125
+ for (const row of rows) {
126
+ try {
127
+ if (typeof row['entry_type'] !== 'string' || !ENTRY_TYPES.has(row['entry_type'])
128
+ || typeof row['payload'] !== 'string'
129
+ || (row['step_id'] !== null && typeof row['step_id'] !== 'string')) {
130
+ throw new Error('Invalid journal entry or unknown record type.');
131
+ }
132
+ yield {
133
+ seq: integer(row['seq'], 'seq'),
134
+ segment_id: integer(row['segment_id'], 'segment_id'),
135
+ entry_type: row['entry_type'],
136
+ run_id: runId,
137
+ step_id: row['step_id'],
138
+ attempt: row['attempt'] === null ? null : integer(row['attempt'], 'attempt'),
139
+ at_ms: integer(row['at_ms'], 'at_ms'),
140
+ payload: JSON.parse(row['payload']),
141
+ };
142
+ } catch (error) {
143
+ const root = database.prepare("SELECT rootpage FROM sqlite_schema WHERE name = 'entries'").get();
144
+ const location = journalRecordOffset(snapshot, integer(root?.['rootpage'], 'rootpage'), integer(row['seq'], 'seq'));
145
+ throw new Error(`Entry seq ${row['seq']} at ${location}: ${error instanceof Error ? error.message : String(error)}`);
146
+ }
147
+ }
148
+ } catch (error) {
149
+ if (error instanceof JournalReadError) throw error;
150
+ throw new JournalReadError('journal_read_failed', `Cannot read journal for run "${runId}": ${error instanceof Error ? error.message : String(error)}`);
151
+ } finally {
152
+ try {
153
+ database?.close();
154
+ } finally {
155
+ if (scratch !== undefined) await rm(scratch, { recursive: true, force: true });
156
+ }
157
+ }
158
+ }
159
+
160
+ function integer(value: unknown, field: string): number {
161
+ if (typeof value !== 'number' || !Number.isSafeInteger(value)) {
162
+ throw new Error(`Invalid journal ${field}: expected a safe integer.`);
163
+ }
164
+ return value;
165
+ }
@@ -22,24 +22,7 @@ export function jsonSchemaError(schema: boolean | Record<string, unknown>): stri
22
22
  const unbounded = jsonSchemaBoundError(schema);
23
23
  if (unbounded !== undefined) return unbounded;
24
24
  try {
25
- const dialect = typeof schema === 'object' && typeof schema['$schema'] === 'string'
26
- ? schema['$schema'].replace(/#$/, '')
27
- : DRAFT_2020_12;
28
- const options = { strict: false, allErrors: true } as const;
29
- if (dialect === DRAFT_4) {
30
- new AjvDraft4(options).compile(schema);
31
- } else if (dialect === DRAFT_6) {
32
- const validator = new Ajv(options);
33
- validator.addMetaSchema(draft6MetaSchema);
34
- validator.compile(schema);
35
- } else if (dialect === DRAFT_7) {
36
- new Ajv(options).compile(schema);
37
- } else if (dialect === DRAFT_2019_09) {
38
- new Ajv2019(options).compile(schema);
39
- } else {
40
- // Ajv reports unknown dialect identifiers instead of guessing.
41
- new Ajv2020(options).compile(schema);
42
- }
25
+ compileSchema(schema);
43
26
  return undefined;
44
27
  } catch (error) {
45
28
  // A RangeError here is Ajv exhausting its own JS stack while COMPILING,
@@ -63,3 +46,36 @@ export function snapshotJsonSchema(schema: unknown, at: string): boolean | Recor
63
46
  if (snapshot !== null && !Array.isArray(snapshot) && typeof snapshot === 'object') return snapshot;
64
47
  throw new Error(`${at}: expected a JSON Schema object or boolean`);
65
48
  }
49
+
50
+ function compileSchema(schema: boolean | Record<string, unknown>) {
51
+ const dialect = typeof schema === 'object' && typeof schema['$schema'] === 'string'
52
+ ? schema['$schema'].replace(/#$/, '')
53
+ : DRAFT_2020_12;
54
+ const options = { strict: false, allErrors: true } as const;
55
+ if (dialect === DRAFT_4) {
56
+ return new AjvDraft4(options).compile(schema);
57
+ } else if (dialect === DRAFT_6) {
58
+ const validator = new Ajv(options);
59
+ validator.addMetaSchema(draft6MetaSchema);
60
+ return validator.compile(schema);
61
+ } else if (dialect === DRAFT_7) {
62
+ return new Ajv(options).compile(schema);
63
+ } else if (dialect === DRAFT_2019_09) {
64
+ return new Ajv2019(options).compile(schema);
65
+ } else {
66
+ // Ajv reports unknown dialect identifiers instead of guessing.
67
+ return new Ajv2020(options).compile(schema);
68
+ }
69
+ }
70
+
71
+ /** A worker verdict; the kernel independently verifies the same schema. */
72
+ export function jsonSchemaOutputError(schema: boolean | Record<string, unknown>, output: unknown): string | undefined {
73
+ const invalid = jsonSchemaError(schema);
74
+ if (invalid !== undefined) return invalid;
75
+ try {
76
+ const validate = compileSchema(schema);
77
+ return validate(output) ? undefined : JSON.stringify(validate.errors);
78
+ } catch (error) {
79
+ return error instanceof Error ? error.message : String(error);
80
+ }
81
+ }
@@ -0,0 +1,82 @@
1
+ import { workerSpend } from './worker-spend.js';
2
+ import type { WorkerCliResult } from './worker-cli.js';
3
+ import { EventEmitter } from 'node:events';
4
+ import type { JournalClient } from './journal-client.js';
5
+ import type { CompletionReason, StepDispatchEvent } from './protocol.js';
6
+ import type { KernelLlmStep } from './spec.js';
7
+ import { runAgentCli } from './worker-cli.js';
8
+ import { withWorkerLease } from './worker-lease.js';
9
+ import { workerInstruction } from './worker-input.js';
10
+ import { jsonSchemaOutputError } from './json-schema.js';
11
+
12
+ /** Bare value-producing LLM dispatch, without agent workspace/recovery pins. */
13
+ export class LlmWorker extends EventEmitter {
14
+ private attached = false;
15
+ private closing = false;
16
+ private readonly inFlight = new Set<Promise<void>>();
17
+
18
+ constructor(private readonly client: JournalClient, private readonly workerId: string) {
19
+ super();
20
+ }
21
+
22
+ async attach(): Promise<void> {
23
+ if (this.attached || this.closing) throw new Error('llm worker: cannot attach twice or after close');
24
+ this.client.on('step.dispatch', this.onDispatch);
25
+ try {
26
+ await this.client.workerAttach(this.workerId, ['llm'], { workspace: [], streams: [] }, 1);
27
+ this.attached = true;
28
+ } catch (error) {
29
+ this.client.off('step.dispatch', this.onDispatch);
30
+ throw error;
31
+ }
32
+ }
33
+
34
+ async close(): Promise<void> {
35
+ this.closing = true;
36
+ this.client.off('step.dispatch', this.onDispatch);
37
+ await Promise.allSettled([...this.inFlight]);
38
+ }
39
+
40
+ private readonly onDispatch = (dispatch: StepDispatchEvent): void => {
41
+ if (this.closing || dispatch.step_type !== 'llm') return;
42
+ const running = this.execute(dispatch).catch(error => { this.emit('error', error); });
43
+ this.inFlight.add(running);
44
+ void running.finally(() => { this.inFlight.delete(running); });
45
+ };
46
+
47
+ private async execute(dispatch: StepDispatchEvent): Promise<void> {
48
+ const spec = dispatch.spec as KernelLlmStep;
49
+ const schema = spec.verification?.json_schema;
50
+ const prompt = schema === undefined ? spec.prompt
51
+ : `${spec.prompt}\n\nReturn only a JSON value matching this JSON Schema (no Markdown fences):\n${JSON.stringify(schema)}`;
52
+ const completed: WorkerCliResult = await withWorkerLease(this.client, dispatch, signal =>
53
+ typeof spec.cli === 'string' && typeof spec.prompt === 'string'
54
+ ? runAgentCli(spec.cli, workerInstruction(prompt, dispatch), dispatch.wake_context, spec.model, undefined, signal, 'llm')
55
+ : Promise.resolve({ exit_code: null, stdout_tail: '', stderr_tail: 'llm step has no declared CLI' }));
56
+ const { result, usage } = workerSpend(completed, spec.model);
57
+ let reason: CompletionReason = result.exit_code === 0 ? 'success' : 'worker_error';
58
+ let output: unknown = result.stdout_tail;
59
+ let detail = result.stderr_tail;
60
+ if (reason === 'success' && schema !== undefined) {
61
+ try {
62
+ output = JSON.parse(result.stdout_tail);
63
+ const invalid = jsonSchemaOutputError(schema, output);
64
+ if (invalid !== undefined) {
65
+ reason = 'verification_failed';
66
+ detail = `LLM output does not match its declared schema: ${invalid}`;
67
+ }
68
+ } catch {
69
+ reason = 'verification_failed';
70
+ detail = 'LLM output is not valid JSON.';
71
+ }
72
+ }
73
+ // Never submit schema-invalid JSON as a successful completion. The kernel
74
+ // also runs its own gate before making output available to dependents.
75
+ await this.client.stepComplete(dispatch.run_id, dispatch.step_id, dispatch.attempt,
76
+ dispatch.idempotency_key, reason, {
77
+ output,
78
+ ...(usage !== undefined ? { usage } : {}),
79
+ ...(reason === 'success' ? {} : { trajectory_tail: { error: detail } }),
80
+ });
81
+ }
82
+ }
@@ -0,0 +1,30 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import type { JournalClient } from './journal-client.js';
3
+ import { AgentWorker } from './worker.js';
4
+
5
+ /** A local worker for stream-only steps; no workspace recovery is claimed. */
6
+ export async function attachLocalAgent(client: JournalClient, dataDir?: string, onPtyReady?: (path: string) => void): Promise<{
7
+ stream: string;
8
+ readonly failure: unknown;
9
+ close(): Promise<void>;
10
+ }> {
11
+ // A fresh, unconsumed stream has offset zero. The executor declares exactly
12
+ // this stream on its agent steps. No worktree revision is invented.
13
+ const stream = `local-agent-${randomUUID()}`;
14
+ const worker = new AgentWorker(client, {
15
+ workerId: stream,
16
+ capacity: 1,
17
+ dataDir, onPtyReady,
18
+ pins: { workspace: [], streams: [{ stream, read_offset: 0 }] },
19
+ });
20
+ let failure: unknown;
21
+ worker.on('error', error => { failure = error; client.close(); });
22
+ await worker.attach();
23
+ return {
24
+ stream,
25
+ get failure() { return failure; },
26
+ async close() {
27
+ await worker.close();
28
+ },
29
+ };
30
+ }
@@ -0,0 +1,97 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
3
+ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
4
+ import { Agent } from 'undici';
5
+ import type { McpServerConfig } from './spec.js';
6
+ import { McpStdioTransport } from './mcp-stdio.js';
7
+
8
+ export type McpDiagnostic = 'spawn_failed' | 'handshake_timeout' | 'handshake_rejected' | 'mcp_disconnected';
9
+ export class McpError extends Error {
10
+ constructor(readonly code: McpDiagnostic) { super(code); }
11
+ }
12
+ export interface McpSession {
13
+ listTools(): Promise<string[]>;
14
+ callTool(name: string, args: unknown): Promise<unknown>;
15
+ close(): Promise<void>;
16
+ }
17
+
18
+ /** One bounded session, never a reconnect/retry loop. Protocol and framing are
19
+ * supplied by the official SDK; this adapter owns resource lifetimes. */
20
+ export async function openMcpSession(config: McpServerConfig, timeoutMs: number): Promise<McpSession> {
21
+ const client = new Client({ name: 'relayflows', version: '1.0.0' });
22
+ const controller = new AbortController();
23
+ let dispatcher: Agent | undefined;
24
+ let transport: Transport;
25
+ if ('command' in config) {
26
+ transport = new McpStdioTransport(config);
27
+ } else {
28
+ dispatcher = new Agent();
29
+ transport = new StreamableHTTPClientTransport(new URL(config.url), {
30
+ requestInit: { headers: config.headers },
31
+ reconnectionOptions: { maxRetries: 0, initialReconnectionDelay: 0, maxReconnectionDelay: 0, reconnectionDelayGrowFactor: 1 },
32
+ fetch: (url, init) => fetch(url, {
33
+ ...init, redirect: 'error',
34
+ signal: AbortSignal.any([controller.signal, ...(init?.signal ? [init.signal] : [])]),
35
+ dispatcher,
36
+ } as RequestInit),
37
+ });
38
+ }
39
+ let closing: Promise<void> | undefined;
40
+ const close = (): Promise<void> => closing ??= (async () => {
41
+ controller.abort();
42
+ try { await client.close(); } finally {
43
+ await transport.close();
44
+ await dispatcher?.destroy();
45
+ }
46
+ })();
47
+ let phase: 'handshake' | 'call' = 'handshake';
48
+ let failure: McpError | undefined;
49
+ let rejectTransport!: (error: McpError) => void;
50
+ const broken = new Promise<never>((_, reject) => { rejectTransport = reject; });
51
+ void broken.catch(() => undefined);
52
+ const fail = (code: McpDiagnostic): void => {
53
+ failure ??= new McpError(code);
54
+ rejectTransport(failure);
55
+ };
56
+ client.onerror = error => {
57
+ const code = (error as NodeJS.ErrnoException).code;
58
+ fail(phase === 'call' ? 'mcp_disconnected'
59
+ : code === 'ENOENT' || code === 'EACCES' ? 'spawn_failed' : 'handshake_rejected');
60
+ };
61
+ client.onclose = () => {
62
+ if (!closing) fail(phase === 'call' ? 'mcp_disconnected' : 'handshake_rejected');
63
+ };
64
+ const bounded = async <T>(work: () => Promise<T>): Promise<T> => {
65
+ let timer: ReturnType<typeof setTimeout> | undefined;
66
+ try {
67
+ if (failure) throw failure;
68
+ return await Promise.race([work(), broken, new Promise<never>((_, reject) => {
69
+ timer = setTimeout(() => reject(new McpError(phase === 'call' ? 'mcp_disconnected' : 'handshake_timeout')), timeoutMs);
70
+ })]);
71
+ } catch (error) {
72
+ await close();
73
+ throw error instanceof McpError ? error : new McpError(phase === 'call' ? 'mcp_disconnected' : 'handshake_rejected');
74
+ } finally { clearTimeout(timer); }
75
+ };
76
+ await bounded(() => client.connect(transport));
77
+ return {
78
+ listTools: () => bounded(async () => {
79
+ const names: string[] = [];
80
+ const cursors = new Set<string>();
81
+ let cursor: string | undefined;
82
+ do {
83
+ const page = await client.listTools(cursor === undefined ? {} : { cursor });
84
+ names.push(...page.tools.map(tool => tool.name));
85
+ cursor = page.nextCursor;
86
+ if (cursor !== undefined && cursors.has(cursor)) throw new McpError('handshake_rejected');
87
+ if (cursor !== undefined) cursors.add(cursor);
88
+ } while (cursor !== undefined);
89
+ return [...new Set(names)];
90
+ }),
91
+ callTool: (name, args) => {
92
+ phase = 'call';
93
+ return bounded(() => client.callTool({ name, arguments: args as Record<string, unknown> }));
94
+ },
95
+ close,
96
+ };
97
+ }
@@ -0,0 +1,42 @@
1
+ import type { McpServerConfig } from './spec.js';
2
+
3
+ /** Called on parsed project JSON before any process or connection is opened. */
4
+ export function parseMcpConfig(value: unknown): Record<string, McpServerConfig> {
5
+ if (!record(value)) throw new Error('mcp must be a server map');
6
+ const result: Record<string, McpServerConfig> = Object.create(null);
7
+ for (const [name, config] of Object.entries(value)) {
8
+ const invalid = (): never => { throw new Error(`mcp.${name} has an invalid server configuration`); };
9
+ if (!name.trim() || !record(config)) invalid();
10
+ const entry = config as Record<string, unknown>;
11
+ const stdio = Object.hasOwn(entry, 'command');
12
+ if (stdio === Object.hasOwn(entry, 'url')) invalid();
13
+ const allowed = stdio ? ['command', 'args', 'env'] : ['url', 'headers'];
14
+ if (Object.keys(entry).some(key => !allowed.includes(key))) invalid();
15
+ if (stdio) {
16
+ if (!nonempty(entry.command)) invalid();
17
+ if (entry.args !== undefined && (!Array.isArray(entry.args)
18
+ || !entry.args.every(arg => typeof arg === 'string' && !arg.includes('\0')))) invalid();
19
+ if (entry.env !== undefined && (!Array.isArray(entry.env)
20
+ || !entry.env.every(key => typeof key === 'string' && /^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
21
+ || new Set(entry.env).size !== entry.env.length)) invalid();
22
+ } else {
23
+ if (!nonempty(entry.url)) invalid();
24
+ let url: URL;
25
+ try { url = new URL(entry.url as string); } catch { invalid(); }
26
+ if (!['http:', 'https:'].includes(url!.protocol) || url!.username || url!.password || url!.hash) invalid();
27
+ if (entry.headers !== undefined) {
28
+ if (!record(entry.headers) || !Object.values(entry.headers).every(v => typeof v === 'string')) invalid();
29
+ try { new Headers(entry.headers as Record<string, string>); } catch { invalid(); }
30
+ }
31
+ }
32
+ result[name] = entry as McpServerConfig;
33
+ }
34
+ return result;
35
+ }
36
+
37
+ function record(value: unknown): value is Record<string, unknown> {
38
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
39
+ }
40
+ function nonempty(value: unknown): value is string {
41
+ return typeof value === 'string' && value.trim().length > 0 && !value.includes('\0');
42
+ }
@@ -0,0 +1,84 @@
1
+ import { childStop, type ChildStop } from './child-stop.js';
2
+ import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
3
+ import { ReadBuffer, serializeMessage } from '@modelcontextprotocol/sdk/shared/stdio.js';
4
+ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
5
+ import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
6
+ import type { McpServerConfig } from './spec.js';
7
+
8
+ /** SDK framing with parent-owned spawning/cleanup: SDK's stdio transport
9
+ * unconditionally inherits HOME/PATH/etc. and cannot enforce our env contract. */
10
+ export class McpStdioTransport implements Transport {
11
+ onclose?: Transport['onclose'];
12
+ onerror?: Transport['onerror'];
13
+ onmessage?: Transport['onmessage'];
14
+ private child?: ChildProcessWithoutNullStreams;
15
+ private stopTree?: ChildStop;
16
+ private readonly buffer = new ReadBuffer({ maxBufferSize: 1_048_576 });
17
+ private closed?: Promise<void>;
18
+ private closing?: Promise<void>;
19
+ constructor(private readonly config: Extract<McpServerConfig, { command: string }>) {}
20
+
21
+ async start(): Promise<void> {
22
+ const env: NodeJS.ProcessEnv = Object.create(null);
23
+ for (const name of this.config.env ?? []) {
24
+ if (process.env[name] !== undefined) env[name] = process.env[name];
25
+ }
26
+ const child = this.child = spawn(this.config.command, this.config.args ?? [], {
27
+ env, stdio: ['pipe', 'pipe', 'pipe'], detached: process.platform !== 'win32',
28
+ });
29
+ this.stopTree = childStop(child, process.platform !== 'win32');
30
+ this.closed = new Promise(resolve => child.once('close', () => resolve()));
31
+ child.stderr.resume();
32
+ child.stdout.on('data', (chunk: Buffer) => {
33
+ if (this.closing) return;
34
+ try {
35
+ this.buffer.append(chunk);
36
+ let message: JSONRPCMessage | null;
37
+ while ((message = this.buffer.readMessage()) !== null) this.onmessage?.(message);
38
+ } catch (error) { this.onerror?.(error as Error); }
39
+ });
40
+ child.stdout.once('end', () => this.onclose?.());
41
+ child.stdout.on('error', error => this.onerror?.(error));
42
+ child.stdin.on('error', error => this.onerror?.(error));
43
+ child.on('error', error => this.onerror?.(error));
44
+ await new Promise<void>((resolve, reject) => {
45
+ child.once('spawn', resolve);
46
+ child.once('error', reject);
47
+ });
48
+ }
49
+
50
+ send(message: JSONRPCMessage): Promise<void> {
51
+ return new Promise((resolve, reject) => {
52
+ if (!this.child || this.closing) return reject(new Error('MCP transport closed'));
53
+ this.child.stdin.write(serializeMessage(message), error => error ? reject(error) : resolve());
54
+ });
55
+ }
56
+
57
+ close(): Promise<void> {
58
+ return this.closing ??= this.stop();
59
+ }
60
+
61
+ private async stop(): Promise<void> {
62
+ const child = this.child;
63
+ if (!child) return;
64
+ child.stdin.end();
65
+ this.stopTree!.terminate();
66
+ // A direct-child close is insufficient: wrappers can leave descendants
67
+ // alive with either inherited pipes or completely detached stdio.
68
+ let timer: ReturnType<typeof setTimeout> | undefined;
69
+ const deadline = new Promise<void>(resolve => { timer = setTimeout(resolve, 1000); });
70
+ await Promise.race([this.closed, deadline]);
71
+ if (!this.stopTree!.maySettleOnChildExit()) await deadline;
72
+ clearTimeout(timer);
73
+ if (child.exitCode === null && child.signalCode === null && child.pid !== undefined) {
74
+ const exited = new Promise<void>(resolve => child.once('exit', () => resolve()));
75
+ this.stopTree!.kill();
76
+ await exited;
77
+ }
78
+ child.stdin.destroy();
79
+ child.stdout.destroy();
80
+ child.stderr.destroy();
81
+ this.buffer.clear();
82
+ this.onclose?.();
83
+ }
84
+ }
@@ -0,0 +1,32 @@
1
+ /** Frozen dollars per million tokens. Each rate is an integer microdollar per token. */
2
+ export const MODEL_PRICING: Readonly<Record<string, Readonly<{ input: number; output: number }>>> = Object.freeze({
3
+ 'claude-sonnet-4-6': Object.freeze({ input: 3, output: 15 }),
4
+ 'claude-opus-4-7': Object.freeze({ input: 15, output: 75 }),
5
+ 'codex-medium': Object.freeze({ input: 2, output: 8 }),
6
+ 'codex-large': Object.freeze({ input: 5, output: 20 }),
7
+ });
8
+
9
+ export function hasPricing(model: string | undefined): boolean {
10
+ return model !== undefined && Object.hasOwn(MODEL_PRICING, model);
11
+ }
12
+
13
+ /**
14
+ * Cost accounting for a step's declared model.
15
+ *
16
+ * Returns `undefined` for unpriced models — callers should omit `usage`
17
+ * from the journal payload rather than sending nulls that break the kernel
18
+ * wire schema. The refusal for a declared dollar budget against an unpriced
19
+ * model is `budgetDiagnostics` at preflight (before any CLI dispatches).
20
+ * Throwing here after usage decode would waste the CLI invocation that
21
+ * preflight was meant to prevent.
22
+ */
23
+ export function pricedUsage(model: string | undefined, input = 0, output = 0):
24
+ | { tokens_in: number; tokens_out: number; dollars: string }
25
+ | undefined
26
+ {
27
+ if (![input, output].every(n => Number.isSafeInteger(n) && n >= 0)) throw new Error('Invalid token usage');
28
+ const price = model === undefined || !Object.hasOwn(MODEL_PRICING, model) ? undefined : MODEL_PRICING[model];
29
+ if (price === undefined) return undefined;
30
+ const micro = BigInt(input) * BigInt(price.input) + BigInt(output) * BigInt(price.output);
31
+ return { tokens_in: input, tokens_out: output, dollars: `${micro / 1_000_000n}.${String(micro % 1_000_000n).padStart(6, '0')}` };
32
+ }
@@ -0,0 +1,44 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { isScopePath, SCOPE_MODES, type MountRegistry } from './scope-compiler.js';
4
+
5
+ /**
6
+ * Local relayfile shim: nearest relayfile.mounts.json contains
7
+ * {version: 1, mounts: {acme: [{path: "api", modes: ["readonly"]}]}}.
8
+ * It describes existing mount capabilities, not additional flow permissions.
9
+ * No manifest means no known mounts. An unreadable/invalid manifest fails closed.
10
+ */
11
+ export function readMountRegistry(start: string): MountRegistry {
12
+ let directory = start;
13
+ for (;;) {
14
+ const path = join(directory, 'relayfile.mounts.json');
15
+ let source: string;
16
+ try {
17
+ source = readFileSync(path, 'utf8');
18
+ } catch (error) {
19
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
20
+ const parent = dirname(directory);
21
+ if (parent === directory) return {};
22
+ directory = parent;
23
+ continue;
24
+ }
25
+ const value: unknown = JSON.parse(source);
26
+ if (!isObject(value) || value.version !== 1
27
+ || Object.keys(value).some(key => !['version', 'mounts'].includes(key))
28
+ || !isObject(value.mounts)) throw new Error(`Invalid relayfile mount manifest "${path}".`);
29
+ for (const [name, prefixes] of Object.entries(value.mounts)) {
30
+ if (!/^[A-Za-z0-9_-]+$/.test(name) || !Array.isArray(prefixes)
31
+ || !prefixes.every(prefix => isObject(prefix)
32
+ && Object.keys(prefix).every(key => ['path', 'modes'].includes(key))
33
+ && isScopePath(prefix.path) && Array.isArray(prefix.modes)
34
+ && prefix.modes.every(mode => SCOPE_MODES.includes(mode)))) {
35
+ throw new Error(`Invalid relayfile mount "${name}" in "${path}".`);
36
+ }
37
+ }
38
+ return value.mounts as MountRegistry;
39
+ }
40
+ }
41
+
42
+ function isObject(value: unknown): value is Record<string, unknown> {
43
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
44
+ }