@relayflows/sdk 2.0.42 → 2.0.44

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 (374) hide show
  1. package/dist/adapters/base.d.ts +12 -1
  2. package/dist/adapters/base.d.ts.map +1 -1
  3. package/dist/adapters/base.js +11 -1
  4. package/dist/adapters/base.js.map +1 -1
  5. package/dist/adapters/claude.d.ts.map +1 -1
  6. package/dist/adapters/claude.js +8 -4
  7. package/dist/adapters/claude.js.map +1 -1
  8. package/dist/adapters/codex.d.ts.map +1 -1
  9. package/dist/adapters/codex.js +15 -6
  10. package/dist/adapters/codex.js.map +1 -1
  11. package/dist/agent-resume.d.ts +8 -0
  12. package/dist/agent-resume.d.ts.map +1 -0
  13. package/dist/agent-resume.js +37 -0
  14. package/dist/agent-resume.js.map +1 -0
  15. package/dist/agent-transcript.d.ts.map +1 -1
  16. package/dist/agent-transcript.js +5 -3
  17. package/dist/agent-transcript.js.map +1 -1
  18. package/dist/authored-flow-error.d.ts +10 -2
  19. package/dist/authored-flow-error.d.ts.map +1 -1
  20. package/dist/authored-flow-error.js.map +1 -1
  21. package/dist/authored-flow-executor.d.ts.map +1 -1
  22. package/dist/authored-flow-executor.js +16 -2
  23. package/dist/authored-flow-executor.js.map +1 -1
  24. package/dist/authored-flow-loader.d.ts +6 -0
  25. package/dist/authored-flow-loader.d.ts.map +1 -1
  26. package/dist/authored-flow-loader.js +18 -6
  27. package/dist/authored-flow-loader.js.map +1 -1
  28. package/dist/authored-helper-effect.js +3 -2
  29. package/dist/authored-helper-effect.js.map +1 -1
  30. package/dist/authored-human.d.ts +2 -0
  31. package/dist/authored-human.d.ts.map +1 -1
  32. package/dist/authored-human.js +4 -0
  33. package/dist/authored-human.js.map +1 -1
  34. package/dist/authored-memory.d.ts +47 -2
  35. package/dist/authored-memory.d.ts.map +1 -1
  36. package/dist/authored-memory.js +394 -15
  37. package/dist/authored-memory.js.map +1 -1
  38. package/dist/authored-node-entry.js +6 -1
  39. package/dist/authored-node-entry.js.map +1 -1
  40. package/dist/authored-node-runner.d.ts +1 -1
  41. package/dist/authored-node-runner.d.ts.map +1 -1
  42. package/dist/authored-node-runner.js +6 -3
  43. package/dist/authored-node-runner.js.map +1 -1
  44. package/dist/authored-root-readback.d.ts +21 -0
  45. package/dist/authored-root-readback.d.ts.map +1 -0
  46. package/dist/authored-root-readback.js +90 -0
  47. package/dist/authored-root-readback.js.map +1 -0
  48. package/dist/authored-root.d.ts.map +1 -1
  49. package/dist/authored-root.js +53 -74
  50. package/dist/authored-root.js.map +1 -1
  51. package/dist/authored-step-output.d.ts +2 -0
  52. package/dist/authored-step-output.d.ts.map +1 -1
  53. package/dist/authored-step-output.js +1 -0
  54. package/dist/authored-step-output.js.map +1 -1
  55. package/dist/authored-worker-step.d.ts.map +1 -1
  56. package/dist/authored-worker-step.js +26 -4
  57. package/dist/authored-worker-step.js.map +1 -1
  58. package/dist/cli/check-helper-body.d.ts +2 -2
  59. package/dist/cli/check-helper-body.d.ts.map +1 -1
  60. package/dist/cli/check-helper-body.js +7 -2
  61. package/dist/cli/check-helper-body.js.map +1 -1
  62. package/dist/cli/check-helper-surface.d.ts +8 -0
  63. package/dist/cli/check-helper-surface.d.ts.map +1 -0
  64. package/dist/cli/check-helper-surface.js +37 -0
  65. package/dist/cli/check-helper-surface.js.map +1 -0
  66. package/dist/cli/check-triggers.d.ts +4 -3
  67. package/dist/cli/check-triggers.d.ts.map +1 -1
  68. package/dist/cli/check-triggers.js +7 -3
  69. package/dist/cli/check-triggers.js.map +1 -1
  70. package/dist/cli/check.d.ts +7 -1
  71. package/dist/cli/check.d.ts.map +1 -1
  72. package/dist/cli/check.js +35 -8
  73. package/dist/cli/check.js.map +1 -1
  74. package/dist/cli/cloud-answer.d.ts +9 -0
  75. package/dist/cli/cloud-answer.d.ts.map +1 -0
  76. package/dist/cli/cloud-answer.js +39 -0
  77. package/dist/cli/cloud-answer.js.map +1 -0
  78. package/dist/cli/cloud-connect-cli.d.ts +2 -1
  79. package/dist/cli/cloud-connect-cli.d.ts.map +1 -1
  80. package/dist/cli/cloud-connect-cli.js +10 -2
  81. package/dist/cli/cloud-connect-cli.js.map +1 -1
  82. package/dist/cli/cloud-human.d.ts +7 -0
  83. package/dist/cli/cloud-human.d.ts.map +1 -0
  84. package/dist/cli/cloud-human.js +22 -0
  85. package/dist/cli/cloud-human.js.map +1 -0
  86. package/dist/cli/cloud-live.d.ts +2 -2
  87. package/dist/cli/cloud-live.d.ts.map +1 -1
  88. package/dist/cli/cloud-live.js +9 -2
  89. package/dist/cli/cloud-live.js.map +1 -1
  90. package/dist/cli/cloud-mirror-session.d.ts.map +1 -1
  91. package/dist/cli/cloud-mirror-session.js +3 -2
  92. package/dist/cli/cloud-mirror-session.js.map +1 -1
  93. package/dist/cli/cloud-run.d.ts +1 -1
  94. package/dist/cli/cloud-run.d.ts.map +1 -1
  95. package/dist/cli/cloud-run.js +12 -0
  96. package/dist/cli/cloud-run.js.map +1 -1
  97. package/dist/cli/detached-record.d.ts +22 -0
  98. package/dist/cli/detached-record.d.ts.map +1 -0
  99. package/dist/cli/detached-record.js +57 -0
  100. package/dist/cli/detached-record.js.map +1 -0
  101. package/dist/cli/detached-run.d.ts +28 -0
  102. package/dist/cli/detached-run.d.ts.map +1 -0
  103. package/dist/cli/detached-run.js +122 -0
  104. package/dist/cli/detached-run.js.map +1 -0
  105. package/dist/cli/diagnostic-fold.d.ts +19 -0
  106. package/dist/cli/diagnostic-fold.d.ts.map +1 -0
  107. package/dist/cli/diagnostic-fold.js +19 -0
  108. package/dist/cli/diagnostic-fold.js.map +1 -0
  109. package/dist/cli/direct-run.d.ts.map +1 -1
  110. package/dist/cli/direct-run.js +16 -2
  111. package/dist/cli/direct-run.js.map +1 -1
  112. package/dist/cli/eval.d.ts +22 -0
  113. package/dist/cli/eval.d.ts.map +1 -0
  114. package/dist/cli/eval.js +131 -0
  115. package/dist/cli/eval.js.map +1 -0
  116. package/dist/cli/journal-timeout.d.ts +12 -0
  117. package/dist/cli/journal-timeout.d.ts.map +1 -0
  118. package/dist/cli/journal-timeout.js +42 -0
  119. package/dist/cli/journal-timeout.js.map +1 -0
  120. package/dist/cli/local-agent-remedy.d.ts.map +1 -1
  121. package/dist/cli/local-agent-remedy.js +5 -4
  122. package/dist/cli/local-agent-remedy.js.map +1 -1
  123. package/dist/cli/observer-session.d.ts +1 -0
  124. package/dist/cli/observer-session.d.ts.map +1 -1
  125. package/dist/cli/observer-session.js +8 -1
  126. package/dist/cli/observer-session.js.map +1 -1
  127. package/dist/cli/reuse.d.ts +8 -0
  128. package/dist/cli/reuse.d.ts.map +1 -1
  129. package/dist/cli/reuse.js +24 -0
  130. package/dist/cli/reuse.js.map +1 -1
  131. package/dist/cli/run.d.ts +8 -2
  132. package/dist/cli/run.d.ts.map +1 -1
  133. package/dist/cli/run.js +72 -56
  134. package/dist/cli/run.js.map +1 -1
  135. package/dist/cli/running-step.d.ts +9 -0
  136. package/dist/cli/running-step.d.ts.map +1 -0
  137. package/dist/cli/running-step.js +156 -0
  138. package/dist/cli/running-step.js.map +1 -0
  139. package/dist/cli/status.d.ts.map +1 -1
  140. package/dist/cli/status.js +12 -8
  141. package/dist/cli/status.js.map +1 -1
  142. package/dist/cli-adapter.d.ts +3 -1
  143. package/dist/cli-adapter.d.ts.map +1 -1
  144. package/dist/cli-adapter.js +6 -2
  145. package/dist/cli-adapter.js.map +1 -1
  146. package/dist/cli-commands.d.ts +77 -11
  147. package/dist/cli-commands.d.ts.map +1 -1
  148. package/dist/cli-commands.js +41 -7
  149. package/dist/cli-commands.js.map +1 -1
  150. package/dist/cli-watch.d.ts +6 -2
  151. package/dist/cli-watch.d.ts.map +1 -1
  152. package/dist/cli-watch.js +7 -6
  153. package/dist/cli-watch.js.map +1 -1
  154. package/dist/cli.d.ts +22 -1
  155. package/dist/cli.d.ts.map +1 -1
  156. package/dist/cli.js +154 -50
  157. package/dist/cli.js.map +1 -1
  158. package/dist/cloud-answer.d.ts +35 -0
  159. package/dist/cloud-answer.d.ts.map +1 -0
  160. package/dist/cloud-answer.js +194 -0
  161. package/dist/cloud-answer.js.map +1 -0
  162. package/dist/cloud-auth-store.d.ts +46 -0
  163. package/dist/cloud-auth-store.d.ts.map +1 -0
  164. package/dist/cloud-auth-store.js +171 -0
  165. package/dist/cloud-auth-store.js.map +1 -0
  166. package/dist/cloud-http.d.ts +7 -16
  167. package/dist/cloud-http.d.ts.map +1 -1
  168. package/dist/cloud-http.js +59 -40
  169. package/dist/cloud-http.js.map +1 -1
  170. package/dist/cloud-human-wait.d.ts +10 -0
  171. package/dist/cloud-human-wait.d.ts.map +1 -0
  172. package/dist/cloud-human-wait.js +17 -0
  173. package/dist/cloud-human-wait.js.map +1 -0
  174. package/dist/cloud-mirror-transport.d.ts.map +1 -1
  175. package/dist/cloud-mirror-transport.js +1 -0
  176. package/dist/cloud-mirror-transport.js.map +1 -1
  177. package/dist/cloud-run-record.d.ts +7 -2
  178. package/dist/cloud-run-record.d.ts.map +1 -1
  179. package/dist/cloud-run-record.js +13 -5
  180. package/dist/cloud-run-record.js.map +1 -1
  181. package/dist/cloud-run.d.ts +9 -0
  182. package/dist/cloud-run.d.ts.map +1 -1
  183. package/dist/cloud-run.js +36 -2
  184. package/dist/cloud-run.js.map +1 -1
  185. package/dist/communication/local.d.ts.map +1 -1
  186. package/dist/communication/local.js +6 -1
  187. package/dist/communication/local.js.map +1 -1
  188. package/dist/communication/worker.d.ts.map +1 -1
  189. package/dist/communication/worker.js +12 -2
  190. package/dist/communication/worker.js.map +1 -1
  191. package/dist/compile.d.ts.map +1 -1
  192. package/dist/compile.js +5 -3
  193. package/dist/compile.js.map +1 -1
  194. package/dist/daemon-spec-validation.d.ts +31 -0
  195. package/dist/daemon-spec-validation.d.ts.map +1 -0
  196. package/dist/daemon-spec-validation.js +115 -0
  197. package/dist/daemon-spec-validation.js.map +1 -0
  198. package/dist/failure-kinds.d.ts +6 -4
  199. package/dist/failure-kinds.d.ts.map +1 -1
  200. package/dist/failure-kinds.js +17 -1
  201. package/dist/failure-kinds.js.map +1 -1
  202. package/dist/flow-eval-cloud.d.ts +25 -0
  203. package/dist/flow-eval-cloud.d.ts.map +1 -0
  204. package/dist/flow-eval-cloud.js +108 -0
  205. package/dist/flow-eval-cloud.js.map +1 -0
  206. package/dist/flow-eval-error.d.ts +7 -0
  207. package/dist/flow-eval-error.d.ts.map +1 -0
  208. package/dist/flow-eval-error.js +10 -0
  209. package/dist/flow-eval-error.js.map +1 -0
  210. package/dist/flow-eval-gate.d.ts +15 -0
  211. package/dist/flow-eval-gate.d.ts.map +1 -0
  212. package/dist/flow-eval-gate.js +102 -0
  213. package/dist/flow-eval-gate.js.map +1 -0
  214. package/dist/flow-eval-local.d.ts +54 -0
  215. package/dist/flow-eval-local.d.ts.map +1 -0
  216. package/dist/flow-eval-local.js +220 -0
  217. package/dist/flow-eval-local.js.map +1 -0
  218. package/dist/flow-eval-outcome.d.ts +22 -0
  219. package/dist/flow-eval-outcome.d.ts.map +1 -0
  220. package/dist/flow-eval-outcome.js +37 -0
  221. package/dist/flow-eval-outcome.js.map +1 -0
  222. package/dist/flow-eval-report.d.ts +106 -0
  223. package/dist/flow-eval-report.d.ts.map +1 -0
  224. package/dist/flow-eval-report.js +4 -0
  225. package/dist/flow-eval-report.js.map +1 -0
  226. package/dist/flow-eval-suite.d.ts +46 -0
  227. package/dist/flow-eval-suite.d.ts.map +1 -0
  228. package/dist/flow-eval-suite.js +144 -0
  229. package/dist/flow-eval-suite.js.map +1 -0
  230. package/dist/flow-eval-version.d.ts +59 -0
  231. package/dist/flow-eval-version.d.ts.map +1 -0
  232. package/dist/flow-eval-version.js +303 -0
  233. package/dist/flow-eval-version.js.map +1 -0
  234. package/dist/flow-eval.d.ts +41 -0
  235. package/dist/flow-eval.d.ts.map +1 -0
  236. package/dist/flow-eval.js +201 -0
  237. package/dist/flow-eval.js.map +1 -0
  238. package/dist/helper-operation-use.d.ts +18 -0
  239. package/dist/helper-operation-use.d.ts.map +1 -0
  240. package/dist/helper-operation-use.js +336 -0
  241. package/dist/helper-operation-use.js.map +1 -0
  242. package/dist/helper-preflight.d.ts.map +1 -1
  243. package/dist/helper-preflight.js +16 -2
  244. package/dist/helper-preflight.js.map +1 -1
  245. package/dist/helper-reference.d.ts +33 -0
  246. package/dist/helper-reference.d.ts.map +1 -1
  247. package/dist/helper-reference.js +5 -5
  248. package/dist/helper-reference.js.map +1 -1
  249. package/dist/index.d.ts +6 -1
  250. package/dist/index.d.ts.map +1 -1
  251. package/dist/index.js +5 -1
  252. package/dist/index.js.map +1 -1
  253. package/dist/journal-budgeted-reads.d.ts +41 -0
  254. package/dist/journal-budgeted-reads.d.ts.map +1 -0
  255. package/dist/journal-budgeted-reads.js +142 -0
  256. package/dist/journal-budgeted-reads.js.map +1 -0
  257. package/dist/journal-client.d.ts +34 -29
  258. package/dist/journal-client.d.ts.map +1 -1
  259. package/dist/journal-client.js +45 -151
  260. package/dist/journal-client.js.map +1 -1
  261. package/dist/journal-connection.d.ts +42 -0
  262. package/dist/journal-connection.d.ts.map +1 -0
  263. package/dist/journal-connection.js +209 -0
  264. package/dist/journal-connection.js.map +1 -0
  265. package/dist/journal-read-policy.d.ts +38 -0
  266. package/dist/journal-read-policy.d.ts.map +1 -0
  267. package/dist/journal-read-policy.js +126 -0
  268. package/dist/journal-read-policy.js.map +1 -0
  269. package/dist/named-gate-lowering.d.ts.map +1 -1
  270. package/dist/named-gate-lowering.js +2 -6
  271. package/dist/named-gate-lowering.js.map +1 -1
  272. package/dist/re2js-embedded.d.ts +3 -0
  273. package/dist/re2js-embedded.d.ts.map +1 -0
  274. package/dist/re2js-embedded.js +4 -0
  275. package/dist/re2js-embedded.js.map +1 -0
  276. package/dist/spec.d.ts +10 -0
  277. package/dist/spec.d.ts.map +1 -1
  278. package/dist/spec.js.map +1 -1
  279. package/dist/step-fields.d.ts +1 -1
  280. package/dist/step-fields.js +1 -1
  281. package/dist/step-fields.js.map +1 -1
  282. package/dist/validate.d.ts.map +1 -1
  283. package/dist/validate.js +15 -0
  284. package/dist/validate.js.map +1 -1
  285. package/dist/worker-cli.d.ts +3 -1
  286. package/dist/worker-cli.d.ts.map +1 -1
  287. package/dist/worker-cli.js +15 -5
  288. package/dist/worker-cli.js.map +1 -1
  289. package/dist/worker-lease.d.ts.map +1 -1
  290. package/dist/worker-lease.js +11 -3
  291. package/dist/worker-lease.js.map +1 -1
  292. package/dist/worker.js +1 -1
  293. package/dist/worker.js.map +1 -1
  294. package/package.json +5 -3
  295. package/src/adapters/base.ts +16 -1
  296. package/src/adapters/claude.ts +8 -4
  297. package/src/adapters/codex.ts +15 -6
  298. package/src/agent-resume.ts +36 -0
  299. package/src/agent-transcript.ts +5 -3
  300. package/src/authored-flow-error.ts +9 -0
  301. package/src/authored-flow-executor.ts +18 -1
  302. package/src/authored-flow-loader.ts +27 -10
  303. package/src/authored-helper-effect.ts +3 -2
  304. package/src/authored-human.ts +5 -0
  305. package/src/authored-memory.ts +445 -15
  306. package/src/authored-node-entry.ts +6 -1
  307. package/src/authored-node-runner.ts +6 -3
  308. package/src/authored-root-readback.ts +100 -0
  309. package/src/authored-root.ts +58 -85
  310. package/src/authored-step-output.ts +3 -0
  311. package/src/authored-worker-step.ts +29 -4
  312. package/src/cli/check-helper-body.ts +9 -3
  313. package/src/cli/check-helper-surface.ts +43 -0
  314. package/src/cli/check-triggers.ts +9 -4
  315. package/src/cli/check.ts +37 -7
  316. package/src/cli/cloud-answer.ts +40 -0
  317. package/src/cli/cloud-connect-cli.ts +9 -2
  318. package/src/cli/cloud-human.ts +24 -0
  319. package/src/cli/cloud-live.ts +10 -5
  320. package/src/cli/cloud-mirror-session.ts +3 -2
  321. package/src/cli/cloud-run.ts +10 -1
  322. package/src/cli/detached-record.ts +71 -0
  323. package/src/cli/detached-run.ts +131 -0
  324. package/src/cli/diagnostic-fold.ts +28 -0
  325. package/src/cli/direct-run.ts +14 -1
  326. package/src/cli/eval.ts +141 -0
  327. package/src/cli/journal-timeout.ts +49 -0
  328. package/src/cli/local-agent-remedy.ts +6 -4
  329. package/src/cli/observer-session.ts +7 -1
  330. package/src/cli/reuse.ts +25 -0
  331. package/src/cli/run.ts +75 -67
  332. package/src/cli/running-step.ts +140 -0
  333. package/src/cli/status.ts +19 -13
  334. package/src/cli-adapter.ts +7 -1
  335. package/src/cli-commands.ts +43 -7
  336. package/src/cli-watch.ts +8 -5
  337. package/src/cli.ts +158 -51
  338. package/src/cloud-answer.ts +202 -0
  339. package/src/cloud-auth-store.ts +185 -0
  340. package/src/cloud-http.ts +61 -50
  341. package/src/cloud-human-wait.ts +23 -0
  342. package/src/cloud-mirror-transport.ts +1 -0
  343. package/src/cloud-run-record.ts +14 -5
  344. package/src/cloud-run.ts +46 -2
  345. package/src/communication/local.ts +6 -1
  346. package/src/communication/worker.ts +11 -3
  347. package/src/compile.ts +5 -3
  348. package/src/daemon-spec-validation.ts +126 -0
  349. package/src/failure-kinds.ts +17 -1
  350. package/src/flow-eval-cloud.ts +114 -0
  351. package/src/flow-eval-error.ts +10 -0
  352. package/src/flow-eval-gate.ts +114 -0
  353. package/src/flow-eval-local.ts +236 -0
  354. package/src/flow-eval-outcome.ts +43 -0
  355. package/src/flow-eval-report.ts +102 -0
  356. package/src/flow-eval-suite.ts +169 -0
  357. package/src/flow-eval-version.ts +316 -0
  358. package/src/flow-eval.ts +234 -0
  359. package/src/helper-operation-use.ts +285 -0
  360. package/src/helper-preflight.ts +17 -2
  361. package/src/helper-reference.ts +6 -6
  362. package/src/index.ts +15 -1
  363. package/src/journal-budgeted-reads.ts +134 -0
  364. package/src/journal-client.ts +59 -166
  365. package/src/journal-connection.ts +222 -0
  366. package/src/journal-read-policy.ts +123 -0
  367. package/src/named-gate-lowering.ts +2 -6
  368. package/src/re2js-embedded.ts +3 -0
  369. package/src/spec.ts +10 -0
  370. package/src/step-fields.ts +1 -1
  371. package/src/validate.ts +14 -0
  372. package/src/worker-cli.ts +14 -3
  373. package/src/worker-lease.ts +11 -3
  374. package/src/worker.ts +1 -1
