@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,98 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import { pathToFileURL } from 'node:url';
6
+ import { canonicalize } from './canonical.js';
7
+ import { compileSpec } from './compile.js';
8
+ import type { BundleFile } from './bundle.js';
9
+
10
+ /** Bun is an explicit build dependency, never an implicit install. */
11
+ function bun(args: string[], cwd: string): string {
12
+ const result = spawnSync('bun', ['--no-env-file', ...args], {
13
+ cwd, encoding: 'utf8', timeout: 120_000, maxBuffer: 16 * 1024 * 1024,
14
+ env: { PATH: process.env['PATH'], NODE_ENV: 'production', TZ: 'UTC' },
15
+ });
16
+ if (result.error || result.status !== 0) {
17
+ throw new Error(`TypeScript build requires Bun: ${result.error?.message ?? result.stderr}`);
18
+ }
19
+ return result.stdout.trim();
20
+ }
21
+
22
+ export async function buildTypescript(input: string) {
23
+ const directory = dirname(input);
24
+ const lockPath = await findLock(directory);
25
+ const lock = JSON.parse(await readFile(lockPath, 'utf8'));
26
+ if (![2, 3].includes(lock.lockfileVersion) || !lock.packages) {
27
+ throw new Error('package-lock.json: expected npm lockfile version 2 or 3 with pinned packages');
28
+ }
29
+ await checkInstalledVersions(dirname(lockPath), lock.packages);
30
+ const compiler = `bun@${bun(['--version'], directory)}`;
31
+ const staging = await mkdtemp(join(tmpdir(), 'flows-ts-'));
32
+ try {
33
+ // Evaluate module declarations, never the authored body. Resolve the runtime
34
+ // beside the input, keeping flow()'s WeakMap identity in the same module.
35
+ const probe = `
36
+ import { createRequire } from 'node:module';
37
+ import { pathToFileURL } from 'node:url';
38
+ const module = await import(${JSON.stringify(pathToFileURL(input).href)});
39
+ let spec = module.default;
40
+ let authored = false;
41
+ if (!spec || !Array.isArray(spec.steps)) {
42
+ const require = createRequire(${JSON.stringify(pathToFileURL(input).href)});
43
+ const { getFlowDefinition } = await import(pathToFileURL(require.resolve('@relayflows/surface/runtime')).href);
44
+ const definition = getFlowDefinition(spec);
45
+ if (Object.keys(definition.header).length) throw new Error('unsupported authored flow header');
46
+ if (!module.spec) throw new Error('authored flow() requires an exported spec declaration for build-time preflight; the body is not executed during build');
47
+ spec = module.spec;
48
+ if (spec.name !== definition.name) throw new Error('exported spec name must match flow() name');
49
+ authored = true;
50
+ }
51
+
52
+ process.stdout.write(JSON.stringify({ spec, authored }));
53
+ `;
54
+ const probePath = join(staging, 'inspect.ts');
55
+ await writeFile(probePath, probe);
56
+ const inspected = JSON.parse(bun([probePath], directory));
57
+ const spec = compileSpec(inspected.spec);
58
+ const executable = join(staging, 'flow');
59
+ bun(['build', '--compile', '--env=disable', '--no-compile-autoload-dotenv',
60
+ '--no-compile-autoload-bunfig', '--outfile', executable, input], directory);
61
+ const files: BundleFile[] = [
62
+ { path: 'flow', data: await readFile(executable) },
63
+ { path: 'lockfile.json', data: canonicalize(lock) },
64
+ ];
65
+ return { spec, authored: inspected.authored === true, compiler, files };
66
+ } finally { await rm(staging, { recursive: true, force: true }); }
67
+ }
68
+
69
+ /** Do not label a stale node_modules tree with a newer lockfile's pins. */
70
+ async function checkInstalledVersions(root: string, packages: Record<string, {
71
+ version?: string; optional?: boolean; dev?: boolean; link?: boolean; resolved?: string;
72
+ }>): Promise<void> {
73
+ for (const [path, entry] of Object.entries(packages)) {
74
+ if (path === '') continue;
75
+ if (path.startsWith('/') || path.includes('\\') || path.split('/').includes('..')
76
+ || (entry.resolved !== undefined && /^(?:file:|\/|[A-Za-z]:)/.test(entry.resolved))) {
77
+ throw new Error(`package-lock.json: ${path} must use portable, pinned dependency locations`);
78
+ }
79
+ if (entry.link) continue; // Workspace source is captured by the compiler.
80
+ let installed;
81
+ try { installed = JSON.parse(await readFile(join(root, path, 'package.json'), 'utf8')); }
82
+ catch (error) {
83
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT' && (entry.optional || entry.dev)) continue;
84
+ throw new Error(`package-lock.json: ${path} is missing; run npm ci before building`);
85
+ }
86
+ if (typeof entry.version !== 'string' || installed.version !== entry.version) {
87
+ throw new Error(`package-lock.json: ${path} does not match its pinned version; run npm ci before building`);
88
+ }
89
+ }
90
+ }
91
+ async function findLock(start: string): Promise<string> {
92
+ for (let directory = start; ; directory = dirname(directory)) {
93
+ const candidate = join(directory, 'package-lock.json');
94
+ try { await readFile(candidate); return candidate; }
95
+ catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
96
+ if (dirname(directory) === directory) throw new Error('package-lock.json: no workspace lockfile found for TS flow');
97
+ }
98
+ }
package/src/bundle.ts ADDED
@@ -0,0 +1,182 @@
1
+ import { createHash, createPrivateKey, createPublicKey, randomBytes, sign, verify } from 'node:crypto';
2
+ import { lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ import { canonicalize } from './canonical.js';
5
+
6
+ export interface BundleEntry { path: string; sha256: string; bytes: number }
7
+ export interface BundleFile { path: string; data: Uint8Array | string; executable?: boolean }
8
+ export interface BundleOptions {
9
+ name: string;
10
+ out: string;
11
+ repo: string;
12
+ files: BundleFile[];
13
+ env?: NodeJS.ProcessEnv;
14
+ warn(message: string): void;
15
+ }
16
+
17
+ export function sha256(data: Uint8Array | string): string {
18
+ return createHash('sha256').update(data).digest('hex');
19
+ }
20
+
21
+ function safePath(path: string): boolean {
22
+ return path.length > 0 && !path.includes('\\') && !path.includes('\0')
23
+ && path.split('/').every(part => part !== '' && part !== '.' && part !== '..')
24
+ && !path.includes(':');
25
+ }
26
+
27
+ /** Manifest and identity are envelopes, excluded to avoid circular hashing. */
28
+ export async function sealBundle(options: BundleOptions): Promise<string> {
29
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(options.name)) {
30
+ throw new Error('spec.canonical.json: name must be a safe single directory component');
31
+ }
32
+ const files = options.files.map(file => ({ ...file, data: Buffer.from(file.data) }))
33
+ .sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
34
+ const paths = new Set<string>();
35
+ for (const file of files) {
36
+ if (!safePath(file.path) || ['manifest.json', 'identity.json'].includes(file.path) || paths.has(file.path)) {
37
+ throw new Error(`${file.path}: invalid or duplicate bundle path`);
38
+ }
39
+ paths.add(file.path);
40
+ }
41
+ for (const required of ['spec.canonical.json', 'preflight.json', 'lockfile.json']) {
42
+ if (!paths.has(required)) throw new Error(`${required}: missing bundle file`);
43
+ }
44
+ const manifest = canonicalize(files.map(file => ({
45
+ path: file.path, sha256: sha256(file.data), bytes: file.data.length,
46
+ })));
47
+ const digest = sha256(manifest);
48
+ const out = resolve(options.out);
49
+ const target = join(out, `${options.name}@sha256:${digest}`);
50
+ let exists = false;
51
+ try { await lstat(target); exists = true; }
52
+ catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
53
+ if (exists) { await verifyBundle(target); return target; }
54
+ const identity = await signDigest(digest, options);
55
+ await mkdir(out, { recursive: true });
56
+ const staging = await mkdtemp(join(out, '.build-'));
57
+ try {
58
+ for (const file of files) {
59
+ await mkdir(dirname(join(staging, file.path)), { recursive: true });
60
+ await writeFile(join(staging, file.path), file.data, { mode: file.path === 'flow' || file.executable ? 0o755 : 0o644 });
61
+ }
62
+ await writeFile(join(staging, 'manifest.json'), manifest);
63
+ await writeFile(join(staging, 'identity.json'), canonicalize(identity));
64
+ try { await rename(staging, target); }
65
+ catch (error) {
66
+ if (!['EEXIST', 'ENOTEMPTY'].includes((error as NodeJS.ErrnoException).code ?? '')) throw error;
67
+ await verifyBundle(target);
68
+ }
69
+ return target;
70
+ } finally { await rm(staging, { recursive: true, force: true }); }
71
+ }
72
+
73
+ async function signDigest(digest: string, options: BundleOptions) {
74
+ let seedText = (options.env ?? process.env)['FLOWS_BUILD_KEY'];
75
+ if (seedText === undefined) {
76
+ try { seedText = (await readFile(join(options.repo, '.flows/build.key'), 'utf8')).trim(); }
77
+ catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
78
+ }
79
+ let seed: Buffer;
80
+ if (seedText === undefined) {
81
+ options.warn('identity_ephemeral: bundle can be verified but not attributed');
82
+ seed = randomBytes(32);
83
+ } else {
84
+ seed = Buffer.from(seedText, 'base64');
85
+ if (seed.length !== 32 || seed.toString('base64') !== seedText) {
86
+ throw new Error('build key: expected a base64-encoded 32-byte seed');
87
+ }
88
+ }
89
+ const privateKey = createPrivateKey({
90
+ key: Buffer.concat([Buffer.from('302e020100300506032b657004220420', 'hex'), seed]),
91
+ format: 'der', type: 'pkcs8',
92
+ });
93
+ const publicKey = createPublicKey(privateKey).export({ format: 'der', type: 'spki' }).subarray(-32);
94
+ return {
95
+ algorithm: 'ed25519', keyid: sha256(publicKey).slice(0, 16), pubkey_b64: publicKey.toString('base64'),
96
+ signature_hex: sign(null, Buffer.from(digest, 'hex'), privateKey).toString('hex'),
97
+ };
98
+ }
99
+
100
+ async function regularFile(root: string, path: string): Promise<Buffer> {
101
+ try {
102
+ const parts = path.split('/');
103
+ for (let i = 1; i <= parts.length; i++) {
104
+ const stat = await lstat(join(root, ...parts.slice(0, i)));
105
+ if (i === parts.length ? !stat.isFile() : !stat.isDirectory()) {
106
+ throw new Error('expected a regular file, without symlinks');
107
+ }
108
+ }
109
+ return await readFile(join(root, path));
110
+ } catch (error) {
111
+ throw new Error(`${path}: ${error instanceof Error ? error.message : 'unreadable'}`);
112
+ }
113
+ }
114
+
115
+ /** Verify an untrusted directory without following symlinks or manifest traversal.
116
+ * Bucket/cache readers supply the requested digest; local builds bind it to the directory name. */
117
+ export async function verifyBundle(directory: string, expectedDigest?: string): Promise<string> {
118
+ const root = resolve(directory);
119
+ if (!(await lstat(root)).isDirectory()) throw new Error('manifest.json: bundle must be a directory, not a symlink');
120
+ const raw = (await regularFile(root, 'manifest.json')).toString('utf8');
121
+ let manifest: BundleEntry[];
122
+ try {
123
+ const parsed: unknown = JSON.parse(raw);
124
+ if (!Array.isArray(parsed)) throw new Error('expected an array');
125
+ manifest = parsed;
126
+ } catch { throw new Error('manifest.json: invalid manifest'); }
127
+ const paths = new Set<string>();
128
+ let previous = '';
129
+ for (const entry of manifest) {
130
+ if (entry === null || typeof entry !== 'object' || typeof entry.path !== 'string'
131
+ || !safePath(entry.path) || entry.path <= previous
132
+ || ['manifest.json', 'identity.json'].includes(entry.path)
133
+ || Object.keys(entry).sort().join(',') !== 'bytes,path,sha256'
134
+ || !/^[a-f0-9]{64}$/.test(entry.sha256) || !Number.isSafeInteger(entry.bytes) || entry.bytes < 0) {
135
+ throw new Error('manifest.json: invalid, duplicate, or unsorted entry');
136
+ }
137
+ previous = entry.path;
138
+ paths.add(entry.path);
139
+ const bytes = await regularFile(root, entry.path);
140
+ if (bytes.length !== entry.bytes || sha256(bytes) !== entry.sha256) {
141
+ throw new Error(`${entry.path}: sha256 or byte count mismatch`);
142
+ }
143
+ }
144
+ for (const required of ['spec.canonical.json', 'preflight.json', 'lockfile.json']) {
145
+ if (!paths.has(required)) throw new Error(`${required}: missing manifest entry`);
146
+ }
147
+ const canonical = canonicalize(manifest);
148
+ if (raw !== canonical) throw new Error('manifest.json: noncanonical bytes');
149
+ const digest = sha256(canonical);
150
+ const matchesDigest = expectedDigest === undefined
151
+ ? basename(root).endsWith(`@sha256:${digest}`) : digest === expectedDigest;
152
+ if (!matchesDigest) throw new Error('manifest.json: directory digest mismatch');
153
+ await rejectExtras(root, '', new Set([...paths, 'manifest.json', 'identity.json']));
154
+ try {
155
+ const identity = JSON.parse((await regularFile(root, 'identity.json')).toString('utf8'));
156
+ const publicKey = Buffer.from(identity.pubkey_b64, 'base64');
157
+ if (identity.algorithm !== 'ed25519' || publicKey.length !== 32
158
+ || publicKey.toString('base64') !== identity.pubkey_b64
159
+ || identity.keyid !== sha256(publicKey).slice(0, 16)
160
+ || !/^[a-f0-9]{128}$/.test(identity.signature_hex)) throw new Error('invalid identity');
161
+ const key = createPublicKey({
162
+ key: Buffer.concat([Buffer.from('302a300506032b6570032100', 'hex'), publicKey]), format: 'der', type: 'spki',
163
+ });
164
+ if (!verify(null, Buffer.from(digest, 'hex'), key, Buffer.from(identity.signature_hex, 'hex'))) {
165
+ throw new Error('signature mismatch');
166
+ }
167
+ } catch (error) {
168
+ throw new Error(`identity.json: ${error instanceof Error ? error.message : 'signature verification failed'}`);
169
+ }
170
+ return digest;
171
+ }
172
+
173
+ async function rejectExtras(root: string, prefix: string, paths: Set<string>): Promise<void> {
174
+ for (const entry of await readdir(join(root, prefix), { withFileTypes: true })) {
175
+ const path = prefix + entry.name;
176
+ if (entry.isDirectory() && [...paths].some(file => file.startsWith(`${path}/`))) {
177
+ await rejectExtras(root, `${path}/`, paths);
178
+ } else if (!entry.isFile() || !paths.has(path)) {
179
+ throw new Error(`${path}: unlisted file or unsupported file type`);
180
+ }
181
+ }
182
+ }
@@ -0,0 +1,138 @@
1
+ import type { ChildProcess } from 'node:child_process';
2
+
3
+ /**
4
+ * Grace between the graceful signal and the force kill. A tree that ignores
5
+ * `SIGTERM` still has to go: it is holding the inherited stdio pipes that keep
6
+ * the `flows run` event loop alive long after the step itself has settled.
7
+ */
8
+ export const FORCE_KILL_DELAY_MS = 1_000;
9
+
10
+ /**
11
+ * The one stop path for a spawned agent process.
12
+ *
13
+ * There are three stops — lease abort, execution timeout, and protocol
14
+ * `terminate` — and the decision they share is whether a signal must address
15
+ * the process GROUP or the direct child. That decision used to be made at each
16
+ * call site, which meant it was made once and omitted twice: only abort killed
17
+ * the group, so a timeout or a protocol violation left the grandchildren alive
18
+ * holding the pipes they inherited. The step Promise settled; `flows run` never
19
+ * exited, because those pipe handles stay open and referenced.
20
+ *
21
+ * So the decision lives here instead, bound once at the spawn site. A call site
22
+ * chooses only the FORCE of the stop, never its REACH — and, per
23
+ * {@link ChildStop.maySettleOnChildExit}, never gets to decide on its own that
24
+ * a stop is finished.
25
+ */
26
+ export interface ChildStop {
27
+ /** Stop the tree now, unconditionally. */
28
+ kill(): void;
29
+ /** Ask the tree to stop, then force whatever is still alive. */
30
+ terminate(): void;
31
+ /**
32
+ * Answer the only question a child-level event can raise: the direct child is
33
+ * gone — may the session settle?
34
+ *
35
+ * INVARIANT: a session may not settle until either the process group is
36
+ * confirmed dead or the escalation has actually run.
37
+ *
38
+ * `'close'` and `'error'` are evidence about the CHILD, never about the
39
+ * group. A descendant that ignores `SIGTERM` and inherited none of the
40
+ * wrapper's stdio emits exactly those events while it is still running, so no
41
+ * call site may read one as a dead tree. This is therefore the only place a
42
+ * pending escalation may be dropped for any reason other than a forced kill,
43
+ * and it drops one only after asking the group whether it is empty.
44
+ *
45
+ * Returns true when settling is safe: no escalation is armed, or the group is
46
+ * confirmed gone and the now-pointless escalation has been dropped here.
47
+ * Returns false when an escalation is armed over a group that still answers —
48
+ * the caller must then leave the settle to that escalation's own deadline.
49
+ */
50
+ maySettleOnChildExit(): boolean;
51
+ }
52
+
53
+ /**
54
+ * Whether a stop can reach descendants at all. It can only when the spawn asked
55
+ * for a process group of its own, which needs POSIX and is only worth the
56
+ * detach on the lease-bound path. Callers pass this same value to `spawn`'s
57
+ * `detached` and to {@link childStop}, so the spawn flag and the stop strategy
58
+ * cannot drift apart.
59
+ */
60
+ export function ownsProcessGroup(signal: AbortSignal | undefined): boolean {
61
+ return signal !== undefined && process.platform !== 'win32';
62
+ }
63
+
64
+ export function childStop(
65
+ child: ChildProcess,
66
+ ownsGroup: boolean,
67
+ forceKillDelayMs: number = FORCE_KILL_DELAY_MS,
68
+ ): ChildStop {
69
+ // Pinned at the spawn rather than read per signal. Every interesting use of
70
+ // this id happens AFTER the direct child has been reaped — the escalation
71
+ // fires a second later, and the group probe runs on `'close'` — so reading
72
+ // `child.pid` there would be reading a field the runtime owns and is free to
73
+ // clear. The group keeps this id for as long as any member of it is alive,
74
+ // which is exactly the window both of those need to address.
75
+ const pid = child.pid;
76
+ let forceTimer: NodeJS.Timeout | undefined;
77
+ const cancel = (): void => {
78
+ if (forceTimer !== undefined) clearTimeout(forceTimer);
79
+ forceTimer = undefined;
80
+ };
81
+ /** Whether anything is still in the group. See `maySettleOnChildExit`. */
82
+ const groupAnswers = (): boolean => {
83
+ // With no group of our own a stop never reached past the direct child, so
84
+ // that child's exit IS the whole of our reach and there is nothing left to
85
+ // ask about.
86
+ if (!ownsGroup || pid === undefined) return false;
87
+ try {
88
+ process.kill(-pid, 0);
89
+ return true;
90
+ } catch (error) {
91
+ // Only `ESRCH` proves the group is empty. `EPERM` proves the opposite —
92
+ // something is in there that we may not signal — and any other errno
93
+ // proves nothing at all, so both must read as alive: an unproven group is
94
+ // not a reason to spare a survivor.
95
+ return (error as NodeJS.ErrnoException).code !== 'ESRCH';
96
+ }
97
+ };
98
+ const signalTree = (name: NodeJS.Signals): void => {
99
+ if (ownsGroup && pid !== undefined) {
100
+ // `-pid` addresses the group this detached child leads, which is every
101
+ // descendant that has not left it. It throws only once the whole group
102
+ // is gone — the outcome we were asking for — so fall through and let the
103
+ // direct-child signal report on a child that never became a leader.
104
+ try {
105
+ process.kill(-pid, name);
106
+ return;
107
+ } catch { /* the group is gone, or we never led one */ }
108
+ }
109
+ child.kill(name);
110
+ };
111
+ return {
112
+ kill: (): void => {
113
+ cancel();
114
+ signalTree('SIGKILL');
115
+ },
116
+ terminate: (): void => {
117
+ cancel();
118
+ signalTree('SIGTERM');
119
+ forceTimer = setTimeout(() => {
120
+ forceTimer = undefined;
121
+ signalTree('SIGKILL');
122
+ }, forceKillDelayMs);
123
+ // Deliberately REFERENCED, unlike every other timer we arm. The survivor
124
+ // this escalation exists for is the one that ignored `SIGTERM` and holds
125
+ // none of our stdio: nothing it does keeps our loop alive, so an unref'd
126
+ // escalation would be dropped by the drain in precisely the case it was
127
+ // armed for. The cost is bounded by `forceKillDelayMs`, is paid only
128
+ // after a stop was actually issued, and is refunded the moment
129
+ // `maySettleOnChildExit` confirms the group is empty.
130
+ },
131
+ maySettleOnChildExit: (): boolean => {
132
+ if (forceTimer === undefined) return true;
133
+ if (groupAnswers()) return false;
134
+ cancel();
135
+ return true;
136
+ },
137
+ };
138
+ }
package/src/cli/add.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import type { CliIo } from '../cli.js';
5
+ import { findPluginProject, probePlugin, readPlugin } from '../plugin-loader.js';
6
+ import { PluginError, pluginPackageName } from '../plugin-manifest.js';
7
+
8
+ export async function addPlugin(name: string, io: CliIo, options: {
9
+ cwd?: string;
10
+ install?: (packageName: string, root: string) => void;
11
+ } = {}): Promise<0 | 2> {
12
+ try {
13
+ const packageName = pluginPackageName(name);
14
+ const root = findPluginProject(options.cwd ?? process.cwd());
15
+ if (!root) throw new PluginError('plugin_manifest_invalid', 'flows add requires a project with flows.json.');
16
+ const configPath = join(root, 'flows.json');
17
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
18
+ if (!config || Array.isArray(config) || typeof config !== 'object' || (config.plugins !== undefined && (!Array.isArray(config.plugins) || !config.plugins.every((p: unknown) => typeof p === 'string')))) throw new PluginError('plugin_manifest_invalid', 'Invalid flows.json plugins list.');
19
+ const tsPath = join(root, 'tsconfig.json');
20
+ const tsconfig = existsSync(tsPath) ? { config: JSON.parse(readFileSync(tsPath, 'utf8')) } : { config: {} };
21
+ if (!tsconfig.config || typeof tsconfig.config !== 'object' || Array.isArray(tsconfig.config) || (tsconfig.config.include !== undefined && (!Array.isArray(tsconfig.config.include) || !tsconfig.config.include.every((p: unknown) => typeof p === 'string')))) throw new PluginError('plugin_manifest_invalid', 'Invalid tsconfig.json include list.');
22
+ try {
23
+ (options.install ?? ((pkg, cwd) => { execFileSync('npm', ['install', '--save', pkg], { cwd, encoding: 'utf8', stdio: 'pipe' }); }))(packageName, root);
24
+ } catch (error) {
25
+ const message = String((error as { stderr?: unknown }).stderr ?? error);
26
+ throw new PluginError(/E404|404 Not Found/.test(message) ? 'plugin_unknown' : 'plugin_install_failed', `Could not install ${packageName}.`);
27
+ }
28
+ const plugin = readPlugin(join(root, 'node_modules', packageName), packageName);
29
+ await probePlugin(plugin);
30
+ if (!existsSync(join(plugin.directory, 'src/index.js'))) throw new PluginError('plugin_manifest_invalid', 'Plugin requires src/index.js.');
31
+ const declaration = `node_modules/${packageName}/flows-plugin.d.ts`;
32
+ if (existsSync(join(root, declaration))) {
33
+ tsconfig.config.include = [...new Set([...(tsconfig.config.include ?? ['**/*']), declaration])];
34
+ writeFileSync(tsPath, `${JSON.stringify(tsconfig.config, null, 2)}\n`);
35
+ }
36
+ config.plugins = [...new Set([...(config.plugins ?? []), packageName])];
37
+ writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
38
+ io.stdout(`Added ${packageName}`);
39
+ return 0;
40
+ } catch (error) {
41
+ const refusal = error instanceof PluginError ? error : new PluginError('plugin_manifest_invalid', (error as Error).message);
42
+ io.stderr(`REFUSED [${refusal.code}] ${refusal.message}`);
43
+ return 2;
44
+ }
45
+ }