@relayflows/sdk 2.0.9 → 2.0.10

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 (475) 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-transport.d.ts +84 -0
  22. package/dist/agent-relay-transport.d.ts.map +1 -0
  23. package/dist/agent-relay-transport.js +134 -0
  24. package/dist/agent-relay-transport.js.map +1 -0
  25. package/dist/authored-budget.d.ts +13 -0
  26. package/dist/authored-budget.d.ts.map +1 -0
  27. package/dist/authored-budget.js +78 -0
  28. package/dist/authored-budget.js.map +1 -0
  29. package/dist/authored-flow-error.d.ts +2 -21
  30. package/dist/authored-flow-error.d.ts.map +1 -1
  31. package/dist/authored-flow-error.js +1 -12
  32. package/dist/authored-flow-error.js.map +1 -1
  33. package/dist/authored-flow-executor.d.ts +13 -7
  34. package/dist/authored-flow-executor.d.ts.map +1 -1
  35. package/dist/authored-flow-executor.js +116 -235
  36. package/dist/authored-flow-executor.js.map +1 -1
  37. package/dist/authored-flow-loader.d.ts +10 -1
  38. package/dist/authored-flow-loader.d.ts.map +1 -1
  39. package/dist/authored-flow-loader.js +56 -3
  40. package/dist/authored-flow-loader.js.map +1 -1
  41. package/dist/authored-flow-operation.d.ts +9 -1
  42. package/dist/authored-flow-operation.d.ts.map +1 -1
  43. package/dist/authored-flow-operation.js +35 -4
  44. package/dist/authored-flow-operation.js.map +1 -1
  45. package/dist/authored-helper-effect.d.ts +12 -0
  46. package/dist/authored-helper-effect.d.ts.map +1 -0
  47. package/dist/authored-helper-effect.js +144 -0
  48. package/dist/authored-helper-effect.js.map +1 -0
  49. package/dist/authored-mcp.d.ts +17 -0
  50. package/dist/authored-mcp.d.ts.map +1 -0
  51. package/dist/authored-mcp.js +171 -0
  52. package/dist/authored-mcp.js.map +1 -0
  53. package/dist/authored-memory.d.ts +7 -0
  54. package/dist/authored-memory.d.ts.map +1 -0
  55. package/dist/authored-memory.js +60 -0
  56. package/dist/authored-memory.js.map +1 -0
  57. package/dist/authored-plugin-effect.d.ts +16 -0
  58. package/dist/authored-plugin-effect.d.ts.map +1 -0
  59. package/dist/authored-plugin-effect.js +163 -0
  60. package/dist/authored-plugin-effect.js.map +1 -0
  61. package/dist/authored-step-output.d.ts +23 -0
  62. package/dist/authored-step-output.d.ts.map +1 -0
  63. package/dist/authored-step-output.js +67 -0
  64. package/dist/authored-step-output.js.map +1 -0
  65. package/dist/authored-worker-step.d.ts +15 -0
  66. package/dist/authored-worker-step.d.ts.map +1 -0
  67. package/dist/authored-worker-step.js +136 -0
  68. package/dist/authored-worker-step.js.map +1 -0
  69. package/dist/budget-preflight.d.ts +5 -0
  70. package/dist/budget-preflight.d.ts.map +1 -0
  71. package/dist/budget-preflight.js +33 -0
  72. package/dist/budget-preflight.js.map +1 -0
  73. package/dist/budget.d.ts +14 -0
  74. package/dist/budget.d.ts.map +1 -0
  75. package/dist/budget.js +52 -0
  76. package/dist/budget.js.map +1 -0
  77. package/dist/bundle-transport.d.ts +18 -0
  78. package/dist/bundle-transport.d.ts.map +1 -0
  79. package/dist/bundle-transport.js +111 -0
  80. package/dist/bundle-transport.js.map +1 -0
  81. package/dist/bundle-typescript.d.ts +8 -0
  82. package/dist/bundle-typescript.d.ts.map +1 -0
  83. package/dist/bundle-typescript.js +108 -0
  84. package/dist/bundle-typescript.js.map +1 -0
  85. package/dist/bundle.d.ts +25 -0
  86. package/dist/bundle.d.ts.map +1 -0
  87. package/dist/bundle.js +199 -0
  88. package/dist/bundle.js.map +1 -0
  89. package/dist/child-stop.d.ts +59 -0
  90. package/dist/child-stop.d.ts.map +1 -0
  91. package/dist/child-stop.js +94 -0
  92. package/dist/child-stop.js.map +1 -0
  93. package/dist/cli/add.d.ts +6 -0
  94. package/dist/cli/add.d.ts.map +1 -0
  95. package/dist/cli/add.js +47 -0
  96. package/dist/cli/add.js.map +1 -0
  97. package/dist/cli/build.d.ts +13 -0
  98. package/dist/cli/build.d.ts.map +1 -0
  99. package/dist/cli/build.js +224 -0
  100. package/dist/cli/build.js.map +1 -0
  101. package/dist/cli/bundle-preflight.d.ts +5 -0
  102. package/dist/cli/bundle-preflight.d.ts.map +1 -0
  103. package/dist/cli/bundle-preflight.js +28 -0
  104. package/dist/cli/bundle-preflight.js.map +1 -0
  105. package/dist/cli/check-helper-body.d.ts +4 -0
  106. package/dist/cli/check-helper-body.d.ts.map +1 -0
  107. package/dist/cli/check-helper-body.js +15 -0
  108. package/dist/cli/check-helper-body.js.map +1 -0
  109. package/dist/cli/check-triggers.d.ts +14 -0
  110. package/dist/cli/check-triggers.d.ts.map +1 -0
  111. package/dist/cli/check-triggers.js +37 -0
  112. package/dist/cli/check-triggers.js.map +1 -0
  113. package/dist/cli/check-typescript.d.ts +20 -0
  114. package/dist/cli/check-typescript.d.ts.map +1 -0
  115. package/dist/cli/check-typescript.js +54 -0
  116. package/dist/cli/check-typescript.js.map +1 -0
  117. package/dist/cli/check.d.ts +36 -3
  118. package/dist/cli/check.d.ts.map +1 -1
  119. package/dist/cli/check.js +122 -11
  120. package/dist/cli/check.js.map +1 -1
  121. package/dist/cli/cloud-run.d.ts +8 -0
  122. package/dist/cli/cloud-run.d.ts.map +1 -0
  123. package/dist/cli/cloud-run.js +50 -0
  124. package/dist/cli/cloud-run.js.map +1 -0
  125. package/dist/cli/deploy.d.ts +9 -0
  126. package/dist/cli/deploy.d.ts.map +1 -0
  127. package/dist/cli/deploy.js +58 -0
  128. package/dist/cli/deploy.js.map +1 -0
  129. package/dist/cli/deterministic-failure.d.ts +5 -0
  130. package/dist/cli/deterministic-failure.d.ts.map +1 -0
  131. package/dist/cli/deterministic-failure.js +66 -0
  132. package/dist/cli/deterministic-failure.js.map +1 -0
  133. package/dist/cli/direct-run.d.ts.map +1 -1
  134. package/dist/cli/direct-run.js +83 -18
  135. package/dist/cli/direct-run.js.map +1 -1
  136. package/dist/cli/hn-monitor.d.ts.map +1 -1
  137. package/dist/cli/hn-monitor.js +2 -1
  138. package/dist/cli/hn-monitor.js.map +1 -1
  139. package/dist/cli/replay.d.ts +12 -0
  140. package/dist/cli/replay.d.ts.map +1 -0
  141. package/dist/cli/replay.js +82 -0
  142. package/dist/cli/replay.js.map +1 -0
  143. package/dist/cli/reuse.d.ts +8 -0
  144. package/dist/cli/reuse.d.ts.map +1 -0
  145. package/dist/cli/reuse.js +25 -0
  146. package/dist/cli/reuse.js.map +1 -0
  147. package/dist/cli/run-digest.d.ts +4 -0
  148. package/dist/cli/run-digest.d.ts.map +1 -0
  149. package/dist/cli/run-digest.js +24 -0
  150. package/dist/cli/run-digest.js.map +1 -0
  151. package/dist/cli/run.d.ts +16 -5
  152. package/dist/cli/run.d.ts.map +1 -1
  153. package/dist/cli/run.js +99 -17
  154. package/dist/cli/run.js.map +1 -1
  155. package/dist/cli/serve-webhook.d.ts +40 -0
  156. package/dist/cli/serve-webhook.d.ts.map +1 -0
  157. package/dist/cli/serve-webhook.js +233 -0
  158. package/dist/cli/serve-webhook.js.map +1 -0
  159. package/dist/cli/tick-runner.d.ts.map +1 -1
  160. package/dist/cli/tick-runner.js +2 -1
  161. package/dist/cli/tick-runner.js.map +1 -1
  162. package/dist/cli-adapter.d.ts +8 -14
  163. package/dist/cli-adapter.d.ts.map +1 -1
  164. package/dist/cli-adapter.js +19 -64
  165. package/dist/cli-adapter.js.map +1 -1
  166. package/dist/cli-watch.d.ts +13 -0
  167. package/dist/cli-watch.d.ts.map +1 -0
  168. package/dist/cli-watch.js +185 -0
  169. package/dist/cli-watch.js.map +1 -0
  170. package/dist/cli.d.ts +4 -0
  171. package/dist/cli.d.ts.map +1 -1
  172. package/dist/cli.js +387 -14
  173. package/dist/cli.js.map +1 -1
  174. package/dist/cloud-http.d.ts +22 -0
  175. package/dist/cloud-http.d.ts.map +1 -0
  176. package/dist/cloud-http.js +84 -0
  177. package/dist/cloud-http.js.map +1 -0
  178. package/dist/cloud-run.d.ts +38 -0
  179. package/dist/cloud-run.d.ts.map +1 -0
  180. package/dist/cloud-run.js +112 -0
  181. package/dist/cloud-run.js.map +1 -0
  182. package/dist/compile.d.ts +21 -3
  183. package/dist/compile.d.ts.map +1 -1
  184. package/dist/compile.js +97 -23
  185. package/dist/compile.js.map +1 -1
  186. package/dist/create-flow.d.ts +17 -0
  187. package/dist/create-flow.d.ts.map +1 -0
  188. package/dist/create-flow.js +72 -0
  189. package/dist/create-flow.js.map +1 -0
  190. package/dist/daemon-connection.d.ts +14 -0
  191. package/dist/daemon-connection.d.ts.map +1 -1
  192. package/dist/daemon-connection.js +33 -1
  193. package/dist/daemon-connection.js.map +1 -1
  194. package/dist/demo-hn-monitor.js +2 -1
  195. package/dist/demo-hn-monitor.js.map +1 -1
  196. package/dist/effect-channel.d.ts +30 -0
  197. package/dist/effect-channel.d.ts.map +1 -0
  198. package/dist/effect-channel.js +71 -0
  199. package/dist/effect-channel.js.map +1 -0
  200. package/dist/failure-kinds.d.ts +14 -3
  201. package/dist/failure-kinds.d.ts.map +1 -1
  202. package/dist/failure-kinds.js +27 -0
  203. package/dist/failure-kinds.js.map +1 -1
  204. package/dist/gate-contract.d.ts.map +1 -1
  205. package/dist/gate-contract.js +9 -0
  206. package/dist/gate-contract.js.map +1 -1
  207. package/dist/helper-preflight.d.ts +18 -0
  208. package/dist/helper-preflight.d.ts.map +1 -0
  209. package/dist/helper-preflight.js +32 -0
  210. package/dist/helper-preflight.js.map +1 -0
  211. package/dist/helper-storage.d.ts +5 -0
  212. package/dist/helper-storage.d.ts.map +1 -0
  213. package/dist/helper-storage.js +31 -0
  214. package/dist/helper-storage.js.map +1 -0
  215. package/dist/helper-writeback.d.ts +12 -0
  216. package/dist/helper-writeback.d.ts.map +1 -0
  217. package/dist/helper-writeback.js +95 -0
  218. package/dist/helper-writeback.js.map +1 -0
  219. package/dist/index.d.ts +10 -2
  220. package/dist/index.d.ts.map +1 -1
  221. package/dist/index.js +7 -1
  222. package/dist/index.js.map +1 -1
  223. package/dist/input-binding.d.ts +4 -0
  224. package/dist/input-binding.d.ts.map +1 -0
  225. package/dist/input-binding.js +78 -0
  226. package/dist/input-binding.js.map +1 -0
  227. package/dist/journal-client.d.ts +12 -4
  228. package/dist/journal-client.d.ts.map +1 -1
  229. package/dist/journal-client.js +17 -6
  230. package/dist/journal-client.js.map +1 -1
  231. package/dist/journal-offset.d.ts +3 -0
  232. package/dist/journal-offset.d.ts.map +1 -0
  233. package/dist/journal-offset.js +69 -0
  234. package/dist/journal-offset.js.map +1 -0
  235. package/dist/journal-reader.d.ts +30 -0
  236. package/dist/journal-reader.d.ts.map +1 -0
  237. package/dist/journal-reader.js +156 -0
  238. package/dist/journal-reader.js.map +1 -0
  239. package/dist/json-schema.d.ts +2 -0
  240. package/dist/json-schema.d.ts.map +1 -1
  241. package/dist/json-schema.js +38 -22
  242. package/dist/json-schema.js.map +1 -1
  243. package/dist/llm-worker.d.ts +16 -0
  244. package/dist/llm-worker.d.ts.map +1 -0
  245. package/dist/llm-worker.js +79 -0
  246. package/dist/llm-worker.js.map +1 -0
  247. package/dist/local-agent.d.ts +8 -0
  248. package/dist/local-agent.d.ts.map +1 -0
  249. package/dist/local-agent.js +25 -0
  250. package/dist/local-agent.js.map +1 -0
  251. package/dist/mcp-client.d.ts +15 -0
  252. package/dist/mcp-client.d.ts.map +1 -0
  253. package/dist/mcp-client.js +104 -0
  254. package/dist/mcp-client.js.map +1 -0
  255. package/dist/mcp-config.d.ts +4 -0
  256. package/dist/mcp-config.d.ts.map +1 -0
  257. package/dist/mcp-config.js +61 -0
  258. package/dist/mcp-config.js.map +1 -0
  259. package/dist/mcp-stdio.d.ts +24 -0
  260. package/dist/mcp-stdio.d.ts.map +1 -0
  261. package/dist/mcp-stdio.js +89 -0
  262. package/dist/mcp-stdio.js.map +1 -0
  263. package/dist/model-pricing.d.ts +22 -0
  264. package/dist/model-pricing.d.ts.map +1 -0
  265. package/dist/model-pricing.js +30 -0
  266. package/dist/model-pricing.js.map +1 -0
  267. package/dist/mount-registry.d.ts +9 -0
  268. package/dist/mount-registry.d.ts.map +1 -0
  269. package/dist/mount-registry.js +47 -0
  270. package/dist/mount-registry.js.map +1 -0
  271. package/dist/named-gate-lowering.d.ts +9 -0
  272. package/dist/named-gate-lowering.d.ts.map +1 -0
  273. package/dist/named-gate-lowering.js +152 -0
  274. package/dist/named-gate-lowering.js.map +1 -0
  275. package/dist/named-gates.d.ts +10 -0
  276. package/dist/named-gates.d.ts.map +1 -0
  277. package/dist/named-gates.js +69 -0
  278. package/dist/named-gates.js.map +1 -0
  279. package/dist/observer-link.d.ts +129 -0
  280. package/dist/observer-link.d.ts.map +1 -0
  281. package/dist/observer-link.js +274 -0
  282. package/dist/observer-link.js.map +1 -0
  283. package/dist/plugin-loader.d.ts +13 -0
  284. package/dist/plugin-loader.d.ts.map +1 -0
  285. package/dist/plugin-loader.js +110 -0
  286. package/dist/plugin-loader.js.map +1 -0
  287. package/dist/plugin-manifest.d.ts +27 -0
  288. package/dist/plugin-manifest.d.ts.map +1 -0
  289. package/dist/plugin-manifest.js +89 -0
  290. package/dist/plugin-manifest.js.map +1 -0
  291. package/dist/preflight.d.ts +26 -2
  292. package/dist/preflight.d.ts.map +1 -1
  293. package/dist/preflight.js +175 -1
  294. package/dist/preflight.js.map +1 -1
  295. package/dist/progress.d.ts +14 -0
  296. package/dist/progress.d.ts.map +1 -0
  297. package/dist/progress.js +39 -0
  298. package/dist/progress.js.map +1 -0
  299. package/dist/protocol.d.ts +11 -0
  300. package/dist/protocol.d.ts.map +1 -1
  301. package/dist/pty-sidechannel.d.ts +14 -0
  302. package/dist/pty-sidechannel.d.ts.map +1 -0
  303. package/dist/pty-sidechannel.js +103 -0
  304. package/dist/pty-sidechannel.js.map +1 -0
  305. package/dist/scope-compiler.d.ts +52 -0
  306. package/dist/scope-compiler.d.ts.map +1 -0
  307. package/dist/scope-compiler.js +62 -0
  308. package/dist/scope-compiler.js.map +1 -0
  309. package/dist/slack-preflight.d.ts +6 -0
  310. package/dist/slack-preflight.d.ts.map +1 -0
  311. package/dist/slack-preflight.js +28 -0
  312. package/dist/slack-preflight.js.map +1 -0
  313. package/dist/slack-writeback.d.ts +40 -0
  314. package/dist/slack-writeback.d.ts.map +1 -0
  315. package/dist/slack-writeback.js +60 -0
  316. package/dist/slack-writeback.js.map +1 -0
  317. package/dist/spec.d.ts +134 -4
  318. package/dist/spec.d.ts.map +1 -1
  319. package/dist/spec.js.map +1 -1
  320. package/dist/step-dependencies.d.ts.map +1 -1
  321. package/dist/step-dependencies.js +2 -1
  322. package/dist/step-dependencies.js.map +1 -1
  323. package/dist/step-fields.d.ts +4 -4
  324. package/dist/step-fields.d.ts.map +1 -1
  325. package/dist/step-fields.js +5 -2
  326. package/dist/step-fields.js.map +1 -1
  327. package/dist/trigger-executor.d.ts +7 -0
  328. package/dist/trigger-executor.d.ts.map +1 -0
  329. package/dist/trigger-executor.js +35 -0
  330. package/dist/trigger-executor.js.map +1 -0
  331. package/dist/validate.d.ts.map +1 -1
  332. package/dist/validate.js +53 -6
  333. package/dist/validate.js.map +1 -1
  334. package/dist/webhook-rate-limit.d.ts +29 -0
  335. package/dist/webhook-rate-limit.d.ts.map +1 -0
  336. package/dist/webhook-rate-limit.js +38 -0
  337. package/dist/webhook-rate-limit.js.map +1 -0
  338. package/dist/webhook-signature.d.ts +22 -0
  339. package/dist/webhook-signature.d.ts.map +1 -0
  340. package/dist/webhook-signature.js +53 -0
  341. package/dist/webhook-signature.js.map +1 -0
  342. package/dist/worker-cli.d.ts +13 -1
  343. package/dist/worker-cli.d.ts.map +1 -1
  344. package/dist/worker-cli.js +133 -12
  345. package/dist/worker-cli.js.map +1 -1
  346. package/dist/worker-input.d.ts +4 -0
  347. package/dist/worker-input.d.ts.map +1 -0
  348. package/dist/worker-input.js +14 -0
  349. package/dist/worker-input.js.map +1 -0
  350. package/dist/worker-lease.d.ts +5 -0
  351. package/dist/worker-lease.d.ts.map +1 -0
  352. package/dist/worker-lease.js +80 -0
  353. package/dist/worker-lease.js.map +1 -0
  354. package/dist/worker-spend.d.ts +19 -0
  355. package/dist/worker-spend.d.ts.map +1 -0
  356. package/dist/worker-spend.js +22 -0
  357. package/dist/worker-spend.js.map +1 -0
  358. package/dist/worker-usage.d.ts +7 -0
  359. package/dist/worker-usage.d.ts.map +1 -0
  360. package/dist/worker-usage.js +54 -0
  361. package/dist/worker-usage.js.map +1 -0
  362. package/dist/worker.d.ts +2 -0
  363. package/dist/worker.d.ts.map +1 -1
  364. package/dist/worker.js +22 -8
  365. package/dist/worker.js.map +1 -1
  366. package/dist/wrapper-session.d.ts +1 -1
  367. package/dist/wrapper-session.d.ts.map +1 -1
  368. package/dist/wrapper-session.js +56 -19
  369. package/dist/wrapper-session.js.map +1 -1
  370. package/dist/yaml-helper-effect.d.ts +8 -0
  371. package/dist/yaml-helper-effect.d.ts.map +1 -0
  372. package/dist/yaml-helper-effect.js +79 -0
  373. package/dist/yaml-helper-effect.js.map +1 -0
  374. package/dist/yaml-helpers.d.ts +18 -0
  375. package/dist/yaml-helpers.d.ts.map +1 -0
  376. package/dist/yaml-helpers.js +116 -0
  377. package/dist/yaml-helpers.js.map +1 -0
  378. package/package.json +19 -2
  379. package/src/adapters/base.ts +53 -0
  380. package/src/adapters/claude.ts +55 -0
  381. package/src/adapters/codex.ts +56 -0
  382. package/src/adapters/index.ts +36 -0
  383. package/src/adapters/wrapper.ts +42 -0
  384. package/src/agent-relay-transport.ts +187 -0
  385. package/src/authored-budget.ts +69 -0
  386. package/src/authored-flow-error.ts +10 -10
  387. package/src/authored-flow-executor.ts +171 -347
  388. package/src/authored-flow-loader.ts +60 -3
  389. package/src/authored-flow-operation.ts +38 -5
  390. package/src/authored-helper-effect.ts +143 -0
  391. package/src/authored-mcp.ts +166 -0
  392. package/src/authored-memory.ts +55 -0
  393. package/src/authored-plugin-effect.ts +152 -0
  394. package/src/authored-step-output.ts +107 -0
  395. package/src/authored-worker-step.ts +181 -0
  396. package/src/budget-preflight.ts +31 -0
  397. package/src/budget.ts +49 -0
  398. package/src/bundle-transport.ts +79 -0
  399. package/src/bundle-typescript.ts +98 -0
  400. package/src/bundle.ts +182 -0
  401. package/src/child-stop.ts +138 -0
  402. package/src/cli/add.ts +45 -0
  403. package/src/cli/build.ts +214 -0
  404. package/src/cli/bundle-preflight.ts +28 -0
  405. package/src/cli/check-helper-body.ts +14 -0
  406. package/src/cli/check-triggers.ts +41 -0
  407. package/src/cli/check-typescript.ts +63 -0
  408. package/src/cli/check.ts +132 -14
  409. package/src/cli/cloud-run.ts +47 -0
  410. package/src/cli/deploy.ts +52 -0
  411. package/src/cli/deterministic-failure.ts +69 -0
  412. package/src/cli/direct-run.ts +79 -18
  413. package/src/cli/hn-monitor.ts +2 -1
  414. package/src/cli/replay.ts +81 -0
  415. package/src/cli/reuse.ts +23 -0
  416. package/src/cli/run-digest.ts +24 -0
  417. package/src/cli/run.ts +104 -21
  418. package/src/cli/serve-webhook.ts +246 -0
  419. package/src/cli/tick-runner.ts +2 -1
  420. package/src/cli-adapter.ts +31 -74
  421. package/src/cli-watch.ts +174 -0
  422. package/src/cli.ts +396 -17
  423. package/src/cloud-http.ts +100 -0
  424. package/src/cloud-run.ts +137 -0
  425. package/src/compile.ts +102 -23
  426. package/src/create-flow.ts +82 -0
  427. package/src/daemon-connection.ts +32 -1
  428. package/src/demo-hn-monitor.ts +2 -1
  429. package/src/effect-channel.ts +97 -0
  430. package/src/failure-kinds.ts +38 -0
  431. package/src/gate-contract.ts +7 -0
  432. package/src/helper-preflight.ts +34 -0
  433. package/src/helper-storage.ts +23 -0
  434. package/src/helper-writeback.ts +83 -0
  435. package/src/index.ts +26 -0
  436. package/src/input-binding.ts +76 -0
  437. package/src/journal-client.ts +20 -7
  438. package/src/journal-offset.ts +60 -0
  439. package/src/journal-reader.ts +165 -0
  440. package/src/json-schema.ts +34 -18
  441. package/src/llm-worker.ts +82 -0
  442. package/src/local-agent.ts +30 -0
  443. package/src/mcp-client.ts +97 -0
  444. package/src/mcp-config.ts +42 -0
  445. package/src/mcp-stdio.ts +84 -0
  446. package/src/model-pricing.ts +32 -0
  447. package/src/mount-registry.ts +44 -0
  448. package/src/named-gate-lowering.ts +147 -0
  449. package/src/named-gates.ts +76 -0
  450. package/src/observer-link.ts +368 -0
  451. package/src/plugin-loader.ts +88 -0
  452. package/src/plugin-manifest.ts +76 -0
  453. package/src/preflight.ts +178 -4
  454. package/src/progress.ts +51 -0
  455. package/src/protocol.ts +12 -0
  456. package/src/pty-sidechannel.ts +92 -0
  457. package/src/scope-compiler.ts +105 -0
  458. package/src/slack-preflight.ts +25 -0
  459. package/src/slack-writeback.ts +63 -0
  460. package/src/spec.ts +112 -4
  461. package/src/step-dependencies.ts +3 -1
  462. package/src/step-fields.ts +5 -2
  463. package/src/trigger-executor.ts +34 -0
  464. package/src/validate.ts +47 -6
  465. package/src/webhook-rate-limit.ts +69 -0
  466. package/src/webhook-signature.ts +74 -0
  467. package/src/worker-cli.ts +156 -11
  468. package/src/worker-input.ts +15 -0
  469. package/src/worker-lease.ts +85 -0
  470. package/src/worker-spend.ts +23 -0
  471. package/src/worker-usage.ts +48 -0
  472. package/src/worker.ts +27 -9
  473. package/src/wrapper-session.ts +50 -15
  474. package/src/yaml-helper-effect.ts +78 -0
  475. package/src/yaml-helpers.ts +131 -0