@@ -1,3 +1,4 @@
1
+ import { isReadInterruptionError } from './journal-read-policy.js';
1
2
  import { loadPinnedAuthoredSource } from './authored-source-authority.js';
2
3
  import { assertAuthoredRuntimeAvailable, runAuthoredInNode } from './authored-node-runner.js';
3
4
  import { createHash, randomUUID } from 'node:crypto';
@@ -5,7 +6,6 @@ import { readFile } from 'node:fs/promises';
5
6
  import { canonicalize } from './canonical.js';
6
7
  import { compileSpec, toKernelSpec } from './compile.js';
7
8
  import { executeAuthoredFlow, type AuthoredFlowExecutionResult, type AuthoredFlowSuspendedResult } from './authored-flow-executor.js';
8
- import { isDurableCompletionDetail, isLoweredCompletion } from './authored-completion.js';
9
9
  import { AUTHORED_ROOT_KIND } from './authored-verdict.js';
10
10
  import {
11
11
  loadAuthoredFlow,
@@ -19,9 +19,9 @@ import type { RunLifecycleOptions } from './cli/run.js';
19
19
  import { isLeaseLost, withWorkerLease } from './worker-lease.js';
20
20
  import { AuthoredFlowExecutionError, AuthoredHumanParked } from './authored-flow-error.js';
21
21
  import { readOpenHumanWaits, resumeCommand } from './authored-human.js';
22
- import { isSurfaceCompletionReason } from './authored-step-output.js';
23
22
  import { readSubscriptionPark } from './authored-subscription-park.js';
24
23
  import { localAgentCredentialEnvironment } from './local-agent-environment.js';
24
+ import { completedResult, rootKernelStep, rootStepIfPresent } from './authored-root-readback.js';
25
25
 
26
26
  export type DurableAuthoredFlowResult =
27
27
  | (AuthoredFlowExecutionResult & { readonly rootRunId: string })
@@ -117,9 +117,10 @@ export async function executeDurableAuthoredFlow(
117
117
  });
