@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,147 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { deflateRawSync } from 'node:zlib';
4
+ import { bindingDependencies } from './input-binding.js';
5
+ import { isNamedGate, regexFlags } from './named-gates.js';
6
+ import type { NamedDataGate, StepSpec, VerificationSpec } from './spec.js';
7
+
8
+ const quote = (text: string): string => `'${text.replaceAll("'", "'\\''")}'`;
9
+ let re2Source: string | undefined;
10
+
11
+ /** Embed the pinned engine in the command: execution does not resolve npm or SDK paths. */
12
+ function embeddedRE2(): string {
13
+ re2Source ??= deflateRawSync(readFileSync(createRequire(import.meta.url).resolve('re2js'))).toString('base64');
14
+ return `const re2={};new Function('exports',require('node:zlib').inflateRawSync(Buffer.from(${JSON.stringify(re2Source)},'base64')).toString())(re2);`;
15
+ }
16
+
17
+ /**
18
+ * A gate is a nested deterministic step in the same journal/DAG. Keep the
19
+ * producer's identity/output, and add a barrier to every dependent (including
20
+ * implicit input edges). Internal bindings select the whole envelope so no
21
+ * invented output-path schema is needed. Missing paths fail in the gate.
22
+ */
23
+ export function lowerNamedGates(steps: readonly StepSpec[]): StepSpec[] {
24
+ const used = new Set(steps.map(step => step.id));
25
+ const barriers = new Map<string, string>();
26
+ for (const step of steps) {
27
+ if (!isNamedGate(step.verification)) continue;
28
+ let id = `${step.id}.gate`;
29
+ while (used.has(id)) id += '.gate';
30
+ used.add(id);
31
+ barriers.set(step.id, id);
32
+ }
33
+ if (barriers.size === 0) return [...steps];
34
+ const output: StepSpec[] = [];
35
+ for (const step of steps) {
36
+ const dependencies = [...new Set([...(step.dependsOn ?? []), ...bindingDependencies(step.input)])];
37
+ const dependsOn = [...new Set([...dependencies, ...dependencies.flatMap(id => barriers.get(id) ?? [])])];
38
+ const producer = dependsOn.length ? { ...step, dependsOn } : step;
39
+ const gate = step.verification;
40
+ if (!isNamedGate(gate)) {
41
+ output.push(producer);
42
+ continue;
43
+ }
44
+ // The original output is still checked for a successful process/worker
45
+ // completion. This schema permits an internal whole-output binding.
46
+ output.push({ ...producer, verification: { type: 'json_schema', schema: true } });
47
+ const input = {
48
+ output: { step: step.id },
49
+ ...(gate.type === 'references_input' ? { reference: step.input![gate.input_key]! } : {}),
50
+ };
51
+ output.push({
52
+ id: barriers.get(step.id)!, type: 'deterministic',
53
+ dependsOn: [...new Set([step.id, ...bindingDependencies(input)])], input,
54
+ command: gateCommand(gate, step.type === 'deterministic'),
55
+ verification: gateVerification(gate), maxIterations: step.maxIterations ?? 1,
56
+ ...(step.requirements === undefined ? {} : { requirements: step.requirements }),
57
+ });
58
+ }
59
+ return output;
60
+ }
61
+
62
+ function gateCommand(gate: NamedDataGate, deterministic: boolean): string {
63
+ const path = gate.type === 'subprocess_gate' ? gate.from_output
64
+ : gate.type === 'word_count_bounds' ? undefined : gate.in_output_at;
65
+ // Only compiler-owned code is serialized. Author strings are JSON literals;
66
+ // upstream output travels exclusively through FLOWS_INPUT, never shell text.
67
+ const setup = `const cp=require('node:child_process');
68
+ const input=JSON.parse(process.env.FLOWS_INPUT);
69
+ let value=input.output;
70
+ const path=${JSON.stringify(path ?? null)};
71
+ if(path!==null){for(const key of path){
72
+ if(value===null||typeof value!=='object'||!Object.hasOwn(value,key)||
73
+ (typeof key==='number'?!Array.isArray(value):Array.isArray(value)))process.exit(1);
74
+ value=value[key];
75
+ }}else if(${deterministic})value=value.stdout_tail;
76
+ const text=typeof value==='string'?value:JSON.stringify(value);
77
+ if(typeof text!=='string')process.exit(1);
78
+ `;
79
+ let body: string;
80
+ switch (gate.type) {
81
+ case 'references_input':
82
+ body = `const reference=input.reference;
83
+ if(typeof reference!=='string'||reference.length===0||!text.includes(reference))process.exit(1);
84
+ process.stdout.write('references_input:pass');`;
85
+ break;
86
+ case 'subprocess_gate':
87
+ body = `if(text.includes('\\0'))process.exit(1);
88
+ const result=cp.spawnSync('/bin/sh',['-c',${JSON.stringify(gate.command)}],{
89
+ env:{...process.env,INPUT:text},stdio:'inherit'});
90
+ process.exit(result.status===0?0:1);`;
91
+ break;
92
+ case 'word_count_bounds':
93
+ body = `const result=cp.spawnSync('wc',['-w'],{input:text,encoding:'utf8',env:{...process.env,LC_ALL:'C'}});
94
+ if(result.status!==0)process.exit(1);
95
+ const count=result.stdout.trim();
96
+ if(!/^[0-9]+$/.test(count))process.exit(1);
97
+ process.stdout.write(BigInt(count).toString());`;
98
+ break;
99
+ case 'regex_match':
100
+ body = `${embeddedRE2()}
101
+ process.exit(re2.RE2JS.compile(${JSON.stringify(gate.pattern)},${regexFlags(gate.flags ?? '')}).matcher(text).find()?0:1);`;
102
+ break;
103
+ }
104
+ return `node -e ${quote(setup + body)}`;
105
+ }
106
+
107
+ function gateVerification(gate: NamedDataGate): VerificationSpec {
108
+ if (gate.type === 'references_input') {
109
+ // output_contains cannot carry a runtime binding. The command resolves and
110
+ // checks the literal substring, then emits this fixed, unforgeable receipt.
111
+ return { type: 'output_contains', value: 'references_input:pass' };
112
+ }
113
+ if (gate.type === 'word_count_bounds') {
114
+ // Deterministic output is an envelope with STRING stdout, not parsed JSON.
115
+ // A bounded decimal language enforces the same inclusive numeric range
116
+ // with the existing json_schema primitive and retains the actual count.
117
+ return { type: 'json_schema', schema: {
118
+ type: 'object', required: ['stdout_tail'], properties: {
119
+ stdout_tail: { type: 'string', pattern: decimalRange(gate.min ?? 0, gate.max ?? Number.MAX_SAFE_INTEGER) },
120
+ },
121
+ } };
122
+ }
123
+ return { type: 'exit_code' };
124
+ }
125
+
126
+ /** Compact decimal range, bounded by 16 digits rather than by range width. */
127
+ function decimalRange(min: number, max: number): string {
128
+ const patterns: string[] = [];
129
+ function between(low: string, high: string, prefix: string): void {
130
+ if (low === high) { patterns.push(prefix + low); return; }
131
+ if (/^0+$/.test(low) && /^9+$/.test(high)) {
132
+ patterns.push(`${prefix}[0-9]{${low.length}}`); return;
133
+ }
134
+ const a = Number(low[0]), b = Number(high[0]);
135
+ if (a === b) { between(low.slice(1), high.slice(1), prefix + a); return; }
136
+ const rest = low.length - 1;
137
+ between(low.slice(1), '9'.repeat(rest), prefix + a);
138
+ if (a + 1 <= b - 1) patterns.push(`${prefix}[${a + 1}-${b - 1}]${rest ? `[0-9]{${rest}}` : ''}`);
139
+ between('0'.repeat(rest), high.slice(1), prefix + b);
140
+ }
141
+ for (let digits = String(min).length; digits <= String(max).length; digits++) {
142
+ const low = digits === String(min).length ? String(min) : '1' + '0'.repeat(digits - 1);
143
+ const high = digits === String(max).length ? String(max) : '9'.repeat(digits);
144
+ between(low, high, '');
145
+ }
146
+ return `^(?:${patterns.join('|')})$`;
147
+ }
@@ -0,0 +1,76 @@
1
+ import { RE2JS } from 're2js';
2
+ import type { NamedDataGate, VerificationSpec } from './spec.js';
3
+
4
+ export const NAMED_GATE_FAILURE_KINDS = [
5
+ 'unknown_gate_kind', 'gate_pattern_invalid', 'gate_command_missing', 'gate_bound_invalid',
6
+ ] as const;
7
+ export type NamedGateFailureKind = typeof NAMED_GATE_FAILURE_KINDS[number];
8
+
9
+ export const NAMED_GATE_KEYS: Record<NamedDataGate['type'], readonly string[]> = {
10
+ references_input: ['type', 'input_key', 'in_output_at'],
11
+ subprocess_gate: ['type', 'command', 'from_output'],
12
+ word_count_bounds: ['type', 'min', 'max'],
13
+ regex_match: ['type', 'pattern', 'in_output_at', 'flags'],
14
+ };
15
+
16
+ export function isNamedGate(gate: VerificationSpec | undefined): gate is NamedDataGate {
17
+ return gate !== undefined && Object.hasOwn(NAMED_GATE_KEYS, gate.type);
18
+ }
19
+
20
+ export function regexFlags(flags: string): number {
21
+ return (flags.includes('i') ? RE2JS.CASE_INSENSITIVE : 0)
22
+ | (flags.includes('m') ? RE2JS.MULTILINE : 0)
23
+ | (flags.includes('s') ? RE2JS.DOTALL : 0);
24
+ }
25
+
26
+ /** Called only on snapshotted data, including by the public validator. */
27
+ export function namedGateErrors(gate: Record<string, unknown>, input: unknown, at: string): string[] {
28
+ const errors: string[] = [];
29
+ for (const key of ['in_output_at', 'from_output']) {
30
+ const path = gate[key];
31
+ if (path !== undefined && (!Array.isArray(path) || !path.every(segment =>
32
+ typeof segment === 'string' || (Number.isSafeInteger(segment) && (segment as number) >= 0)))) {
33
+ errors.push(`${at}.${key}: expected an array of object keys or non-negative integer indices`);
34
+ }
35
+ }
36
+ switch (gate.type) {
37
+ case 'references_input':
38
+ if (typeof gate.input_key !== 'string' || !gate.input_key.trim()
39
+ || input === null || typeof input !== 'object' || !Object.hasOwn(input, gate.input_key)) {
40
+ errors.push(`${at}.input_key: must name a declared input binding`);
41
+ }
42
+ break;
43
+ case 'subprocess_gate':
44
+ if (typeof gate.command !== 'string' || !gate.command.trim() || gate.command.includes('\0')) {
45
+ errors.push(`${at}.command: gate_command_missing: expected a non-empty shell command`);
46
+ }
47
+ break;
48
+ case 'word_count_bounds': {
49
+ const { min, max } = gate;
50
+ if ([min, max].some(n => n !== undefined && (!Number.isSafeInteger(n) || (n as number) < 0))
51
+ || (typeof min === 'number' && typeof max === 'number' && min > max)) {
52
+ errors.push(`${at}: gate_bound_invalid: min and max must be non-negative safe integers with min <= max`);
53
+ }
54
+ break;
55
+ }
56
+ case 'regex_match':
57
+ try {
58
+ if (typeof gate.pattern !== 'string' || gate.pattern.length > 8192) {
59
+ throw new Error('pattern must be a string of at most 8192 characters');
60
+ }
61
+ const flags = gate.flags ?? '';
62
+ if (typeof flags !== 'string' || !/^(?!.*(.).*\1)[ims]*$/.test(flags)) {
63
+ throw new Error('flags must be a non-repeating subset of i, m, s');
64
+ }
65
+ RE2JS.compile(gate.pattern, regexFlags(flags));
66
+ } catch (error) {
67
+ errors.push(`${at}: gate_pattern_invalid: ${error instanceof Error ? error.message : 'invalid RE2 pattern'}`);
68
+ }
69
+ break;
70
+ }
71
+ return errors;
72
+ }
73
+
74
+ export function namedGateFailure(errors: readonly string[]): NamedGateFailureKind | undefined {
75
+ return NAMED_GATE_FAILURE_KINDS.find(kind => errors.some(error => error.includes(`${kind}:`)));
76
+ }
@@ -0,0 +1,368 @@
1
+ /**
2
+ * Observer-link support for `flows run`: mint a scoped read-only observer token
3
+ * from a Relaycast workspace admin key, then print
4
+ * `Observer: <url>?key=<ot_live_...>` on stdout so an operator can drop into the
5
+ * Relaycast observer dashboard with the live-stream credential pre-filled.
6
+ *
7
+ * The workspace admin key (`rk_live_*`) is refused by the realtime endpoint —
8
+ * only an `ot_live_*` observer token with `stream:read` may open the workspace
9
+ * stream. So we do the same thing the Relaycast observer dashboard does when a
10
+ * workspace admin logs in with their admin key: mint a fresh, uniquely-named,
11
+ * scoped, short-lived observer token on their behalf and hand *that* to the
12
+ * URL. The workspace admin key never leaves the operator's machine.
13
+ *
14
+ * Everything here is best-effort. A missing workspace key means "no observer
15
+ * link, silently" — this is a parity feature relaying v1's convenience, not a
16
+ * requirement of a local run. A mint failure emits a labeled diagnostic to
17
+ * stderr and continues; a run must never fail because the observer link failed.
18
+ */
19
+
20
+ import { readFileSync } from 'node:fs';
21
+ import { homedir } from 'node:os';
22
+ import { join } from 'node:path';
23
+
24
+ /** Scopes the minted token carries. Mirrors the observer dashboard's set. */
25
+ const OBSERVER_SCOPES = [
26
+ 'stream:read',
27
+ 'messages:read',
28
+ 'threads:read',
29
+ 'dms:read',
30
+ 'channels:read',
31
+ 'search:read',
32
+ 'agents:read',
33
+ 'nodes:read',
34
+ 'deliveries:read',
35
+ 'activity:read',
36
+ 'files:read',
37
+ 'reactions:read',
38
+ ] as const;
39
+
40
+ /** Token lifetime for a `flows run` session. 24h is well past the longest run. */
41
+ const OBSERVER_TOKEN_TTL_MS = 60 * 60 * 24 * 1000;
42
+
43
+ /**
44
+ * Default hosts for the two axes of the observer link. They are DIFFERENT
45
+ * subdomains and must not be conflated (empirically verified 2026-09-10):
46
+ *
47
+ * - Mint API: `POST cast.agentrelay.com/v1/observer-tokens`. `agentrelay.com`
48
+ * returns 404 for this path. Overridable via `RELAYCAST_API_URL`.
49
+ * - Observer dashboard: `agentrelay.com/observer?key=<ot_live_...>`.
50
+ * `cast.agentrelay.com/observer` returns 404. Overridable via
51
+ * `RELAYCAST_DASHBOARD_URL`.
52
+ *
53
+ * An earlier fix collapsed both to `cast.agentrelay.com` and closed #278 for
54
+ * the mint half only; a demo verification then landed on the 404 dashboard
55
+ * URL. Keep the two constants distinct.
56
+ */
57
+ const DEFAULT_RELAYCAST_MINT_URL = 'https://cast.agentrelay.com';
58
+ const DEFAULT_RELAYCAST_DASHBOARD_URL = 'https://agentrelay.com';
59
+
60
+ /** Bounded so a stalled Relaycast API cannot delay the RUN summary. */
61
+ const MINT_TIMEOUT_MS = 5_000;
62
+
63
+ export interface ObserverLinkEnv {
64
+ workspaceKey?: string;
65
+ /** Base URL for the mint API (`POST /v1/observer-tokens`). */
66
+ baseUrl?: string;
67
+ /** Base URL for the observer dashboard the emitted link points at. */
68
+ dashboardUrl?: string;
69
+ /** `FLOWS_NO_OBSERVER=1` suppresses the mint even when a key is present. */
70
+ suppressed: boolean;
71
+ }
72
+
73
+ /**
74
+ * Path to the `agent-relay` local workspace-key store. Structure:
75
+ * `{ active: string, workspaces: { [name]: { key: string } } }`. Written
76
+ * by `agent-relay workspace set_key` / `agent-relay workspace join`; the
77
+ * canonical file the `@agent-relay/cloud` package reads via
78
+ * `resolveActiveWorkspaceKey`. Verified against
79
+ * `packages/cloud/src/workspace-store.ts` in `AgentWorkforce/relay`.
80
+ *
81
+ * Respects `AGENT_RELAY_HOME` so a caller can point at a different store
82
+ * for tests without touching the real one. Falls back to
83
+ * `~/.agentworkforce/relay/workspaces.json` -- the same default that
84
+ * package uses.
85
+ */
86
+ export function agentRelayWorkspaceStorePath(
87
+ env: NodeJS.ProcessEnv = process.env,
88
+ ): string {
89
+ const dir = env['AGENT_RELAY_HOME'] ?? join(homedir(), '.agentworkforce/relay');
90
+ return join(dir, 'workspaces.json');
91
+ }
92
+
93
+ /**
94
+ * Shape of the `workspaces.json` file. Only the fields we depend on are
95
+ * declared; other keys are ignored. Kept structural so a type mismatch on
96
+ * an untrusted disk read falls into the "no cloud key readable" branch
97
+ * rather than throwing.
98
+ */
99
+ interface AgentRelayWorkspaceStore {
100
+ active?: string;
101
+ workspaces?: Record<string, { key?: string } | undefined>;
102
+ }
103
+
104
+ /**
105
+ * Read the active workspace key from the `agent-relay cloud login` /
106
+ * `agent-relay workspace set_key` store. Returns `undefined` on every
107
+ * failure mode -- file absent, unreadable, malformed JSON, no active
108
+ * workspace, active workspace has no key, key is empty -- so the caller
109
+ * can silently fall through to "no observer link" the same way an unset
110
+ * `RELAYCAST_WORKSPACE_KEY` does.
111
+ *
112
+ * A file-shape mismatch is INTENTIONALLY not a hard error: `agent-relay`
113
+ * may extend this file in future without warning, and refusing a flow
114
+ * because a fallback credential store looked odd would be strictly worse
115
+ * than falling back to the explicit env var (which is the primary path).
116
+ */
117
+ export function readAgentRelayWorkspaceKey(
118
+ env: NodeJS.ProcessEnv = process.env,
119
+ readFile: (path: string) => string = defaultReadFile,
120
+ ): string | undefined {
121
+ let raw: string;
122
+ try {
123
+ raw = readFile(agentRelayWorkspaceStorePath(env));
124
+ } catch {
125
+ return undefined;
126
+ }
127
+ let parsed: AgentRelayWorkspaceStore;
128
+ try {
129
+ parsed = JSON.parse(raw) as AgentRelayWorkspaceStore;
130
+ } catch {
131
+ return undefined;
132
+ }
133
+ if (typeof parsed !== 'object' || parsed === null) return undefined;
134
+ const activeName = parsed.active;
135
+ if (typeof activeName !== 'string' || activeName === '') return undefined;
136
+ const workspaces = parsed.workspaces;
137
+ if (typeof workspaces !== 'object' || workspaces === null) return undefined;
138
+ const entry = workspaces[activeName];
139
+ if (typeof entry !== 'object' || entry === null) return undefined;
140
+ const key = entry.key;
141
+ if (typeof key !== 'string') return undefined;
142
+ const trimmed = key.trim();
143
+ return trimmed === '' ? undefined : trimmed;
144
+ }
145
+
146
+ function defaultReadFile(filePath: string): string {
147
+ return readFileSync(filePath, 'utf8');
148
+ }
149
+
150
+ /**
151
+ * Options controlling `resolveObserverLinkEnv`'s fallback behavior. The
152
+ * `readWorkspaceKey` seam is what the tests inject to simulate presence,
153
+ * absence, and malformed shapes of the on-disk store without touching the
154
+ * real home directory.
155
+ */
156
+ export interface ResolveObserverLinkEnvOptions {
157
+ readWorkspaceKey?: (env: NodeJS.ProcessEnv) => string | undefined;
158
+ }
159
+
160
+ /**
161
+ * Env-first observer link resolution with an `agent-relay` cloud fallback.
162
+ *
163
+ * Priority (from the task spec, explicit beats implicit):
164
+ * 1. `RELAYCAST_WORKSPACE_KEY` if set and non-empty.
165
+ * 2. Active workspace key from `~/.agentworkforce/relay/workspaces.json`
166
+ * (populated by `agent-relay cloud login` + `agent-relay workspace
167
+ * set_key`), when readable.
168
+ * 3. Nothing -- silent skip, same as an unset primary env var.
169
+ *
170
+ * `FLOWS_NO_OBSERVER=1` still suppresses regardless of source.
171
+ */
172
+ export function resolveObserverLinkEnv(
173
+ env: NodeJS.ProcessEnv = process.env,
174
+ options: ResolveObserverLinkEnvOptions = {},
175
+ ): ObserverLinkEnv {
176
+ const primary = readObserverLinkEnv(env);
177
+ if (primary.workspaceKey !== undefined || primary.suppressed) {
178
+ return primary;
179
+ }
180
+ const readWorkspaceKey = options.readWorkspaceKey ?? readAgentRelayWorkspaceKey;
181
+ const fallback = readWorkspaceKey(env);
182
+ if (fallback === undefined) return primary;
183
+ return {
184
+ ...primary,
185
+ workspaceKey: fallback,
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Read the observer-link config from the environment. Both trims and
191
+ * emptiness-checks the workspace key so a `RELAYCAST_WORKSPACE_KEY=` with no
192
+ * value reads the same as unset (no observer link, silently).
193
+ */
194
+ export function readObserverLinkEnv(
195
+ env: NodeJS.ProcessEnv = process.env,
196
+ ): ObserverLinkEnv {
197
+ const rawKey = env['RELAYCAST_WORKSPACE_KEY'];
198
+ const workspaceKey = typeof rawKey === 'string' ? rawKey.trim() : '';
199
+ const rawApi = env['RELAYCAST_API_URL'];
200
+ const baseUrl = typeof rawApi === 'string' && rawApi.trim() !== ''
201
+ ? rawApi.trim()
202
+ : undefined;
203
+ const rawDashboard = env['RELAYCAST_DASHBOARD_URL'];
204
+ const dashboardUrl = typeof rawDashboard === 'string' && rawDashboard.trim() !== ''
205
+ ? rawDashboard.trim()
206
+ : undefined;
207
+ return {
208
+ ...(workspaceKey !== '' ? { workspaceKey } : {}),
209
+ ...(baseUrl !== undefined ? { baseUrl } : {}),
210
+ ...(dashboardUrl !== undefined ? { dashboardUrl } : {}),
211
+ suppressed: env['FLOWS_NO_OBSERVER'] === '1',
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Minimal `fetch` shape we depend on. Kept structural so tests can inject a
217
+ * mock without pulling in a fetch replacement library.
218
+ */
219
+ export type ObserverFetch = (
220
+ url: string,
221
+ init: {
222
+ method: string;
223
+ headers: Record<string, string>;
224
+ body: string;
225
+ signal?: AbortSignal;
226
+ },
227
+ ) => Promise<{
228
+ ok: boolean;
229
+ status: number;
230
+ json: () => Promise<unknown>;
231
+ }>;
232
+
233
+ export interface MintObserverOptions {
234
+ workspaceKey: string;
235
+ /** Base URL of the mint API (default `https://cast.agentrelay.com`). */
236
+ baseUrl?: string;
237
+ /** Base URL of the observer dashboard the returned URL points at
238
+ * (default `https://agentrelay.com`). Separate axis from `baseUrl` — the
239
+ * mint API lives on a different subdomain from the dashboard. */
240
+ dashboardUrl?: string;
241
+ fetch?: ObserverFetch;
242
+ now?: () => number;
243
+ /** Called for the token's uniquely-suffixed name; injectable for tests. */
244
+ uuid?: () => string;
245
+ signal?: AbortSignal;
246
+ timeoutMs?: number;
247
+ }
248
+
249
+ export interface MintObserverOutcome {
250
+ observerUrl?: string;
251
+ /**
252
+ * A short diagnostic to log when mint failed. `undefined` on success or on
253
+ * "no observer link is possible" (missing key/suppressed) so the caller can
254
+ * distinguish silent-skip from "warn about the miss".
255
+ */
256
+ warning?: string;
257
+ }
258
+
259
+ /**
260
+ * Mint an `ot_live_*` observer token via `POST /v1/observer-tokens` and return
261
+ * the observer dashboard URL that pre-fills it. Any failure — 4xx/5xx from the
262
+ * engine, malformed response, network error, timeout — returns a `warning`
263
+ * string and no URL. The caller must not fail the run on `warning`.
264
+ */
265
+ export async function mintObserverUrl(
266
+ options: MintObserverOptions,
267
+ ): Promise<MintObserverOutcome> {
268
+ const doFetch: ObserverFetch = options.fetch ?? ((globalThis as unknown as {
269
+ fetch: ObserverFetch;
270
+ }).fetch);
271
+ if (typeof doFetch !== 'function') {
272
+ return { warning: 'no fetch implementation available' };
273
+ }
274
+
275
+ const rawApi = options.baseUrl ?? DEFAULT_RELAYCAST_MINT_URL;
276
+ const rawDashboard = options.dashboardUrl ?? DEFAULT_RELAYCAST_DASHBOARD_URL;
277
+ let mintUrl: URL;
278
+ let observerBase: URL;
279
+ try {
280
+ mintUrl = new URL('/v1/observer-tokens', rawApi);
281
+ } catch {
282
+ return { warning: `invalid RELAYCAST_API_URL "${rawApi}"` };
283
+ }
284
+ try {
285
+ observerBase = new URL('/observer', rawDashboard);
286
+ } catch {
287
+ return { warning: `invalid RELAYCAST_DASHBOARD_URL "${rawDashboard}"` };
288
+ }
289
+
290
+ const uuid = (options.uuid ?? defaultUuid)();
291
+ const now = options.now ?? (() => Date.now());
292
+ const timeoutMs = options.timeoutMs ?? MINT_TIMEOUT_MS;
293
+ const deadline = AbortSignal.timeout(timeoutMs);
294
+ const signal = options.signal
295
+ ? AbortSignal.any([options.signal, deadline])
296
+ : deadline;
297
+
298
+ const payload = {
299
+ name: `flows-run-${uuid}`,
300
+ description: 'Auto-minted by `flows run` for the observer dashboard link.',
301
+ scopes: OBSERVER_SCOPES,
302
+ filters: { include_dms: true },
303
+ expires_at: new Date(now() + OBSERVER_TOKEN_TTL_MS).toISOString(),
304
+ };
305
+
306
+ let response: Awaited<ReturnType<ObserverFetch>>;
307
+ try {
308
+ response = await doFetch(mintUrl.toString(), {
309
+ method: 'POST',
310
+ headers: {
311
+ Authorization: `Bearer ${options.workspaceKey}`,
312
+ 'Content-Type': 'application/json',
313
+ },
314
+ body: JSON.stringify(payload),
315
+ signal,
316
+ });
317
+ } catch (error) {
318
+ return { warning: `network error: ${errorMessage(error)}` };
319
+ }
320
+
321
+ if (!response.ok) {
322
+ return { warning: `mint API returned HTTP ${response.status}` };
323
+ }
324
+
325
+ let body: unknown;
326
+ try {
327
+ body = await response.json();
328
+ } catch (error) {
329
+ return { warning: `malformed response: ${errorMessage(error)}` };
330
+ }
331
+
332
+ const token = pickToken(body);
333
+ if (token === undefined) {
334
+ return { warning: 'mint API response missing ot_live_ token' };
335
+ }
336
+
337
+ // `URL.searchParams.set` percent-encodes any exotic characters the token
338
+ // could carry. It also collapses the leading `/observer` path correctly.
339
+ observerBase.searchParams.set('key', token);
340
+ return { observerUrl: observerBase.toString() };
341
+ }
342
+
343
+ /**
344
+ * Pick `data.token` from a `POST /v1/observer-tokens` response, refusing any
345
+ * shape that is not a plain `ot_live_*` string. Anything else is treated as
346
+ * "response too weird to trust" and produces no URL.
347
+ */
348
+ function pickToken(body: unknown): string | undefined {
349
+ if (typeof body !== 'object' || body === null) return undefined;
350
+ const data = (body as Record<string, unknown>)['data'];
351
+ if (typeof data !== 'object' || data === null) return undefined;
352
+ const token = (data as Record<string, unknown>)['token'];
353
+ if (typeof token !== 'string' || !token.startsWith('ot_live_')) return undefined;
354
+ return token;
355
+ }
356
+
357
+ function defaultUuid(): string {
358
+ // `crypto.randomUUID()` is available on every Node 20+ target the SDK
359
+ // supports. The token name is not a security surface — it only has to be
360
+ // unique per (workspace, name) — so the fallback is a plain timestamp.
361
+ const c = (globalThis as unknown as { crypto?: { randomUUID?: () => string } }).crypto;
362
+ return c?.randomUUID?.() ?? String(Date.now());
363
+ }
364
+
365
+ function errorMessage(error: unknown): string {
366
+ if (error instanceof Error) return error.message;
367
+ return 'unknown error';
368
+ }
@@ -0,0 +1,88 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { Ajv } from 'ajv';
5
+ import type { Step } from '@relayflows/surface';
6
+ import { snapshotJsonValue } from './json-value.js';
7
+ import { assertSupportedPlugin, PluginError, pluginPackageName, validatePluginManifest, type PluginManifest, type PluginVerb } from './plugin-manifest.js';
8
+
9
+ export interface LoadedPlugin { readonly directory: string; readonly manifest: PluginManifest }
10
+ export function findPluginProject(start: string): string | undefined {
11
+ let current = resolve(start);
12
+ for (;;) {
13
+ if (existsSync(join(current, 'flows.json'))) return current;
14
+ const parent = dirname(current);
15
+ if (parent === current) return undefined;
16
+ current = parent;
17
+ }
18
+ }
19
+ export function readPlugin(directory: string, packageName: string): LoadedPlugin {
20
+ const path = join(directory, 'flows-plugin.json');
21
+ if (!existsSync(path)) throw new PluginError('plugin_manifest_missing', `${packageName} has no flows-plugin.json.`);
22
+ let input: unknown;
23
+ try { input = JSON.parse(readFileSync(path, 'utf8')); }
24
+ catch { throw new PluginError('plugin_manifest_invalid', `${packageName} manifest is unreadable or invalid JSON.`); }
25
+ const manifest = validatePluginManifest(input, packageName);
26
+ assertSupportedPlugin(manifest);
27
+ return Object.freeze({ directory, manifest });
28
+ }
29
+ export async function probePlugin(plugin: LoadedPlugin, env: NodeJS.ProcessEnv = process.env): Promise<void> {
30
+ for (const credential of plugin.manifest.preflight.credentials) {
31
+ if (!env[credential]?.trim()) throw new PluginError('plugin_credential_missing', `${plugin.manifest.name} requires ${credential}.`);
32
+ }
33
+ for (const server of plugin.manifest.preflight.servers) {
34
+ try {
35
+ const response = await fetch(server, { method: 'HEAD', signal: AbortSignal.timeout(5000) });
36
+ await response.body?.cancel();
37
+ if (!response.ok) throw new Error('unsuccessful response');
38
+ } catch { throw new PluginError('plugin_server_unreachable', `${plugin.manifest.name} cannot reach ${server}.`); }
39
+ }
40
+ }
41
+ export async function loadPlugins(start: string): Promise<readonly LoadedPlugin[]> {
42
+ const root = findPluginProject(start);
43
+ if (root === undefined) return Object.freeze([]);
44
+ let config: { plugins?: unknown };
45
+ try { config = JSON.parse(readFileSync(join(root, 'flows.json'), 'utf8')); }
46
+ catch { throw new PluginError('plugin_manifest_invalid', 'Invalid flows.json.'); }
47
+ if (config === null || typeof config !== 'object' || (config.plugins !== undefined && (!Array.isArray(config.plugins) || !config.plugins.every(p => typeof p === 'string')))) {
48
+ throw new PluginError('plugin_manifest_invalid', 'flows.json plugins must be package names.');
49
+ }
50
+ const scope = join(root, 'node_modules/@flows');
51
+ const names = new Set<string>((config.plugins as string[] | undefined)?.map(pluginPackageName));
52
+ if (existsSync(scope)) for (const name of readdirSync(scope).sort()) {
53
+ if (name.startsWith('helper-') && !names.has(`@flows/${name}`)) {
54
+ throw new PluginError('plugin_unlisted', `@flows/${name} is installed but not declared in flows.json plugins. Run flows add ${name}.`);
55
+ }
56
+ }
57
+ const plugins = [...names].sort().map(name => readPlugin(join(root, 'node_modules', name), name));
58
+ const namespaces = new Set<string>();
59
+ for (const plugin of plugins) {
60
+ for (const namespace of new Set(plugin.manifest.verbs.map(v => v.namespace))) {
61
+ if (namespaces.has(namespace)) throw new PluginError('plugin_manifest_invalid', `Duplicate plugin namespace ${namespace}.`);
62
+ namespaces.add(namespace);
63
+ }
64
+ await probePlugin(plugin);
65
+ if (!existsSync(join(plugin.directory, 'src/index.js'))) throw new PluginError('plugin_manifest_invalid', `${plugin.manifest.name} requires src/index.js.`);
66
+ }
67
+ return Object.freeze(plugins);
68
+ }
69
+ export function pluginHelpers(plugins: readonly LoadedPlugin[], invoke: (plugin: LoadedPlugin, verb: PluginVerb, args: unknown) => Step<unknown>): Record<string, unknown> {
70
+ const helpers: Record<string, Record<string, (args: unknown) => Step<unknown>>> = Object.create(null);
71
+ const ajv = new Ajv({ strict: false });
72
+ for (const plugin of plugins) for (const verb of plugin.manifest.verbs) {
73
+ const validate = ajv.compile(verb.args);
74
+ const namespace = helpers[verb.namespace] ??= Object.create(null);
75
+ namespace[verb.method] = args => {
76
+ const snapshot = snapshotJsonValue(args, `f.${verb.namespace}.${verb.method} arguments`);
77
+ if (!validate(snapshot)) throw new PluginError('plugin_manifest_invalid', `Invalid arguments for ${verb.namespace}.${verb.method}: ${ajv.errorsText(validate.errors)}`);
78
+ return invoke(plugin, verb, snapshot);
79
+ };
80
+ }
81
+ for (const helper of Object.values(helpers)) Object.freeze(helper);
82
+ return Object.freeze(helpers);
83
+ }
84
+ export async function invokePlugin(plugin: LoadedPlugin, verb: PluginVerb, args: unknown, idempotencyKey: string): Promise<unknown> {
85
+ const module = await import(pathToFileURL(join(plugin.directory, 'src/index.js')).href);
86
+ if (typeof module.execute !== 'function') throw new Error('plugin_runtime_invalid: expected execute export');
87
+ return snapshotJsonValue(await module.execute(verb.namespace, verb.method, args, Object.freeze({ idempotencyKey })), 'plugin output');
88
+ }