package/src/worker-cli.ts CHANGED
@@ -1,14 +1,25 @@
1
+ import { decodeProviderResult, decodeWrapperResult, requirePricedUsage } from './worker-usage.js';
2
+ import { openSidechannel, type SidechannelContext } from './pty-sidechannel.js';
1
3
  import { spawn } from 'node:child_process';
4
+ import { childStop, ownsProcessGroup } from './child-stop.js';
2
5
  import {
3
6
  agentExecution,
7
+ llmExecution,
4
8
  cliAdapterKind,
5
9
  type CliInvocation,
10
+ type CliAdapterKind,
6
11
  } from './cli-adapter.js';
7
12
  import {
8
13
  runWrapperSession,
9
14
  type WrapperSessionLimits,
10
15
  } from './wrapper-session.js';
11
16
  import { wrapperEnvironment } from './wrapper-runtime.js';
17
+ import {
18
+ agentRelaySpawn,
19
+ deriveAgentName,
20
+ AgentRelayTransportError,
21
+ type AgentTransport,
22
+ } from './agent-relay-transport.js';
12
23
 
13
24
  /** Present only when a dispatched agent step carries a journaled wake context. */
14
25
  export const WAKE_CONTEXT_ENV = 'RELAYFLOW_WAKE_CONTEXT';