118
118
  const outcome = await journal.runStart(spec, undefined, admissionKey);
119
119
  options.onAdmitted?.(outcome.run_id);
120
+ options.lifecycle?.onRunReceipt?.({ runId: outcome.run_id, flow: definition.name });
120
121
  if (outcome.status === 'completed') {
121
122
  dispatchWait.cancel();
122
- return await completedRootResult(journal, outcome.run_id);
123
+ return await completedResult(journal, outcome, outcome.run_id);
123
124
  }
124
125
  assertRootCanDispatch(outcome);
125
126
  options.lifecycle?.onRunStarted?.({ runId: outcome.run_id, flow: definition.name });
@@ -166,9 +167,10 @@ export async function resumeDurableAuthoredFlow(
166
167
  workspace: [], streams: [{ stream, read_offset: 0 }],
167
168
  });
168
169
  const outcome = await journal.runResume(rootRunId);
170
+ options.lifecycle?.onRunReceipt?.({ runId: rootRunId, flow: metadata.flowName });
169
171
  if (outcome.status === 'completed') {
170
172
  dispatchWait.cancel();
171
- return await completedRootResult(journal, rootRunId);
173
+ return await completedResult(journal, outcome, rootRunId);
172
174
  }
173
175
  assertRootCanDispatch(outcome);