@@ -21,35 +32,61 @@ export const WAKE_CONTEXT_ENV = 'RELAYFLOW_WAKE_CONTEXT';
21
32
  export const MODEL_ENV = 'RELAYFLOW_MODEL';
22
33
 
23
34
  export interface WorkerCliResult {
35
+ tokens_input?: number;
36
+ tokens_output?: number;
24
37
  exit_code: number | null;
25
38
  stdout_tail: string;
26
39
  stderr_tail: string;
27
40
  }
28
41
 
42
+ /**
43
+ * Optional identity threaded through so the relay transport can derive a
44
+ * stable, DM-addressable agent name (flows#385).
45
+ */
46
+ export interface AgentRelayContext {
47
+ runId: string;
48
+ stepId: string;
49
+ }
50
+
29
51
  export async function runAgentCli(
30
52
  cli: string,
31
53
  instruction: string,
32
54
  wakeContext: unknown,
33
55
  model?: string,
34
56
  wrapperLimits?: Partial<WrapperSessionLimits>,
57
+ signal?: AbortSignal,
58
+ mode: 'agent' | 'llm' = 'agent',
59
+ sidechannel?: SidechannelContext,
60
+ cwd?: string,
61
+ transport: AgentTransport = 'direct',
62
+ relayContext?: AgentRelayContext,
35
63
  ): Promise<WorkerCliResult> {
64
+ signal?.throwIfAborted();
65
+ if (signal !== undefined && process.platform === 'win32') {
66
+ throw new Error('Lease-bound agent execution requires macOS or Linux process-group cancellation; Windows is unsupported.');
67
+ }
36
68
  const kind = cliAdapterKind(cli);
37
69
 
38
70
  if (kind === 'relayflows-wrapper-v1') {
39
- return runWrapperSession(
71
+ return requirePricedUsage(decodeWrapperResult(await runWrapperSession(
40
72
  cli,
41
73
  instruction,
42
74
  wakeContext,
43
75
  model,
44
76
  wrapperEnvironment(process.env),
45
77
  wrapperLimits,
46
- );
78
+ signal,
79
+ )), model);
80
+ }
81
+
82
+ if (mode === 'agent' && transport === 'relay') {
83
+ return runViaAgentRelay(kind, instruction, model, relayContext, cwd);
47
84
  }
48
85
 
49
86
  const env: NodeJS.ProcessEnv = { ...process.env };
50
87
  delete env[WAKE_CONTEXT_ENV];
51
88
  delete env[MODEL_ENV];
52
- const invocation = agentExecution(kind, instruction, model);
89
+ const invocation = mode === 'llm' ? llmExecution(kind, instruction, model) : agentExecution(kind, instruction, model);
53
90
 
54
91
  if (wakeContext !== undefined) {
55
92
  try {
@@ -64,16 +101,100 @@ export async function runAgentCli(
64
101
  }
65
102
 
66
103
  if (invocation.modelEnv !== undefined) env[MODEL_ENV] = invocation.modelEnv;
67
- return spawnInvocation(cli, invocation, env);
104
+ // Structured provider output carries the authoritative token counts.
105
+ const args = [...invocation.args];
106
+ args.splice(args.length - 1, 0, ...(kind === 'claude' ? ['--output-format', 'json'] : ['--json']));
107
+ return requirePricedUsage(decodeProviderResult(await spawnInvocation(cli, { ...invocation, args }, env, signal, sidechannel, cwd), kind), model);
68
108
  }
69
109
 
70
- function spawnInvocation(
110
+ /**
111
+ * Relay-transport path (flows#385). Fires an agent-relay spawn HTTP request
112
+ * that registers the CLI as a first-class workspace participant DMs can
113
+ * steer, then reports the spawn outcome as a WorkerCliResult. Streaming
114
+ * completion tracking is a follow-up — this initial slice proves the
115
+ * transport wire and returns quickly with the registered name in stdout.
116
+ */
117
+ async function runViaAgentRelay(
118
+ kind: CliAdapterKind,
119
+ instruction: string,
120
+ model: string | undefined,
121
+ relayContext: AgentRelayContext | undefined,
122
+ worker_cwd: string | undefined,
123
+ ): Promise<WorkerCliResult> {
124
+ if (kind === 'relayflows-wrapper-v1') {
125
+ return {
126
+ exit_code: null,
127
+ stdout_tail: '',
128
+ stderr_tail: 'agent-relay transport does not support the relayflows-wrapper-v1 same-process session.',
129
+ };
130
+ }
131
+ if (relayContext === undefined) {
132
+ return {
133
+ exit_code: null,
134
+ stdout_tail: '',
135
+ stderr_tail: 'agent-relay transport requires a relayContext (runId, stepId); the caller did not thread it through.',
136
+ };
137
+ }
138
+ const name = deriveAgentName(relayContext.runId, relayContext.stepId);
139
+ try {
140
+ const handle = await agentRelaySpawn({
141
+ name,
142
+ cli: kind,
143
+ task: instruction,
144
+ ...(model === undefined ? {} : { model }),
145
+ ...(worker_cwd === undefined ? {} : { worker_cwd }),
146
+ });
147
+ return {
148
+ exit_code: 0,
149
+ stdout_tail: JSON.stringify({ registeredName: handle.registeredName, invocationId: handle.invocationId }),
150
+ stderr_tail: '',
151
+ };
152
+ } catch (error) {
153
+ const detail = error instanceof AgentRelayTransportError
154
+ ? error.message
155
+ : `agent-relay spawn failed: ${(error as Error).message ?? String(error)}`;
156
+ return { exit_code: null, stdout_tail: '', stderr_tail: detail };
157
+ }
158
+ }
159
+
160
+ async function spawnInvocation(
71
161
  cli: string,
72
162
  invocation: CliInvocation,
73
163
  env: NodeJS.ProcessEnv,
164
+ signal?: AbortSignal,
165
+ sidechannel?: SidechannelContext,
166
+ cwd?: string,
74
167
  ): Promise<WorkerCliResult> {
168
+ let writeInput: (bytes: Buffer) => Promise<boolean> = async () => false;
169
+ let canDrive = () => false;
170
+ let driven = false;
171
+ const channel = sidechannel === undefined ? undefined : await openSidechannel({
172
+ ...sidechannel,
173
+ onDrive() { driven = true; sidechannel.onDrive(); },
174
+ }, bytes => writeInput(bytes), () => canDrive());
175
+ if (signal?.aborted) { channel?.close(); signal.throwIfAborted(); }
75
176
  return new Promise((resolve) => {
76
- const child = spawn(cli, invocation.args, { stdio: ['ignore', 'pipe', 'pipe'], env });
177
+ const ownsGroup = ownsProcessGroup(signal);
178
+ const child = spawn(cli, invocation.args, {
179
+ stdio: ['pipe', 'pipe', 'pipe'], env,
180
+ detached: ownsGroup,
181
+ ...(cwd === undefined ? {} : { cwd }),
182
+ });
183
+ child.stdin.on('error', () => {});
184
+ if (channel === undefined) child.stdin.end();
185
+ canDrive = () => !child.stdin.destroyed && !child.stdin.writableEnded;
186
+ // A pipe cannot be reopened after EOF. Give startup subscribers a bounded
187
+ // chance to opt into drive, then let unattended/view-only CLIs read EOF.
188
+ const inputTimer = channel === undefined ? undefined : setTimeout(() => {
189
+ if (!driven) child.stdin.end();
190
+ }, 100);
191
+ writeInput = bytes => new Promise(resolve => {
192
+ if (!canDrive()) { resolve(false); return; }
193
+ // write(false) still accepts the bytes. The completion callback waits
194
+ // until they flush; the sidechannel pauses its reader in the meantime.
195
+ child.stdin.write(bytes, error => resolve(!error));
196
+ });
197
+ const stop = childStop(child, ownsGroup);
77
198
  const stdout: Buffer[] = [];
78
199
  const stderr: Buffer[] = [];
79
200
  let settled = false;
@@ -81,24 +202,48 @@ function spawnInvocation(
81
202
  const finish = (result: WorkerCliResult): void => {
82
203
  if (settled) return;
83
204
  settled = true;
205
+ if (inputTimer !== undefined) clearTimeout(inputTimer);
206
+ channel?.close();
84
207
  if (timer !== undefined) clearTimeout(timer);
208
+ signal?.removeEventListener('abort', onAbort);
85
209
  resolve(result);
86
210
  };
87
- child.stdout.on('data', (chunk: Buffer) => stdout.push(chunk));
88
- child.stderr.on('data', (chunk: Buffer) => stderr.push(chunk));
89
- child.once('error', (error) => finish({
211
+ const onAbort = (): void => {
212
+ stop.kill();
213
+ finish({ exit_code: null, stdout_tail: '', stderr_tail: 'Agent execution aborted: lease ownership lost.' });
214
+ };
215
+ /**
216
+ * Same invariant as `wrapper-session.ts`: `'close'` and `'error'` are
217
+ * evidence about the DIRECT CHILD, so they may not settle over a pending
218
+ * escalation, and only `maySettleOnChildExit` may drop one. This settle
219
+ * carries no deadline of its own because it needs none — the timeout below
220
+ * settles on the spot and lets its escalation outlive that, so refusing
221
+ * here can only defer to a `'close'` we are still going to get.
222
+ */
223
+ const finishOnChildExit = (result: WorkerCliResult): void => {
224
+ if (!stop.maySettleOnChildExit()) return;
225
+ finish(result);
226
+ };
227
+ signal?.addEventListener('abort', onAbort, { once: true });
228
+ if (signal?.aborted) onAbort();
229
+ child.stdout.on('data', (chunk: Buffer) => { stdout.push(chunk); channel?.publish(chunk); });
230
+ child.stderr.on('data', (chunk: Buffer) => { stderr.push(chunk); channel?.publish(chunk); });
231
+ child.once('error', (error) => finishOnChildExit({
90
232
  exit_code: null,
91
233
  stdout_tail: Buffer.concat(stdout).toString('utf8'),
92
234
  stderr_tail: error.message,
93
235
  }));
94
- child.once('close', (code) => finish({
236
+ child.once('close', (code) => finishOnChildExit({
95
237
  exit_code: code,
96
238
  stdout_tail: Buffer.concat(stdout).toString('utf8'),
97
239
  stderr_tail: Buffer.concat(stderr).toString('utf8'),
98
240
  }));
99
241
  if (invocation.timeoutMs > 0) {
100
242
  timer = setTimeout(() => {
101
- child.kill('SIGTERM');
243
+ // The stop outlives this settle on purpose: `finish` resolves the step,
244
+ // but only the forced group kill releases the pipes a leaked descendant
245
+ // is holding, and until they are released `flows run` cannot exit.
246
+ stop.terminate();
102
247
  finish({
103
248
  exit_code: null,
104
249
  stdout_tail: Buffer.concat(stdout).toString('utf8'),
@@ -0,0 +1,15 @@
1
+ import type { StepDispatchEvent } from './protocol.js';
2
+
3
+ /** Memory precedes the final, visibly delimited JSON input segment. */
4
+ export function workerInstruction(instruction: string, dispatch: StepDispatchEvent): string {
5
+ const withMemory = dispatch.memory === undefined ? instruction
6
+ : `${instruction}\n\nMemory context (journaled):\n${JSON.stringify(dispatch.memory.pack)}`;
7
+ if (dispatch.input === undefined) {
8
+ if (typeof dispatch.spec === 'object' && dispatch.spec !== null
9
+ && 'input' in dispatch.spec) {
10
+ throw new Error('Step declares input bindings but the kernel did not resolve them; upgrade relayflowd.');
11
+ }
12
+ return withMemory;
13
+ }
14
+ return `${withMemory}\n\ninput:\n${JSON.stringify(dispatch.input)}`;
15
+ }
@@ -0,0 +1,85 @@
1
+ import type { JournalClient } from './journal-client.js';
2
+ import type { StepDispatchEvent } from './protocol.js';
3
+
4
+ /** Hold the dispatched lease only while its subprocess is still ours to run. */
5
+ export async function withWorkerLease<T>(
6
+ client: JournalClient,
7
+ dispatch: StepDispatchEvent,
8
+ execute: (signal: AbortSignal) => Promise<T>,
9
+ ): Promise<T> {
10
+ const controller = new AbortController();
11
+ let stopped = false;
12
+ let latestDeadline = dispatch.lease_deadline_ms;
13
+ let renewalTimer: NodeJS.Timeout | undefined;
14
+ let expiryTimer: NodeJS.Timeout | undefined;
15
+ let pending: Promise<void> = Promise.resolve();
16
+ const fail = (error: unknown): void => { controller.abort(error); };
17
+ const armExpiry = (deadline: number): number => {
18
+ const remaining = deadline - Date.now();
19
+ if (!Number.isFinite(remaining) || remaining <= 0) {
20
+ throw new Error(`Agent lease is already expired for ${dispatch.run_id}/${dispatch.step_id}.`);
21
+ }
22
+ latestDeadline = deadline;
23
+ if (expiryTimer !== undefined) clearTimeout(expiryTimer);
24
+ expiryTimer = setTimeout(() => fail(new Error(
25
+ `Agent lease expired before renewal for ${dispatch.run_id}/${dispatch.step_id}.`,
26
+ )), remaining);
27
+ return remaining;
28
+ };
29
+ const renew = async (): Promise<void> => {
30
+ const result = await untilAborted(client.stepHeartbeat(
31
+ dispatch.run_id, dispatch.step_id, dispatch.attempt, dispatch.lease_id,
32
+ ), controller.signal);
33
+ controller.signal.throwIfAborted();
34
+ // A response handled after local expiry cannot revive ownership, even
35
+ // if its future deadline was issued before this event loop stalled.
36
+ if (Date.now() >= latestDeadline) {
37
+ throw new Error(`Agent lease expired before renewal for ${dispatch.run_id}/${dispatch.step_id}.`);
38
+ }
39
+ const remaining = armExpiry(result.lease_deadline_ms);
40
+ if (!stopped) {
41
+ renewalTimer = setTimeout(() => {
42
+ pending = renew().catch(fail);
43
+ }, Math.max(1, Math.floor(remaining / 3)));
44
+ }
45
+ };
46
+ try {
47
+ armExpiry(dispatch.lease_deadline_ms);
48
+ // Establish ownership before starting an effectful process.
49
+ await renew();
50
+ const result = await execute(controller.signal);
51
+ stopped = true;
52
+ if (renewalTimer !== undefined) clearTimeout(renewalTimer);
53
+ // Drain any renewal before the caller sends step.complete. A renewal
54
+ // racing after completion would otherwise report a spurious lease error.
55
+ await pending;
56
+ controller.signal.throwIfAborted();
57
+ // Timer callbacks can be delayed behind a resolved subprocess promise.
58
+ // Check the clock itself before permitting step.complete.
59
+ if (Date.now() >= latestDeadline) {
60
+ throw new Error(`Agent lease expired before completion for ${dispatch.run_id}/${dispatch.step_id}.`);
61
+ }
62
+ return result;
63
+ } finally {
64
+ stopped = true;
65
+ controller.abort(new Error('Worker lease scope ended.'));
66
+ if (renewalTimer !== undefined) clearTimeout(renewalTimer);
67
+ if (expiryTimer !== undefined) clearTimeout(expiryTimer);
68
+ await pending;
69
+ }
70
+ }
71
+
72
+ function untilAborted<T>(request: Promise<T>, signal: AbortSignal): Promise<T> {
73
+ return new Promise((resolve, reject) => {
74
+ const abort = (): void => { reject(signal.reason); };
75
+ signal.addEventListener('abort', abort, { once: true });
76
+ request.then(value => {
77
+ signal.removeEventListener('abort', abort);
78
+ resolve(value);
79
+ }, error => {
80
+ signal.removeEventListener('abort', abort);
81
+ reject(error);
82
+ });
83
+ if (signal.aborted) abort();
84
+ });
85
+ }
@@ -0,0 +1,23 @@
1
+ import { pricedUsage } from './model-pricing.js';
2
+ import type { WorkerCliResult } from './worker-cli.js';
3
+
4
+ /**
5
+ * Attach token/dollar usage to a worker's CLI result. Invalid token counts
6
+ * (non-integer or negative) are the one remaining failure mode — those are
7
+ * journaled as `worker_error` with the usage projected from clamped counts.
8
+ *
9
+ * Unpriced models are NOT a failure here: `pricedUsage` returns
10
+ * `dollars: null` and preflight (see `budgetDiagnostics`) has already refused
11
+ * declared dollar budgets against unpriced models before the CLI dispatched.
12
+ */
13
+ export function workerSpend(result: WorkerCliResult, model?: string) {
14
+ try { return { result, usage: pricedUsage(model, result.tokens_input, result.tokens_output) }; }
15
+ catch (error) {
16
+ return {
17
+ result: { ...result, exit_code: null, stderr_tail: error instanceof Error ? error.message : 'Invalid model usage' },
18
+ usage: pricedUsage(undefined,
19
+ Number.isSafeInteger(result.tokens_input) && result.tokens_input! >= 0 ? result.tokens_input : 0,
20
+ Number.isSafeInteger(result.tokens_output) && result.tokens_output! >= 0 ? result.tokens_output : 0),
21
+ };
22
+ }
23
+ }
@@ -0,0 +1,48 @@
1
+ import type { WorkerCliResult } from './worker-cli.js';
2
+ import { MODEL_PRICING } from './model-pricing.js';
3
+
4
+ type RecordValue = Record<string, unknown>;
5
+ const record = (value: unknown): value is RecordValue => typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ const count = (value: unknown): value is number => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
7
+
8
+ function invalid(result: WorkerCliResult, detail: string): WorkerCliResult {
9
+ return { ...result, exit_code: null, stderr_tail: `${result.stderr_tail}\n${detail}`.trim() };
10
+ }
11
+
12
+ function usageResult(result: WorkerCliResult, usage: unknown, output: unknown): WorkerCliResult {
13
+ if (output === undefined) return invalid(result, 'Provider completion is missing its output.');
14
+ if (!record(usage) || !count(usage.input_tokens) || !count(usage.output_tokens)) {
15
+ return invalid(result, 'Provider completion has invalid token usage.');
16
+ }
17
+ return { ...result, stdout_tail: typeof output === 'string' ? output : JSON.stringify(output),
18
+ tokens_input: usage.input_tokens, tokens_output: usage.output_tokens };
19
+ }
20
+
21
+ /** Provider envelopes are execution metadata, never the authored output. */
22
+ export function decodeProviderResult(result: WorkerCliResult, kind: 'claude' | 'codex'): WorkerCliResult {
23
+ const frames: RecordValue[] = [];
24
+ for (const line of result.stdout_tail.split('\n')) {
25
+ try { const frame: unknown = JSON.parse(line); if (record(frame)) frames.push(frame); } catch { /* Plain text remains output. */ }
26
+ }
27
+ const terminal = [...frames].reverse().find(f => kind === 'claude' ? f.type === 'result' : f.type === 'turn.completed');
28
+ if (terminal === undefined) return result;
29
+ const text = kind === 'claude' ? terminal.result : frames
30
+ .flatMap(f => f.type === 'item.completed' && record(f.item) && f.item.type === 'agent_message' && typeof f.item.text === 'string' ? [f.item.text] : []).join('\n');
31
+ return usageResult(result, terminal.usage, text);
32
+ }
33
+
34
+ /** Optional wrapper result envelope; existing opaque text output stays valid. */
35
+ export function decodeWrapperResult(result: WorkerCliResult): WorkerCliResult {
36
+ let value: unknown;
37
+ try { value = JSON.parse(result.stdout_tail); } catch { return result; }
38
+ if (!record(value) || value.protocol !== 'relayflows-agent-cli-v1-result') return result;
39
+ return usageResult(result, value.usage, value.output);
40
+ }
41
+
42
+ export function requirePricedUsage(result: WorkerCliResult, model?: string): WorkerCliResult {
43
+ if (model !== undefined && Object.hasOwn(MODEL_PRICING, model)
44
+ && (result.tokens_input === undefined || result.tokens_output === undefined)) {
45
+ return invalid(result, 'Priced model completion is missing token usage.');
46
+ }
47
+ return result;
48
+ }
package/src/worker.ts CHANGED
@@ -1,8 +1,14 @@
1
+ import { workerSpend } from './worker-spend.js';
2
+ import type { WorkerCliResult } from './worker-cli.js';
1
3
  import { EventEmitter } from 'node:events';
2
4
  import type { JournalClient } from './journal-client.js';
3
5
  import type { Pins, StepDispatchEvent } from './protocol.js';
4
6
  import type { KernelAgentStep } from './spec.js';
5
7
  import { runAgentCli } from './worker-cli.js';
8
+ import { withWorkerLease } from './worker-lease.js';
9
+ import { workerInstruction } from './worker-input.js';
10
+ import { helperCall } from './yaml-helpers.js';
11
+ import { completeHelperDispatch } from './yaml-helper-effect.js';
6
12
 
7
13
  export { MODEL_ENV, WAKE_CONTEXT_ENV } from './worker-cli.js';
8
14
 
@@ -10,6 +16,8 @@ export interface AgentWorkerOptions {
10
16
  workerId: string;
11
17
  pins: Pins;
12
18
  capacity?: number;
19
+ dataDir?: string;
20
+ onPtyReady?: (path: string) => void;
13
21
  }
14
22
 
15
23
  /**
@@ -90,9 +98,23 @@ export class AgentWorker extends EventEmitter {
90
98
 
91
99
  private async execute(dispatch: StepDispatchEvent): Promise<void> {
92
100
  const spec = dispatch.spec as Partial<KernelAgentStep>;
93
- const result = typeof spec.cli === 'string' && typeof spec.instruction === 'string'
94
- ? await runAgentCli(spec.cli, memoryInstruction(spec.instruction, dispatch.memory), dispatch.wake_context, spec.model)
95
- : { exit_code: null, stdout_tail: '', stderr_tail: 'agent step has no declared CLI' };
101
+ const helper = helperCall(spec);
102
+ if (helper !== undefined) {
103
+ if (this.options.dataDir === undefined) throw new Error('Helper worker requires a data directory for durable receipts');
104
+ await completeHelperDispatch(this.client, dispatch, helper, this.options.dataDir);
105
+ return;
106
+ }
107
+ let humanIntervention = false;
108
+ const completed: WorkerCliResult = await withWorkerLease(this.client, dispatch, signal =>
109
+ typeof spec.cli === 'string' && typeof spec.instruction === 'string'
110
+ ? runAgentCli(spec.cli, workerInstruction(spec.instruction, dispatch), dispatch.wake_context, spec.model, undefined, signal, 'agent', this.options.dataDir === undefined ? undefined : {
111
+ dataDir: this.options.dataDir, runId: dispatch.run_id, stepId: dispatch.step_id,
112
+ onReady: this.options.onPtyReady, onDrive: () => { humanIntervention = true; },
113
+ }, typeof spec.cwd === 'string' ? spec.cwd : undefined,
114
+ spec.transport === 'relay' ? 'relay' : 'direct',
115
+ { runId: dispatch.run_id, stepId: dispatch.step_id })
116
+ : Promise.resolve({ exit_code: null, stdout_tail: '', stderr_tail: 'agent step has no declared CLI' }));
117
+ const { result, usage } = workerSpend(completed, spec.model);
96
118
  const completionReason = result.exit_code === 0 ? 'success' : 'worker_error';
97
119
 
98
120
  // Output shape: if the CLI's stdout parses as JSON, promote THAT
@@ -118,6 +140,8 @@ export class AgentWorker extends EventEmitter {
118
140
  completionReason,
119
141
  {
120
142
  output,
143
+ ...(humanIntervention ? { human_intervention: true } : {}),
144
+ ...(usage !== undefined ? { usage } : {}),
121
145
  started_pins: dispatch.pins,
122
146
  end_pins: dispatch.pins,
123
147
  },
@@ -148,9 +172,3 @@ export function parseJsonOutput(stdout: string): Record<string, unknown> | null
148
172
  }
149
173
  return parsed as Record<string, unknown>;
150
174
  }
151
-
152
- /** Pass the journaled pack to both raw CLI and wrapper execution paths. */
153
- function memoryInstruction(instruction: string, memory: StepDispatchEvent['memory']): string {
154
- return memory === undefined ? instruction
155
- : `${instruction}\n\nMemory context (journaled):\n${JSON.stringify(memory.pack)}`;
156
- }
@@ -1,4 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
+ import { FORCE_KILL_DELAY_MS, childStop, ownsProcessGroup } from './child-stop.js';
2
3
  import {
3
4
  WRAPPER_EXECUTE_TOKEN,
4
5
  WRAPPER_IDENTIFY_ARG,
@@ -28,7 +29,6 @@ const DEFAULT_LIMITS: WrapperSessionLimits = {
28
29
  maxOutputBytes: 1_048_576,
29
30
  };
30
31
  const HANDSHAKE_OUTPUT_LIMIT = 8_192;
31
- const FORCE_KILL_DELAY_MS = 1_000;
32
32
  /**
33
33
  * Grace after `SIGKILL` before the reader settles on its own. Node emits
34
34
  * `'close'` only once every inherited stdio pipe is closed, which any
@@ -48,7 +48,12 @@ export function runWrapperSession(
48
48
  model: string | undefined,
49
49
  env: NodeJS.ProcessEnv,
50
50
  overrides: Partial<WrapperSessionLimits> = {},
51
+ signal?: AbortSignal,
51
52
  ): Promise<WrapperSessionResult> {
53
+ if (signal?.aborted) return Promise.reject(signal.reason);
54
+ if (signal !== undefined && process.platform === 'win32') {
55
+ return Promise.reject(new Error('Lease-bound wrapper execution requires macOS or Linux process-group cancellation; Windows is unsupported.'));
56
+ }
52
57
  const limits = sessionLimits(overrides);
53
58
  let request: string;
54
59
  try {
@@ -72,7 +77,7 @@ export function runWrapperSession(
72
77
  ));
73
78
  }
74
79
 
75
- return executePinnedWrapper(cli, identity, request, env, limits);
80
+ return executePinnedWrapper(cli, identity, request, env, limits, signal);
76
81
  }
77
82
 
78
83
  function executePinnedWrapper(
@@ -81,12 +86,16 @@ function executePinnedWrapper(
81
86
  request: string,
82
87
  env: NodeJS.ProcessEnv,
83
88
  limits: WrapperSessionLimits,
89
+ signal?: AbortSignal,
84
90
  ): Promise<WrapperSessionResult> {
85
91
  return new Promise((resolve) => {
92
+ const ownsGroup = ownsProcessGroup(signal);
86
93
  const child = spawn(identity.executable, [WRAPPER_IDENTIFY_ARG], {
87
94
  stdio: ['pipe', 'pipe', 'pipe'],
88
95
  env,
96
+ detached: ownsGroup,
89
97
  });
98
+ const stop = childStop(child, ownsGroup);
90
99
  const stdout: string[] = [];
91
100
  const stderr: Buffer[] = [];
92
101
  let handshakePending = '';
@@ -96,27 +105,53 @@ function executePinnedWrapper(
96
105
  let protocolError: string | undefined;
97
106
  let settled = false;
98
107
  let lifecycleTimer: NodeJS.Timeout | undefined;
99
- let killTimer: NodeJS.Timeout | undefined;
100
108
  let settleTimer: NodeJS.Timeout | undefined;
101
109
 
102
- const clearTimers = (): void => {
103
- if (lifecycleTimer !== undefined) clearTimeout(lifecycleTimer);
104
- if (killTimer !== undefined) clearTimeout(killTimer);
105
- if (settleTimer !== undefined) clearTimeout(settleTimer);
106
- };
107
110
  const finish = (result: WrapperSessionResult): void => {
108
111
  if (settled) return;
109
112
  settled = true;
110
- clearTimers();
113
+ if (lifecycleTimer !== undefined) clearTimeout(lifecycleTimer);
114
+ if (settleTimer !== undefined) clearTimeout(settleTimer);
115
+ signal?.removeEventListener('abort', onAbort);
111
116
  resolve(result);
112
117
  };
118
+ /**
119
+ * INVARIANT: a session may not settle until either the process group is
120
+ * confirmed dead or the escalation has actually run.
121
+ *
122
+ * `'close'` and `'error'` are evidence about the DIRECT CHILD and nothing
123
+ * more. A descendant that ignores `SIGTERM` and inherited none of the
124
+ * wrapper's stdio emits exactly those events while it is still running, so
125
+ * neither may drop a pending escalation and neither may settle ahead of
126
+ * one. Every child-level settle therefore goes through
127
+ * `maySettleOnChildExit`, which is the one place that asks the GROUP.
128
+ *
129
+ * When it says no, `terminate`'s own deadline settles instead, with a
130
+ * byte-identical `failure(protocolError)` result. That deadline is armed
131
+ * whenever an escalation is — both come from the single `terminate` below —
132
+ * so refusing here can defer a settle but can never strand one.
133
+ */
134
+ const finishOnChildExit = (result: WrapperSessionResult): void => {
135
+ // Asked before `finish`, and asked even once we have already settled:
136
+ // this is also the only place a pointless escalation is refunded, and a
137
+ // session that settled on `terminate`'s deadline still owes that refund.
138
+ if (!stop.maySettleOnChildExit()) return;
139
+ finish(result);
140
+ };
141
+ const onAbort = (): void => {
142
+ stop.kill();
143
+ finish(failure('Agent execution aborted: lease ownership lost.'));
144
+ };
145
+ signal?.addEventListener('abort', onAbort, { once: true });
146
+ if (signal?.aborted) { onAbort(); return; }
113
147
  const terminate = (message: string): void => {
114
148
  if (protocolError !== undefined) return;
115
149
  protocolError = message;
116
150
  if (lifecycleTimer !== undefined) clearTimeout(lifecycleTimer);
117
- child.kill('SIGTERM');
118
- killTimer = setTimeout(() => child.kill('SIGKILL'), FORCE_KILL_DELAY_MS);
119
- killTimer.unref();
151
+ // Same reach as an abort, only gentler first: this stop must find the
152
+ // whole group, or a descendant outlives the session still holding the
153
+ // stdio it inherited.
154
+ stop.terminate();
120
155
  // The reader owns the bound. `'close'` is emitted only after every
121
156
  // inherited stdio pipe closes, so a wrapper that leaves a descendant
122
157
  // holding one withholds it forever and strands the step with no
@@ -241,7 +276,7 @@ function executePinnedWrapper(
241
276
  child.stdin.on('error', () => {
242
277
  // A child that closes stdin before acknowledgement is classified on close.
243
278
  });
244
- child.once('error', (error) => finish(failure(error.message)));
279
+ child.once('error', (error) => finishOnChildExit(failure(error.message)));
245
280
  child.once('close', (code) => {
246
281
  if (protocolError === undefined && phase === 'execute' && executionPending.length > 0) {
247
282
  if (normalizeLine(executionPending) === WRAPPER_EXECUTE_TOKEN) {
@@ -251,13 +286,13 @@ function executePinnedWrapper(
251
286
  }
252
287
  }
253
288
  if (protocolError !== undefined || phase !== 'execute') {
254
- finish(failure(
289
+ finishOnChildExit(failure(
255
290
  protocolError
256
291
  ?? `CLI ${JSON.stringify(cli)} exited before completing the ${WRAPPER_IDENTIFY_TOKEN} same-process handshake.`,
257
292
  ));
258
293
  return;
259
294
  }
260
- finish({
295
+ finishOnChildExit({
261
296
  exit_code: code,
262
297
  stdout_tail: stdout.join(''),
263
298
  stderr_tail: Buffer.concat(stderr).toString('utf8'),