174
176
  await assertNoOpenHumanWait(journal, outcome);
@@ -199,7 +201,7 @@ export async function readAuthoredRootMetadata(
199
201
  journal: JournalClient,
200
202
  runId: string,
201
203
  ): Promise<AuthoredRootMetadata | undefined> {
202
- const step = await rootKernelStep(journal, runId).catch(() => undefined);
204
+ const step = await rootStepIfPresent(journal, runId);
203
205
  if (step === undefined || step.id !== 'authored-root' || !hasRootStream(step)) return undefined;
204
206
  if (typeof step.instruction !== 'string') {
205
207
  throw new Error('authored root journal has malformed authority metadata');
@@ -320,38 +322,43 @@ async function driveRoot(
320
322
  ): Promise<DurableAuthoredFlowResult> {
321
323
  try {
322
324
  const result = await withWorkerLease(peer, dispatch, async rootSignal => {
323
- const callerSignal = options.lifecycle?.signal;
324
- const signal = callerSignal === undefined ? rootSignal : AbortSignal.any([callerSignal, rootSignal]);
325
- if (process.versions['bun'] !== undefined) {
326
- return runAuthoredInNode(metadata, journal.socketPath, dispatch.run_id, {
327
- dataDir: options.dataDir, localAgentStream: options.localAgentStream,
328
- ...(options.agentEnvironment === undefined ? {} : {
329
- agentEnvironment: localAgentCredentialEnvironment(options.agentEnvironment),
330
- }),
331
- ...(options.workerCapacity === undefined ? {} : { workerCapacity: options.workerCapacity }),
332
- ...options.lifecycle, signal,
333
- });
334
- }
335
- return await executeAuthoredFlow(
336
- loaded.handle,
337
- journal,
338
- metadata.inputPresent ? metadata.input : undefined,
339
- {
340
- getDefinition: loaded.getDefinition,
341
- dataDir: options.dataDir,
342
- flowPath: metadata.flowPath,
343
- localAgentStream: options.localAgentStream,
344
- ...(options.agentEnvironment === undefined ? {} : { agentEnvironment: options.agentEnvironment }),
345
- ...(options.workerCapacity === undefined ? {} : { workerCapacity: options.workerCapacity }),
346
- rootRunId: dispatch.run_id,
347
- extensions: loaded.extensions,
348
- flowGraph: loaded.graph,
349
- ...options.lifecycle,
350
- signal: callerSignal === undefined
351
- ? rootSignal
352
- : AbortSignal.any([callerSignal, rootSignal]),
353
- },
354
- );
325
+ // A lost root lease must also stop body reads still inside their budget.
326
+ const releaseReads = journal.scopeReads?.(rootSignal) ?? (() => {});
327
+ try {
328
+ const callerSignal = options.lifecycle?.signal;
329
+ const signal = callerSignal === undefined ? rootSignal : AbortSignal.any([callerSignal, rootSignal]);
330
+ if (process.versions['bun'] !== undefined) {
331
+ // Awaited, so the read scope below covers the child's whole run.
332
+ return await runAuthoredInNode(metadata, journal.socketPath, dispatch.run_id, {
333
+ dataDir: options.dataDir, localAgentStream: options.localAgentStream,
334
+ ...(options.agentEnvironment === undefined ? {} : {
335
+ agentEnvironment: localAgentCredentialEnvironment(options.agentEnvironment),
336
+ }),
337
+ ...(options.workerCapacity === undefined ? {} : { workerCapacity: options.workerCapacity }),
338
+ ...options.lifecycle, signal,
339
+ });
340
+ }
341
+ return await executeAuthoredFlow(
342
+ loaded.handle,
343
+ journal,
344
+ metadata.inputPresent ? metadata.input : undefined,
345
+ {
346
+ getDefinition: loaded.getDefinition,
347
+ dataDir: options.dataDir,
348
+ flowPath: metadata.flowPath,
349
+ localAgentStream: options.localAgentStream,
350
+ ...(options.agentEnvironment === undefined ? {} : { agentEnvironment: options.agentEnvironment }),
351
+ ...(options.workerCapacity === undefined ? {} : { workerCapacity: options.workerCapacity }),
352
+ rootRunId: dispatch.run_id,
353
+ extensions: loaded.extensions,
354
+ flowGraph: loaded.graph,
355
+ ...options.lifecycle,
356
+ signal: callerSignal === undefined
357
+ ? rootSignal
358
+ : AbortSignal.any([callerSignal, rootSignal]),
359
+ },
360
+ );
361
+ } finally { releaseReads(); }
355
362
  });
356
363
  await peer.stepComplete(
357
364
  dispatch.run_id, dispatch.step_id, dispatch.attempt,
@@ -368,6 +375,20 @@ async function driveRoot(
368
375
  // A lost lease is owned by the kernel's retry path, not by this attempt's
369
376
  // terminalization or suspension handling.
370
377
  if (isLeaseLost(error)) throw error;
378
+ // A caller cancel is the caller's, not a body failure: reads aborted by it
379
+ // (including the Bun verifier's) leave the root resumable, never terminal.
380
+ const signal = options.lifecycle?.signal;
381
+ if (aborted(signal) && (error === signal!.reason || (error instanceof Error && error.name === 'AbortError'))) {
382
+ if (error instanceof Error) (error as Error & { rootRunId?: string }).rootRunId = dispatch.run_id;
383
+ throw error;
384
+ }
385
+ if (isReadInterruptionError(error)
386
+ || (error instanceof AuthoredFlowExecutionError && error.code === 'daemon_unresponsive')) {
387
+ const parked = new AuthoredFlowExecutionError('daemon_unresponsive',
388
+ `${error.message}. The run remains resumable. Continue with: ${resumeCommand(dispatch.run_id, options.dataDir, options.localAgentStream !== undefined)}.`);
389
+ parked.rootRunId = dispatch.run_id;
390
+ throw parked;
391
+ }
371
392
  if (error instanceof AuthoredFlowExecutionError
372
393
  && error.code === 'subscription_suspended'
373
394
  && error.suspension !== undefined) {
@@ -450,16 +471,6 @@ function rootSpec(metadata: AuthoredRootMetadata, stream: string) {
450
471
  }));
451
472
  }
452
473
 
453
- async function rootKernelStep(journal: JournalClient, runId: string): Promise<Record<string, unknown>> {
454
- const entries = (await journal.journalRead(runId, 1)).entries as Array<Record<string, unknown>>;
455
- const spawned = entries.find(entry => entry.entry_type === 'run.spawned');
456
- const payload = spawned?.payload as { spec?: { steps?: unknown[] } } | undefined;
457
- const step = payload?.spec?.steps?.[0];
458
- if (typeof step !== 'object' || step === null || Array.isArray(step)) {
459
- throw new Error('authored root journal has no root step');
460
- }
461
- return step as Record<string, unknown>;
462
- }
463
474
 
464
475
  function rootStreamFromMetadata(step: Record<string, unknown>): string {
465
476
  const surfaces = step.surfaces as { streams?: Array<{ stream?: unknown }> } | undefined;
@@ -543,44 +554,6 @@ function nextRootDispatch(peer: JournalClient, timeoutMs = 30_000): {
543
554
  return { promise, cancel };
544
555
  }
545
556
 
546
- async function completedRootResult(
547
- journal: JournalClient,
548
- rootRunId: string,
549
- ): Promise<AuthoredFlowExecutionResult & { readonly rootRunId: string }> {
550
- const entries = (await journal.journalRead(rootRunId, 1)).entries as Array<Record<string, unknown>>;
551
- const completed = entries.slice().reverse().find(entry => entry.entry_type === 'step.completed'
552
- && entry.step_id === 'authored-root'
553
- && (entry.payload as { completionReason?: unknown } | undefined)?.completionReason === 'success');
554
- const output = (completed?.payload as { output?: unknown } | undefined)?.output;
555
- if (!isCompletedRootOutput(output)) {
556
- throw new Error('completed authored root has no durable result');
557
- }
558
- // The stored detail, never a freshly computed one: redaction reads the
559
- // CURRENT environment, so recomputing here would let a completed run report
560
- // something its journal does not hold.
561
- return Object.freeze({
562
- name: output.name,
563
- completionReason: output.completionReason,
564
- ...(output.completionDetail === undefined ? {} : { completionDetail: output.completionDetail }),
565
- journalSteps: Object.freeze(output.journalSteps.map(step => Object.freeze({ ...step }))),
566
- rootRunId,
567
- });
568
- }
569
-
570
- function isCompletedRootOutput(value: unknown): value is Omit<AuthoredFlowExecutionResult, 'rootRunId'> {
571
- if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;
572
- const output = value as Partial<AuthoredFlowExecutionResult>;
573
- return typeof output.name === 'string'
574
- && isLoweredCompletion(output.completionReason)
575
- // A malformed detail fails the whole readback rather than being dropped:
576
- // silently discarding it would report the verdict without the evidence
577
- // the journal says it was recorded with.
578
- && (output.completionDetail === undefined || isDurableCompletionDetail(output.completionDetail))
579
- && Array.isArray(output.journalSteps)
580
- && output.journalSteps.every(step => typeof step === 'object' && step !== null
581
- && typeof step.id === 'string' && typeof step.runId === 'string'
582
- && isSurfaceCompletionReason(step.completionReason));
583
- }
584
557
 
585
558
  function assertRootCanDispatch(outcome: RunOutcome): void {
586
559
  // The kernel reports `parked` after handing an agent lease to its worker;
@@ -23,6 +23,8 @@ export interface AuthoredStepContext {
23
23
  readonly stepEdges?: (step: string) => AuthoredStepEdges | undefined;
24
24
  /** Cancels the wait for an adopted child run that is not yet terminal. */
25
25
  readonly signal?: AbortSignal;
26
+ /** Sees the accepted completion's payload (its evidence, not only its output). */
27
+ readonly onCompleted?: (payload: Readonly<Record<string, unknown>>) => void;
26
28
  }
27
29
 
28
30
  /**
@@ -149,6 +151,7 @@ export async function readCompletedStepOutput(
149
151
  await recordAuthoredChild(journal, context.rootRunId, {
150
152
  step: stepId, runId, state: 'completed', completionReason: reason, ...edges,
151
153
  });
154
+ context.onCompleted?.(completed.payload as unknown as Readonly<Record<string, unknown>>);
152
155
  return completed.payload.output;
153
156
  }
154
157
 
@@ -9,6 +9,7 @@ import { classifyOutcome, type RunLifecycleOptions, type RunReport } from './cli
9
9
  import type { PreflightDiagnostic } from './preflight.js';
10
10
  import { AuthoredFlowExecutionError } from './authored-flow-error.js';
11
11
  import { agentCwdDeclarationError, agentCwdTransportError } from './agent-cwd.js';
12
+ import { agentResumeDeclarationError, agentResumeTransportError } from './agent-resume.js';
12
13
  import type { JournalClient } from './journal-client.js';
13
14
  import { SPEC_SCHEMA_VERSION, type FlowSpec, type PermissionsSpec, type StepSpec } from './spec.js';
14
15
  import { isSurfaceCompletionReason, readCompletedStepOutput, readRecordedOutcome, readSuccessfulOutput, type AuthoredStepContext, type RecordedRunOutcome } from './authored-step-output.js';
@@ -46,7 +47,7 @@ export function authoredWorkerRunner(
46
47
  ...(stepEdges === undefined ? {} : { stepEdges }),
47
48
  ...(waitOptions.signal === undefined ? {} : { signal: waitOptions.signal }),
48
49
  };
49
- async function run(step: StepSpec): Promise<unknown> {
50
+ async function run(step: StepSpec, onCompleted?: AuthoredStepContext['onCompleted']): Promise<unknown> {
50
51
  const id = step.id;
51
52
  const authoring: FlowSpec = { version: SPEC_SCHEMA_VERSION, name: `${definition.name}/${id}`, steps: [step], ...(headerBudget === undefined ? {} : { budget: parseBudget(headerBudget) }) };
52
53
  // The kernel never resolves a `cli` on its own — every declarative
@@ -152,7 +153,8 @@ export function authoredWorkerRunner(
152
153
  details,
153
154
  );
154
155
  }
155
- return readCompletedStepOutput(journal, outcome.run_id, id, journalSteps, context, execution.report.status);
156
+ return readCompletedStepOutput(journal, outcome.run_id, id, journalSteps,
157
+ onCompleted === undefined ? context : { ...context, onCompleted }, execution.report.status);
156
158
  };
157
159
  const admissionKey = authoredChildAdmissionKey(rootRunId, id);
158
160
  const admit = async () => budget === undefined
@@ -250,6 +252,11 @@ export function authoredWorkerRunner(
250
252
  `f.agent options.recoveryMode must be 'reset', 'inspect', or 'manual' (got ${JSON.stringify(options.recoveryMode)}).`,
251
253
  );
252
254
  }
255
+ const resumeProblem = options.resume === undefined ? undefined
256
+ : agentResumeDeclarationError(options.resume) ?? agentResumeTransportError(options.resume, options.transport);
257
+ if (resumeProblem !== undefined) {
258
+ throw new AuthoredFlowExecutionError('agent_cli_unresolved', `f.agent options.resume: ${resumeProblem}.`);
259
+ }
253
260
  const cwdTransport = agentCwdTransportError(declaredCwd, options.transport);
254
261
  if (cwdTransport !== undefined) {
255
262
  throw new AuthoredFlowExecutionError('agent_cli_unresolved', `f.agent options.${cwdTransport}.`);
@@ -263,6 +270,7 @@ export function authoredWorkerRunner(
263
270
  'f.agent timeout requires direct transport and maxIterations 1.');
264
271
  }
265
272
  }
273
+ let sessionId: string | undefined;
266
274
  const permissions = options.permissions;
267
275
  const permissionsSnapshot = permissions === undefined ? undefined
268
276
  : snapshotJsonValue(permissions, 'f.agent options.permissions') as unknown as PermissionsSpec;
@@ -275,12 +283,13 @@ export function authoredWorkerRunner(
275
283
  ...(options.cli === undefined ? {} : { cli: options.cli }),
276
284
  ...(options.model === undefined ? {} : { model: options.model }),
277
285
  ...(declaredCwd === undefined ? {} : { cwd: declaredCwd }),
286
+ ...(options.resume === undefined ? {} : { resume: options.resume }),
278
287
  ...(options.transport === undefined ? {} : { transport: options.transport }),
279
288
  ...(options.maxIterations === undefined ? {} : { maxIterations: options.maxIterations }),
280
289
  ...(options.transportRetries === undefined ? {} : { transportRetries: options.transportRetries }),
281
290
  ...(options.recoveryMode === undefined ? {} : { recoveryMode: options.recoveryMode }),
282
291
  ...(verification === undefined ? {} : { verification }),
283
- });
292
+ }, (payload) => { sessionId = journaledSessionId(payload); });
284
293
  if (typeof output !== 'object' || output === null || Array.isArray(output)) {
285
294
  throw new AuthoredFlowExecutionError('journal_protocol_violation', `step "${id}" produced a non-object output`);
286
295
  }
@@ -294,7 +303,10 @@ export function authoredWorkerRunner(
294
303
  const journaled = 'artifacts' in output ? output.artifacts : undefined;
295
304
  const artifacts = Array.isArray(journaled) && journaled.every(entry => typeof entry === 'string')
296
305
  ? [...journaled] : [];
297
- return { completionReason: 'success', summary: typeof stdout === 'string' ? stdout : JSON.stringify(output), artifacts };
306
+ return {
307
+ completionReason: 'success', summary: typeof stdout === 'string' ? stdout : JSON.stringify(output), artifacts,
308
+ ...(sessionId === undefined ? {} : { sessionId }),
309
+ };
298
310
  },
299
311
  async llm(id: string, prompt: string, options?: LlmOptions, verification?: NamedGate): Promise<unknown> {
300
312
  if (options !== undefined && (typeof options !== 'object' || options === null || options.output === undefined)) {
@@ -411,3 +423,16 @@ function stepDetails(
411
423
  };
412
424
  return Object.keys(details).length === 0 ? undefined : details;
413
425
  }
426
+
427
+ /**
428
+ * The CLI session the worker journaled for an agent attempt
429
+ * (`trajectory_tail.transcript.result.session_id`). Evidence, so it is read on
430
+ * the JSON-answer path too, and offered only when it is a session id a later
431
+ * `resume` would accept (a bounded label never is one).
432
+ */
433
+ function journaledSessionId(payload: Readonly<Record<string, unknown>>): string | undefined {
434
+ const field = (value: unknown, key: string): unknown =>
435
+ value !== null && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>)[key] : undefined;
436
+ const id = field(field(field(payload.trajectory_tail, 'transcript'), 'result'), 'session_id');
437
+ return typeof id === 'string' && agentResumeDeclarationError(id) === undefined ? id : undefined;
438
+ }
@@ -1,13 +1,19 @@
1
1
  import { loadAuthoredFlow } from '../authored-flow-loader.js';
2
2
  import { PluginError } from '../plugin-manifest.js';
3
3
  import { checkSlackHelpers } from '../slack-preflight.js';
4
- import { inputFailureReport, type CheckExecution } from './check.js';
4
+ import { inputFailureReport, type CheckInvocation, type CheckExecution } from './check.js';
5
+
6
+ import { helperCredentialDiagnostics } from './check-helper-surface.js';
5
7
 
6
8
  /** Imports the definition, but never executes arbitrary authored body code. */
7
- export async function checkHelperBody(path: string): Promise<CheckExecution> {
9
+ export async function checkHelperBody(path: string, invocation: CheckInvocation = {}): Promise<CheckExecution> {
8
10
  try {
9
11
  const { handle, getDefinition } = await loadAuthoredFlow(path);
10
- return { report: { ...checkSlackHelpers(getDefinition(handle)), path } };
12
+ const report = checkSlackHelpers(getDefinition(handle));
13
+ const diagnostics = invocation.warnUnresolvedHelperCredential === true
14
+ ? helperCredentialDiagnostics(report.diagnostics).diagnostics : report.diagnostics;
15
+ return { report: { ...report, path, diagnostics,
16
+ ok: !diagnostics.some(diagnostic => diagnostic.severity === 'refusal') } };
11
17
  } catch (error) {
12
18
  if (error instanceof PluginError) {
13
19
  return { report: inputFailureReport({ kind: error.code, message: error.message }, path) };
@@ -0,0 +1,43 @@
1
+ import { helperProviders } from '@relayflows/surface/runtime';
2
+ import type { PreflightDiagnostic } from '../preflight.js';
3
+ import type { CheckWarningDiagnostic } from './check.js';
4
+
5
+ const mountKinds = new Set([
6
+ 'helper_slack.credential_missing', 'helper_slack.mount_required',
7
+ 'helper_provider.mount_required', 'helper_mount_required',
8
+ ]);
9
+
10
+ /** Restate local mount failures for inspection only; execution stays strict. */
11
+ export function helperCredentialDiagnostics(diagnostics: readonly PreflightDiagnostic[]): {
12
+ diagnostics: Array<PreflightDiagnostic | CheckWarningDiagnostic>;
13
+ downgraded: boolean;
14
+ } {
15
+ const result: Array<PreflightDiagnostic | CheckWarningDiagnostic> = [];
16
+ const seen = new Set<string>();
17
+ for (const diagnostic of diagnostics) {
18
+ const helper = diagnostic.severity === 'refusal' && mountKinds.has(diagnostic.kind)
19
+ ? helperProviders.find(({ namespace, provider }) =>
20
+ diagnostic.message.startsWith(`f.${namespace} requires `)
21
+ || diagnostic.message.startsWith(`${provider} helper requires `))
22
+ : undefined;
23
+ // Unknown diagnostic shapes remain refusals rather than silently passing.
24
+ if (helper === undefined) {
25
+ result.push(diagnostic);
26
+ continue;
27
+ }
28
+ if (seen.has(helper.provider)) continue;
29
+ seen.add(helper.provider);
30
+ // YAML execution (helper_mount_required) honours a mock only for Slack
31
+ // (yaml-helper-effect.ts helperReady); every other YAML helper needs a mount.
32
+ const mockSupported = diagnostic.kind !== 'helper_mount_required' || helper.provider === 'slack';
33
+ result.push({
34
+ severity: 'warning', kind: 'helper_credential_unresolved',
35
+ message: `f.${helper.namespace} needs a ${helper.provider} mount, which is not available locally. `
36
+ + 'flows schedule / flows deploy / flows run --cloud check the integration against your workspace at submit and refuse if Cloud cannot connect it. '
37
+ + `A local flows run needs a relayfile ${helper.provider} mount (a ${helper.provider}/ directory under RELAYFILE_MOUNT_PATH)`
38
+ + (mockSupported ? ` or ${helper.mockEnv}=1` : '')
39
+ + `, and refuses with [${diagnostic.kind}] without one.`,
40
+ });
41
+ }
42
+ return { diagnostics: result, downgraded: seen.size > 0 };
43
+ }
@@ -7,16 +7,19 @@ import { scheduleLowering } from '../schedule-trigger.js';
7
7
  import { checkSlackHelpers } from '../slack-preflight.js';
8
8
  import { flowRequirements, mergeFlowExtensionRequirements } from '../flow-requirements.js';
9
9
  import { PluginError } from '../plugin-manifest.js';
10
- import { inputFailureReport, readProjectConfig, type CheckReport } from './check.js';
10
+ import { inputFailureReport, readProjectConfig, type CheckInvocation, type CheckReport } from './check.js';
11
+
12
+ import { helperCredentialDiagnostics } from './check-helper-surface.js';
11
13
 
12
14
  /**
13
15
  * Inspect an authored flow without running a handler or contacting the daemon.
14
16
  * Combines the two authored-only preflight paths that share a loaded flow: the
15
17
  * webhook-trigger check (E) and the f.slack helper check (B). Skipping either
16
18
  * turned this into a silent trapdoor -- a `.flow.ts` using `f.slack.post`
17
- * without a token would pass `flows check` and only crash at run.
19
+ * without a mount would silently pass inspection. Check may opt into a
20
+ * warning; local run keeps the default refusal before daemon attachment.
18
21
  */
19
- export async function checkAuthoredTriggers(path: string): Promise<{
22
+ export async function checkAuthoredTriggers(path: string, invocation: CheckInvocation = {}): Promise<{
20
23
  report: CheckReport;
21
24
  loaded?: LoadedAuthoredFlow;
22
25
  }> {
@@ -33,8 +36,10 @@ export async function checkAuthoredTriggers(path: string): Promise<{
33
36
  // refused there, on the first real event.
34
37
  const providerDiagnostics = preflightProviderTriggers(triggers);
35
38
  const helperReport = checkSlackHelpers(definition);
39
+ const helperDiagnostics = invocation.warnUnresolvedHelperCredential === true
40
+ ? helperCredentialDiagnostics(helperReport.diagnostics).diagnostics : helperReport.diagnostics;
36
41
  const diagnostics = [
37
- ...triggerDiagnostics, ...providerDiagnostics, ...helperReport.diagnostics,
42
+ ...triggerDiagnostics, ...providerDiagnostics, ...helperDiagnostics,
38
43
  ];
39
44
  // A schedule is inspectable data: print what it lowers to, and say plainly
40
45
  // when the local runner cannot drive it. Neither is a refusal — Cloud can.
package/src/cli/check.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { validateSpecWithDaemon, type DaemonValidation, type DaemonValidationMode, type DaemonValidationDeps } from '../daemon-spec-validation.js';
1
2
  import { communicationInstruction } from '../communication/spec.js';
2
3
  import { rememberResolvedCliIdentities } from '../resolved-cli-identity.js';
3
4
  import { checkCommunicationEnvironment } from '../communication/preflight.js';
@@ -5,6 +6,7 @@ import { accessSync, constants, readFileSync } from 'node:fs';
5
6
  import { dirname, isAbsolute, join, parse as parsePath, resolve } from 'node:path';
6
7
  import { parse as parseYaml } from 'yaml';
7
8
  import { CompileError, compileSpec, kernelToAuthoring } from '../compile.js';
9
+ import { helperCredentialDiagnostics } from './check-helper-surface.js';
8
10
  import { agentWorkerDiagnostics } from './check-worker-surface.js';
9
11
  import { helperReady } from '../yaml-helper-effect.js';
10
12
  import { flowRequirements, type FlowRequirements } from '../flow-requirements.js';
@@ -44,6 +46,7 @@ export interface ProjectConfig {
44
46
  }
45
47
 
46
48
  export interface CheckReport {
49
+ validation?: DaemonValidation;
47
50
  mcpTools?: Readonly<Record<string, readonly string[]>>;
48
51
  ok: boolean;
49
52
  path?: string;
@@ -125,6 +128,10 @@ type CliProbeOutcomeMap = Map<string, CliProbeOutcome>;
125
128
  * anything that depends on how the flow is about to be invoked opts in here.
126
129
  */
127
130
  export interface CheckInvocation {
131
+ /** Only flows check opts into reporting missing local helper mounts as warnings. */
132
+ warnUnresolvedHelperCredential?: boolean;
133
+ daemonValidation?: DaemonValidationMode;
134
+ daemonValidationDeps?: DaemonValidationDeps;
128
135
  /** Environment used only by provider executable/auth/model probes. */
129
136
  environment?: NodeJS.ProcessEnv;
130
137
  /**
@@ -159,7 +166,7 @@ export function checkFlow(path: string, invocation: CheckInvocation = {}): Check
159
166
  execution = checkAuthoredFlow(readFlow(source, absolutePath), path, undefined, invocation);
160
167
  } catch (error) {
161
168
  if (!(error instanceof CheckFailure)) throw error;
162
- execution = { report: inputFailureReport(error, path) };
169
+ execution = { report: inputFailureReport(error, path, invocation) };
163
170
  }
164
171
  // The editor-schema hint is a documentation nudge, emitted for every
165
172
  // .flow.yaml without a first-line yaml-language-server comment.
@@ -172,14 +179,17 @@ export function checkFlow(path: string, invocation: CheckInvocation = {}): Check
172
179
  const failure = error instanceof CheckFailure
173
180
  ? error
174
181
  : new CheckFailure('invalid_spec', `Flow "${path}" could not be checked as a Relayflow spec.`);
175
- return { report: inputFailureReport(failure, path) };
182
+ return { report: inputFailureReport(failure, path, invocation) };
176
183
  }
177
184
  }
178
185
 
179
186
  /** Requirements never turn a preflight refusal into an unrelated exception. */
180
187
  function safeRequirements(authoring: FlowSpec, projectCli: string | undefined): FlowRequirements | undefined {
181
188
  try {
182
- return flowRequirements(authoring, projectCli === undefined ? {} : { projectCli });
189
+ let requirementsSpec = authoring;
190
+ try { requirementsSpec = compileSpec(authoring); }
191
+ catch { /* Preserve requirements from invalid authoring; preflight owns its refusal. */ }
192
+ return flowRequirements(requirementsSpec, projectCli === undefined ? {} : { projectCli });
183
193
  } catch {
184
194
  return undefined;
185
195
  }
@@ -220,7 +230,14 @@ export function checkAuthoredFlow(
220
230
  ...(config.modelRegistryPath !== undefined ? { modelRegistryPath: config.modelRegistryPath } : {}),
221
231
  probes,
222
232
  });
223
- const flow = result.ok
233
+ // check may restate a missing local helper mount as a warning. When that is
234
+ // the only refusal, still compile the flow so the communication checks
235
+ // below run: they must not depend on whether a helper is mounted. The
236
+ // compiled flow is returned only when preflight itself admitted it.
237
+ const inspectOnly = !result.ok && invocation.warnUnresolvedHelperCredential === true
238
+ && !helperCredentialDiagnostics(result.diagnostics).diagnostics
239
+ .some(diagnostic => diagnostic.severity === 'refusal');
240
+ const flow = result.ok || inspectOnly
224
241
  ? bindResolvedCliPaths(
225
242
  compileSpec(authoring),
226
243
  result.resolutions,
@@ -246,14 +263,25 @@ export function checkAuthoredFlow(
246
263
  const workerSurface = invocation.warnUnresolvedAgentWorker === true
247
264
  ? agentWorkerDiagnostics(authoring)
248
265
  : [];
266
+ // A flow inspected past a missing helper mount is still a compiled spec the
267
+ // daemon must judge; skipping it would let a downgraded warning pass the gate.
268
+ const daemon = invocation.daemonValidation === undefined ? undefined
269
+ : (result.ok || inspectOnly) && flow !== undefined
270
+ ? validateSpecWithDaemon(flow, invocation.daemonValidation, invocation.daemonValidationDeps)
271
+ : { validation: { mode: 'local' as const, reason: 'not_reached' as const }, diagnostics: [] };
272
+ if (daemon?.diagnostics.some(d => d.severity === 'refusal')) result.ok = false;
273
+ const diagnostics = invocation.warnUnresolvedHelperCredential === true
274
+ ? helperCredentialDiagnostics(result.diagnostics).diagnostics : result.diagnostics;
249
275
  return {
250
276
  report: {
251
- ok: result.ok,
277
+ ...(daemon === undefined ? {} : { validation: daemon.validation }),
278
+ ok: !diagnostics.some(diagnostic => diagnostic.severity === 'refusal')
279
+ && !(daemon?.diagnostics.some(diagnostic => diagnostic.severity === 'refusal') ?? false),
252
280
  path,
253
281
  ...(config.path !== undefined ? { projectConfigPath: config.path } : {}),
254
282
  gates: result.gates,
255
283
  resolutions: result.resolutions,
256
- diagnostics: [...result.diagnostics, ...workerSurface],
284
+ diagnostics: [...diagnostics, ...workerSurface, ...(daemon?.diagnostics ?? [])],
257
285
  requirements: safeRequirements(authoring, config.cli),
258
286
  },
259
287
  ...(result.ok && flow !== undefined ? { flow } : {}),
@@ -262,7 +290,7 @@ export function checkAuthoredFlow(
262
290
  const failure = error instanceof CheckFailure
263
291
  ? error
264
292
  : new CheckFailure('invalid_spec', `Flow "${path}" could not be checked as a Relayflow spec.`);
265
- return { report: inputFailureReport(failure, path) };
293
+ return { report: inputFailureReport(failure, path, invocation) };
266
294
  }
267
295
  }
268
296
 
@@ -335,8 +363,10 @@ export async function checkBuildableFlow(path: string): Promise<CheckExecution>
335
363
  export function inputFailureReport(
336
364
  failure: { kind: CheckFailureKind; message: string },
337
365
  path?: string,
366
+ invocation: CheckInvocation = {},
338
367
  ): CheckReport {
339
368
  return {
369
+ ...(invocation.daemonValidation === undefined ? {} : { validation: { mode: 'local' as const, reason: 'not_reached' as const } }),
340
370
  ok: false,
341
371
  ...(path !== undefined ? { path } : {}),
342
372
  gates: [],
@@ -0,0 +1,40 @@
1
+ import { cloudAnswerCommand } from '../authored-human.js';
2
+ import { shellWord } from '../shell-word.js';
3
+ import { answerCloudFlow, CloudAnswerError } from '../cloud-answer.js';
4
+ import { CloudFlowError } from '../cloud-http.js';
5
+ import { redact } from '../redact.js';
6
+ import { safe } from './cloud-format.js';
7
+ import type { CliIo } from '../cli.js';
8
+
9
+ export async function runCloudAnswerCli(
10
+ args: { runId: string; answer: boolean; note?: string; source?: string; json: boolean },
11
+ io: CliIo,
12
+ signal: AbortSignal,
13
+ ): Promise<0 | 1 | 2> {
14
+ try {
15
+ const receipt = await answerCloudFlow(args.runId, args.answer, { ...args, signal });
16
+ if (args.json) io.stdout(JSON.stringify({ ok: true, ...receipt }));
17
+ else {
18
+ io.stdout(`ANSWERED ${receipt.resumedFrom} ${receipt.waitId} ${receipt.answer ? 'yes' : 'no'}`);
19
+ io.stdout(`RESUMED ${receipt.runId}${receipt.resumedByCloud ? ' (by Cloud)' : ''}`);
20
+ }
21
+ return 0;
22
+ } catch (error) {
23
+ const partial = error instanceof CloudAnswerError;
24
+ const cause = partial ? error.cause : error;
25
+ const code = partial ? 'cloud_answer_incomplete' : cause instanceof CloudFlowError ? cause.code : 'cloud_answer_failed';
26
+ const detail = partial && cause instanceof Error ? ` ${cause.message}` : '';
27
+ const message = safe(redact((error instanceof Error ? error.message : 'Cloud answer failed.') + detail, process.env));
28
+ const retry = partial ? cloudAnswerCommand(args.runId).replace(/yes\|no$/u, args.answer ? 'yes' : 'no')
29
+ + (args.source === undefined ? '' : ` --source ${shellWord(args.source)}`)
30
+ + (args.note === undefined ? '' : ` --note ${shellWord(args.note)}`) : undefined;
31
+ const next = retry === undefined ? undefined : safe(redact(retry, process.env));
32
+ if (args.json) io.stdout(JSON.stringify({ ok: false, code, message, runId: args.runId,
33
+ ...(partial ? { answerRecorded: error.answerRecorded, next } : {}) }));
34
+ else {
35
+ io.stderr(`${code}: ${message}`);
36
+ if (next) io.stderr(`After checking Cloud status, retry with: ${next}`);
37
+ }
38
+ return !partial && cause instanceof CloudFlowError && ['configuration', 'invalid_input', 'unsupported_source'].includes(cause.code) ? 2 : 1;
39
+ }
40
+ }
@@ -3,6 +3,7 @@ import { dirname, resolve } from 'node:path';
3
3
  import { createInterface } from 'node:readline';
4
4
  import { parse as parseYaml } from 'yaml';
5
5
  import { loadAuthoredFlow } from '../authored-flow-loader.js';
6
+ import { isAuthoredFlowPath } from '../direct-input.js';
6
7
  import { ensureIntegrationsConnected, type ConnectPrompt, type ConnectionsOutcome } from '../cloud-connect.js';
7
8
  import { CloudFlowError, cloudRequest, isCloudRecord, type CloudConnectionOptions } from '../cloud-http.js';
8
9
  import { CompileError, compileSpec, kernelToAuthoring } from '../compile.js';
@@ -35,7 +36,9 @@ export async function flowRequirementsForPath(
35
36
  const projectCli = context.projectCli ?? projectCliFor(path);
36
37
  const withCli = { ...context, ...(projectCli === undefined ? {} : { projectCli }) };
37
38
  try {
38
- if (/\.flow\.ts$/iu.test(path)) {
39
+ // Every authored extension flows check accepts; skipping one would let a
40
+ // Cloud submission go out without its integration check.
41
+ if (isAuthoredFlowPath(path)) {
39
42
  const loaded = await loadAuthoredFlow(path);
40
43
  return flowRequirements(loaded.getDefinition(loaded.handle), withCli);
41
44
  }
@@ -77,11 +80,15 @@ export async function currentWorkspaceId(options: CloudConnectionOptions): Promi
77
80
  * contacted when the flow requires no integration, so a flow with no
78
81
  * helpers, sources or repository submits exactly as before; the derived
79
82
  * requirements still come back so a later harness refusal can name its remedy.
80
- * `undefined` only when the source does not load — the submission says why.
83
+ * `undefined` when the source does not load or is an authored extension Cloud
84
+ * does not submit — the submission says why.
81
85
  */
82
86
  export async function ensureFlowConnections(
83
87
  input: FlowConnectionsInput, options: CloudConnectionOptions = {},
84
88
  ): Promise<{ requirements: FlowRequirements; outcome: ConnectionsOutcome } | undefined> {
89
+ // Cloud submission accepts only .flow.ts among authored sources (cloud-run.ts
90
+ // prepareCloudSubmission); never prompt or connect for one it will refuse.
91
+ if (isAuthoredFlowPath(input.path) && !/\.flow\.ts$/iu.test(input.path)) return undefined;
85
92
  const requirements = await flowRequirementsForPath(input.path, input);
86
93
  if (requirements === undefined) return undefined;
87
94
  if (requirements.integrations.length === 0) return { requirements, outcome: { ready: [], connected: [] } };