@relayflows/sdk 2.0.29 → 2.0.31

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 (393) hide show
  1. package/dist/agent-artifacts.d.ts.map +1 -1
  2. package/dist/agent-artifacts.js +2 -9
  3. package/dist/agent-artifacts.js.map +1 -1
  4. package/dist/agent-cwd.d.ts +69 -0
  5. package/dist/agent-cwd.d.ts.map +1 -0
  6. package/dist/agent-cwd.js +140 -0
  7. package/dist/agent-cwd.js.map +1 -0
  8. package/dist/artifact-scan-policy.d.ts +35 -0
  9. package/dist/artifact-scan-policy.d.ts.map +1 -0
  10. package/dist/artifact-scan-policy.js +49 -0
  11. package/dist/artifact-scan-policy.js.map +1 -0
  12. package/dist/authored-completion-record.d.ts +35 -0
  13. package/dist/authored-completion-record.d.ts.map +1 -0
  14. package/dist/authored-completion-record.js +92 -0
  15. package/dist/authored-completion-record.js.map +1 -0
  16. package/dist/authored-completion.d.ts +99 -0
  17. package/dist/authored-completion.d.ts.map +1 -0
  18. package/dist/authored-completion.js +184 -0
  19. package/dist/authored-completion.js.map +1 -0
  20. package/dist/authored-flow-error.d.ts +13 -1
  21. package/dist/authored-flow-error.d.ts.map +1 -1
  22. package/dist/authored-flow-error.js +12 -0
  23. package/dist/authored-flow-error.js.map +1 -1
  24. package/dist/authored-flow-executor.d.ts +7 -30
  25. package/dist/authored-flow-executor.d.ts.map +1 -1
  26. package/dist/authored-flow-executor.js +64 -48
  27. package/dist/authored-flow-executor.js.map +1 -1
  28. package/dist/authored-human.d.ts.map +1 -1
  29. package/dist/authored-human.js +1 -3
  30. package/dist/authored-human.js.map +1 -1
  31. package/dist/authored-node-entry.js +4 -1
  32. package/dist/authored-node-entry.js.map +1 -1
  33. package/dist/authored-node-runner.d.ts +9 -1
  34. package/dist/authored-node-runner.d.ts.map +1 -1
  35. package/dist/authored-node-runner.js +92 -17
  36. package/dist/authored-node-runner.js.map +1 -1
  37. package/dist/authored-root.d.ts +5 -0
  38. package/dist/authored-root.d.ts.map +1 -1
  39. package/dist/authored-root.js +20 -2
  40. package/dist/authored-root.js.map +1 -1
  41. package/dist/authored-step-output.d.ts +35 -10
  42. package/dist/authored-step-output.d.ts.map +1 -1
  43. package/dist/authored-step-output.js +108 -40
  44. package/dist/authored-step-output.js.map +1 -1
  45. package/dist/authored-verdict.d.ts +28 -0
  46. package/dist/authored-verdict.d.ts.map +1 -0
  47. package/dist/authored-verdict.js +79 -0
  48. package/dist/authored-verdict.js.map +1 -0
  49. package/dist/authored-worker-step.d.ts +5 -2
  50. package/dist/authored-worker-step.d.ts.map +1 -1
  51. package/dist/authored-worker-step.js +48 -11
  52. package/dist/authored-worker-step.js.map +1 -1
  53. package/dist/bundle-extensions.d.ts.map +1 -1
  54. package/dist/bundle-extensions.js +4 -7
  55. package/dist/bundle-extensions.js.map +1 -1
  56. package/dist/bundle.d.ts.map +1 -1
  57. package/dist/bundle.js +41 -7
  58. package/dist/bundle.js.map +1 -1
  59. package/dist/canonical.d.ts.map +1 -1
  60. package/dist/canonical.js +18 -6
  61. package/dist/canonical.js.map +1 -1
  62. package/dist/cli/check-worker-surface.d.ts +27 -0
  63. package/dist/cli/check-worker-surface.d.ts.map +1 -0
  64. package/dist/cli/check-worker-surface.js +63 -0
  65. package/dist/cli/check-worker-surface.js.map +1 -0
  66. package/dist/cli/check.d.ts +19 -2
  67. package/dist/cli/check.d.ts.map +1 -1
  68. package/dist/cli/check.js +12 -4
  69. package/dist/cli/check.js.map +1 -1
  70. package/dist/cli/cloud-format.d.ts +9 -0
  71. package/dist/cli/cloud-format.d.ts.map +1 -0
  72. package/dist/cli/cloud-format.js +77 -0
  73. package/dist/cli/cloud-format.js.map +1 -0
  74. package/dist/cli/cloud-live.d.ts +39 -0
  75. package/dist/cli/cloud-live.d.ts.map +1 -0
  76. package/dist/cli/cloud-live.js +325 -0
  77. package/dist/cli/cloud-live.js.map +1 -0
  78. package/dist/cli/cloud-read.d.ts +3 -7
  79. package/dist/cli/cloud-read.d.ts.map +1 -1
  80. package/dist/cli/cloud-read.js +14 -266
  81. package/dist/cli/cloud-read.js.map +1 -1
  82. package/dist/cli/cloud-refusal.d.ts +39 -0
  83. package/dist/cli/cloud-refusal.d.ts.map +1 -0
  84. package/dist/cli/cloud-refusal.js +95 -0
  85. package/dist/cli/cloud-refusal.js.map +1 -0
  86. package/dist/cli/cloud-status-view.d.ts +7 -0
  87. package/dist/cli/cloud-status-view.d.ts.map +1 -0
  88. package/dist/cli/cloud-status-view.js +214 -0
  89. package/dist/cli/cloud-status-view.js.map +1 -0
  90. package/dist/cli/direct-run.d.ts.map +1 -1
  91. package/dist/cli/direct-run.js +16 -2
  92. package/dist/cli/direct-run.js.map +1 -1
  93. package/dist/cli/hn-monitor.d.ts +0 -13
  94. package/dist/cli/hn-monitor.d.ts.map +1 -1
  95. package/dist/cli/hn-monitor.js +2 -1
  96. package/dist/cli/hn-monitor.js.map +1 -1
  97. package/dist/cli/local-agent-remedy.d.ts +80 -0
  98. package/dist/cli/local-agent-remedy.d.ts.map +1 -0
  99. package/dist/cli/local-agent-remedy.js +116 -0
  100. package/dist/cli/local-agent-remedy.js.map +1 -0
  101. package/dist/cli/observer-session.d.ts +41 -0
  102. package/dist/cli/observer-session.d.ts.map +1 -0
  103. package/dist/cli/observer-session.js +78 -0
  104. package/dist/cli/observer-session.js.map +1 -0
  105. package/dist/cli/run.d.ts +30 -2
  106. package/dist/cli/run.d.ts.map +1 -1
  107. package/dist/cli/run.js +186 -24
  108. package/dist/cli/run.js.map +1 -1
  109. package/dist/cli/status.d.ts +9 -0
  110. package/dist/cli/status.d.ts.map +1 -1
  111. package/dist/cli/status.js +31 -2
  112. package/dist/cli/status.js.map +1 -1
  113. package/dist/cli/step-evidence.d.ts +90 -0
  114. package/dist/cli/step-evidence.d.ts.map +1 -0
  115. package/dist/cli/step-evidence.js +259 -0
  116. package/dist/cli/step-evidence.js.map +1 -0
  117. package/dist/cli/step-failure.d.ts +14 -4
  118. package/dist/cli/step-failure.d.ts.map +1 -1
  119. package/dist/cli/step-failure.js +41 -76
  120. package/dist/cli/step-failure.js.map +1 -1
  121. package/dist/cli-commands.d.ts +6 -0
  122. package/dist/cli-commands.d.ts.map +1 -1
  123. package/dist/cli-commands.js +2 -0
  124. package/dist/cli-commands.js.map +1 -1
  125. package/dist/cli.d.ts +10 -0
  126. package/dist/cli.d.ts.map +1 -1
  127. package/dist/cli.js +53 -42
  128. package/dist/cli.js.map +1 -1
  129. package/dist/cloud-read.d.ts +15 -0
  130. package/dist/cloud-read.d.ts.map +1 -1
  131. package/dist/cloud-read.js +20 -1
  132. package/dist/cloud-read.js.map +1 -1
  133. package/dist/cloud-run-record.d.ts +27 -0
  134. package/dist/cloud-run-record.d.ts.map +1 -0
  135. package/dist/cloud-run-record.js +56 -0
  136. package/dist/cloud-run-record.js.map +1 -0
  137. package/dist/cloud-run.d.ts +2 -9
  138. package/dist/cloud-run.d.ts.map +1 -1
  139. package/dist/cloud-run.js +3 -20
  140. package/dist/cloud-run.js.map +1 -1
  141. package/dist/communication/local.d.ts.map +1 -1
  142. package/dist/communication/local.js +2 -1
  143. package/dist/communication/local.js.map +1 -1
  144. package/dist/communication/worker.d.ts +1 -1
  145. package/dist/communication/worker.d.ts.map +1 -1
  146. package/dist/communication/worker.js +13 -5
  147. package/dist/communication/worker.js.map +1 -1
  148. package/dist/compile.d.ts.map +1 -1
  149. package/dist/compile.js +28 -6
  150. package/dist/compile.js.map +1 -1
  151. package/dist/direct-input.d.ts.map +1 -1
  152. package/dist/direct-input.js +21 -1
  153. package/dist/direct-input.js.map +1 -1
  154. package/dist/failure-kinds.d.ts +96 -6
  155. package/dist/failure-kinds.d.ts.map +1 -1
  156. package/dist/failure-kinds.js +29 -2
  157. package/dist/failure-kinds.js.map +1 -1
  158. package/dist/flow-extension-compat.d.ts.map +1 -1
  159. package/dist/flow-extension-compat.js +26 -8
  160. package/dist/flow-extension-compat.js.map +1 -1
  161. package/dist/flow-extension-loader.d.ts +7 -0
  162. package/dist/flow-extension-loader.d.ts.map +1 -1
  163. package/dist/flow-extension-loader.js +48 -22
  164. package/dist/flow-extension-loader.js.map +1 -1
  165. package/dist/flow-requirements.d.ts.map +1 -1
  166. package/dist/flow-requirements.js +24 -9
  167. package/dist/flow-requirements.js.map +1 -1
  168. package/dist/fs-descriptor.d.ts +17 -0
  169. package/dist/fs-descriptor.d.ts.map +1 -0
  170. package/dist/fs-descriptor.js +96 -0
  171. package/dist/fs-descriptor.js.map +1 -0
  172. package/dist/gate-contract.d.ts +7 -0
  173. package/dist/gate-contract.d.ts.map +1 -1
  174. package/dist/gate-contract.js +6 -1
  175. package/dist/gate-contract.js.map +1 -1
  176. package/dist/helper-preflight.d.ts.map +1 -1
  177. package/dist/helper-preflight.js +10 -3
  178. package/dist/helper-preflight.js.map +1 -1
  179. package/dist/helper-reference.d.ts +25 -0
  180. package/dist/helper-reference.d.ts.map +1 -0
  181. package/dist/helper-reference.js +129 -0
  182. package/dist/helper-reference.js.map +1 -0
  183. package/dist/hosted-base-snapshot.d.ts +25 -0
  184. package/dist/hosted-base-snapshot.d.ts.map +1 -0
  185. package/dist/hosted-base-snapshot.js +278 -0
  186. package/dist/hosted-base-snapshot.js.map +1 -0
  187. package/dist/hosted-extension-declarations.d.ts +10 -0
  188. package/dist/hosted-extension-declarations.d.ts.map +1 -0
  189. package/dist/hosted-extension-declarations.js +272 -0
  190. package/dist/hosted-extension-declarations.js.map +1 -0
  191. package/dist/hosted-extension-isolation.d.ts +82 -0
  192. package/dist/hosted-extension-isolation.d.ts.map +1 -0
  193. package/dist/hosted-extension-isolation.js +322 -0
  194. package/dist/hosted-extension-isolation.js.map +1 -0
  195. package/dist/hosted-extension-manifest.d.ts +9 -0
  196. package/dist/hosted-extension-manifest.d.ts.map +1 -0
  197. package/dist/hosted-extension-manifest.js +235 -0
  198. package/dist/hosted-extension-manifest.js.map +1 -0
  199. package/dist/hosted-extension-protocol.d.ts +16 -0
  200. package/dist/hosted-extension-protocol.d.ts.map +1 -0
  201. package/dist/hosted-extension-protocol.js +224 -0
  202. package/dist/hosted-extension-protocol.js.map +1 -0
  203. package/dist/hosted-extension-runtime.d.ts +30 -0
  204. package/dist/hosted-extension-runtime.d.ts.map +1 -0
  205. package/dist/hosted-extension-runtime.js +167 -0
  206. package/dist/hosted-extension-runtime.js.map +1 -0
  207. package/dist/hosted-extension-sandbox-source.d.ts +9 -0
  208. package/dist/hosted-extension-sandbox-source.d.ts.map +1 -0
  209. package/dist/hosted-extension-sandbox-source.js +95 -0
  210. package/dist/hosted-extension-sandbox-source.js.map +1 -0
  211. package/dist/hosted-extension-sandbox.d.ts +39 -0
  212. package/dist/hosted-extension-sandbox.d.ts.map +1 -0
  213. package/dist/hosted-extension-sandbox.js +418 -0
  214. package/dist/hosted-extension-sandbox.js.map +1 -0
  215. package/dist/hosted-project.d.ts +3 -0
  216. package/dist/hosted-project.d.ts.map +1 -0
  217. package/dist/hosted-project.js +19 -0
  218. package/dist/hosted-project.js.map +1 -0
  219. package/dist/hosted-promise-safety.d.ts +12 -0
  220. package/dist/hosted-promise-safety.d.ts.map +1 -0
  221. package/dist/hosted-promise-safety.js +63 -0
  222. package/dist/hosted-promise-safety.js.map +1 -0
  223. package/dist/index.d.ts +3 -1
  224. package/dist/index.d.ts.map +1 -1
  225. package/dist/index.js +2 -0
  226. package/dist/index.js.map +1 -1
  227. package/dist/intrinsic-array.d.ts +3 -0
  228. package/dist/intrinsic-array.d.ts.map +1 -0
  229. package/dist/intrinsic-array.js +14 -0
  230. package/dist/intrinsic-array.js.map +1 -0
  231. package/dist/journal-client.d.ts +3 -1
  232. package/dist/journal-client.d.ts.map +1 -1
  233. package/dist/journal-client.js +17 -2
  234. package/dist/journal-client.js.map +1 -1
  235. package/dist/journal-projection.d.ts +16 -0
  236. package/dist/journal-projection.d.ts.map +1 -0
  237. package/dist/journal-projection.js +108 -0
  238. package/dist/journal-projection.js.map +1 -0
  239. package/dist/json-value.d.ts +6 -1
  240. package/dist/json-value.d.ts.map +1 -1
  241. package/dist/json-value.js +155 -36
  242. package/dist/json-value.js.map +1 -1
  243. package/dist/llm-worker.d.ts +9 -0
  244. package/dist/llm-worker.d.ts.map +1 -1
  245. package/dist/llm-worker.js +16 -2
  246. package/dist/llm-worker.js.map +1 -1
  247. package/dist/local-agent.d.ts +1 -1
  248. package/dist/local-agent.d.ts.map +1 -1
  249. package/dist/local-agent.js +4 -3
  250. package/dist/local-agent.js.map +1 -1
  251. package/dist/named-gate-lowering.js +35 -5
  252. package/dist/named-gate-lowering.js.map +1 -1
  253. package/dist/named-gate-preflight.d.ts +27 -0
  254. package/dist/named-gate-preflight.d.ts.map +1 -0
  255. package/dist/named-gate-preflight.js +47 -0
  256. package/dist/named-gate-preflight.js.map +1 -0
  257. package/dist/named-gates.d.ts +1 -1
  258. package/dist/named-gates.d.ts.map +1 -1
  259. package/dist/named-gates.js +3 -0
  260. package/dist/named-gates.js.map +1 -1
  261. package/dist/observer-link.d.ts +7 -0
  262. package/dist/observer-link.d.ts.map +1 -1
  263. package/dist/observer-link.js +3 -1
  264. package/dist/observer-link.js.map +1 -1
  265. package/dist/plugin-store.d.ts +8 -2
  266. package/dist/plugin-store.d.ts.map +1 -1
  267. package/dist/plugin-store.js +233 -56
  268. package/dist/plugin-store.js.map +1 -1
  269. package/dist/preflight.d.ts.map +1 -1
  270. package/dist/preflight.js +7 -0
  271. package/dist/preflight.js.map +1 -1
  272. package/dist/protocol.d.ts +2 -0
  273. package/dist/protocol.d.ts.map +1 -1
  274. package/dist/redact.d.ts +42 -0
  275. package/dist/redact.d.ts.map +1 -1
  276. package/dist/redact.js +128 -0
  277. package/dist/redact.js.map +1 -1
  278. package/dist/run-projection.d.ts +83 -0
  279. package/dist/run-projection.d.ts.map +1 -0
  280. package/dist/run-projection.js +199 -0
  281. package/dist/run-projection.js.map +1 -0
  282. package/dist/semver-range.d.ts.map +1 -1
  283. package/dist/semver-range.js +27 -15
  284. package/dist/semver-range.js.map +1 -1
  285. package/dist/shell-word.d.ts +19 -0
  286. package/dist/shell-word.d.ts.map +1 -0
  287. package/dist/shell-word.js +23 -0
  288. package/dist/shell-word.js.map +1 -0
  289. package/dist/spec.d.ts +52 -3
  290. package/dist/spec.d.ts.map +1 -1
  291. package/dist/spec.js.map +1 -1
  292. package/dist/step-fields.d.ts +1 -1
  293. package/dist/step-fields.js +1 -1
  294. package/dist/step-fields.js.map +1 -1
  295. package/dist/steps-green.d.ts +27 -0
  296. package/dist/steps-green.d.ts.map +1 -0
  297. package/dist/steps-green.js +164 -0
  298. package/dist/steps-green.js.map +1 -0
  299. package/dist/validate.d.ts.map +1 -1
  300. package/dist/validate.js +39 -8
  301. package/dist/validate.js.map +1 -1
  302. package/dist/worker-lease.d.ts +8 -1
  303. package/dist/worker-lease.d.ts.map +1 -1
  304. package/dist/worker-lease.js +34 -4
  305. package/dist/worker-lease.js.map +1 -1
  306. package/dist/worker.d.ts +8 -0
  307. package/dist/worker.d.ts.map +1 -1
  308. package/dist/worker.js +17 -9
  309. package/dist/worker.js.map +1 -1
  310. package/package.json +3 -7
  311. package/src/agent-artifacts.ts +2 -10
  312. package/src/agent-cwd.ts +139 -0
  313. package/src/artifact-scan-policy.ts +51 -0
  314. package/src/authored-completion-record.ts +119 -0
  315. package/src/authored-completion.ts +203 -0
  316. package/src/authored-flow-error.ts +13 -1
  317. package/src/authored-flow-executor.ts +101 -59
  318. package/src/authored-human.ts +1 -4
  319. package/src/authored-node-entry.ts +4 -1
  320. package/src/authored-node-runner.ts +96 -22
  321. package/src/authored-root.ts +20 -2
  322. package/src/authored-step-output.ts +139 -42
  323. package/src/authored-verdict.ts +87 -0
  324. package/src/authored-worker-step.ts +60 -12
  325. package/src/bundle-extensions.ts +6 -5
  326. package/src/bundle.ts +52 -7
  327. package/src/canonical.ts +20 -6
  328. package/src/cli/check-worker-surface.ts +66 -0
  329. package/src/cli/check.ts +35 -4
  330. package/src/cli/cloud-format.ts +82 -0
  331. package/src/cli/cloud-live.ts +366 -0
  332. package/src/cli/cloud-read.ts +13 -282
  333. package/src/cli/cloud-refusal.ts +113 -0
  334. package/src/cli/cloud-status-view.ts +213 -0
  335. package/src/cli/direct-run.ts +19 -2
  336. package/src/cli/hn-monitor.ts +2 -1
  337. package/src/cli/local-agent-remedy.ts +160 -0
  338. package/src/cli/observer-session.ts +107 -0
  339. package/src/cli/run.ts +233 -28
  340. package/src/cli/status.ts +59 -3
  341. package/src/cli/step-evidence.ts +310 -0
  342. package/src/cli/step-failure.ts +57 -81
  343. package/src/cli-commands.ts +2 -0
  344. package/src/cli.ts +64 -44
  345. package/src/cloud-read.ts +25 -1
  346. package/src/cloud-run-record.ts +64 -0
  347. package/src/cloud-run.ts +5 -24
  348. package/src/communication/local.ts +2 -1
  349. package/src/communication/worker.ts +13 -5
  350. package/src/compile.ts +28 -6
  351. package/src/direct-input.ts +22 -1
  352. package/src/failure-kinds.ts +104 -2
  353. package/src/flow-extension-compat.ts +32 -7
  354. package/src/flow-extension-loader.ts +56 -21
  355. package/src/flow-requirements.ts +24 -9
  356. package/src/fs-descriptor.ts +127 -0
  357. package/src/gate-contract.ts +13 -1
  358. package/src/helper-preflight.ts +10 -3
  359. package/src/helper-reference.ts +130 -0
  360. package/src/hosted-base-snapshot.ts +351 -0
  361. package/src/hosted-extension-declarations.ts +387 -0
  362. package/src/hosted-extension-isolation.ts +457 -0
  363. package/src/hosted-extension-manifest.ts +249 -0
  364. package/src/hosted-extension-protocol.ts +262 -0
  365. package/src/hosted-extension-runtime.ts +266 -0
  366. package/src/hosted-extension-sandbox-source.ts +94 -0
  367. package/src/hosted-extension-sandbox.ts +489 -0
  368. package/src/hosted-project.ts +18 -0
  369. package/src/hosted-promise-safety.ts +68 -0
  370. package/src/index.ts +20 -0
  371. package/src/intrinsic-array.ts +14 -0
  372. package/src/journal-client.ts +19 -2
  373. package/src/journal-projection.ts +106 -0
  374. package/src/json-value.ts +184 -32
  375. package/src/llm-worker.ts +16 -2
  376. package/src/local-agent.ts +4 -2
  377. package/src/named-gate-lowering.ts +35 -5
  378. package/src/named-gate-preflight.ts +47 -0
  379. package/src/named-gates.ts +3 -0
  380. package/src/observer-link.ts +10 -1
  381. package/src/plugin-store.ts +323 -56
  382. package/src/preflight.ts +7 -0
  383. package/src/protocol.ts +2 -0
  384. package/src/redact.ts +111 -0
  385. package/src/run-projection.ts +247 -0
  386. package/src/semver-range.ts +39 -14
  387. package/src/shell-word.ts +24 -0
  388. package/src/spec.ts +54 -3
  389. package/src/step-fields.ts +1 -1
  390. package/src/steps-green.ts +168 -0
  391. package/src/validate.ts +35 -8
  392. package/src/worker-lease.ts +35 -5
  393. package/src/worker.ts +27 -11
package/src/cli/run.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { onWorkerFailure } from '../worker-lease.js';
1
2
  import { communicationInstruction } from '../communication/spec.js';
2
3
  import { checkCommunicationEnvironment, CommunicationEnvironmentError } from '../communication/preflight.js';
3
4
  import { parseHumanRecipient } from '../human-to.js';
@@ -9,25 +10,33 @@ import { AuthoredFlowExecutionError, AuthoredHumanParked, type AuthoredHumanWait
9
10
  import { answerCommand, resumeCommand } from '../authored-human.js';
10
11
  import { join, resolve } from 'node:path';
11
12
  import type { ProgressEvent } from '../progress.js';
13
+ import type { JournalEvent } from '../journal-reader.js';
12
14
  import { toKernelSpec } from '../compile.js';
13
15
  import { socketPathFor } from '../daemon-connection.js';
14
16
  import { ensureDaemon, type EnsureDaemonOptions } from '../daemon-lifecycle.js';
15
17
  import { isAuthoredFlowPath } from '../direct-input.js';
16
18
  import { daemonRefusal } from './daemon-refusal.js';
17
- import type { RunFailureKind, RunWarningKind, StepFailedDetails } from '../failure-kinds.js';
19
+ import {
20
+ authoredInput, authoredWorkerRemedy, localAgentRemedy,
21
+ type AuthoredInput, type LocalAgentRemedy,
22
+ } from './local-agent-remedy.js';
23
+ import type { ParkCause, RunFailureKind, RunWarningKind, StepFailedDetails } from '../failure-kinds.js';
18
24
  import { inspectionHint, renderInspection, renderStepEvidence, stepFailureDetails } from './step-failure.js';
19
25
  import { JournalClient, JournalProtocolError } from '../journal-client.js';
20
26
  import { attachLocalAgent } from '../local-agent.js';
21
27
  import { LlmWorker } from '../llm-worker.js';
22
- import { readAuthoredRootMetadata, resumeDurableAuthoredFlow } from '../authored-root.js';
28
+ import { readAuthoredRootMetadata, resumeDurableAuthoredFlow, type AuthoredRootMetadata } from '../authored-root.js';
23
29
  import type {
24
30
  RunCompletionReason,
25
31
  RunOutcome,
26
32
  RunStatus,
27
33
  } from '../protocol.js';
28
34
  import type { StepType } from '../spec.js';
35
+ import {
36
+ singleLineCompletionDetail,
37
+ type LoweredCompletionReason,
38
+ } from '../authored-completion.js';
29
39
  import { DEFAULT_LOCAL_AGENT_CAPACITY } from '../worker-slots.js';
30
- import type { LoweredCompletionReason } from '../authored-flow-executor.js';
31
40
  import {
32
41
  checkFlow,
33
42
  type CheckReport,
@@ -57,9 +66,27 @@ export interface RunReport {
57
66
  socketPath?: string;
58
67
  status?: RunStatus;
59
68
  completionReason?: RunCompletionReason;
69
+ /**
70
+ * What the body passed to `done(reason, { detail })`, normalized: redacted,
71
+ * trimmed, and bounded to 2,000 code points. Absent for every one-argument
72
+ * call, so an existing report keeps its exact shape — and absent on the
73
+ * paths where no authored body declared the outcome at all.
74
+ */
75
+ completionDetail?: string;
60
76
  completedSteps?: number;
61
77
  reuse?: { fromRunId: string; reusedSteps: number; executedSteps: number };
62
78
  parkedStep?: ParkedStep;
79
+ /**
80
+ * Why an exit-3 park happened, when classification established it.
81
+ *
82
+ * Carried structurally so a consumer — including this package's own authored
83
+ * boundaries — can tell "nothing is attached to run this step" from "the
84
+ * kernel is waiting for a human to recover it" without matching on the
85
+ * rendered message. Absent means the cause was not established, never
86
+ * `worker_unavailable` by default: recommending a worker for a `needs_human`
87
+ * park is the wrong answer, and guessing it is worse than saying nothing.
88
+ */
89
+ parkCause?: ParkCause;
63
90
  /** The open `f.human` question a parked authored run is waiting on. */
64
91
  humanWait?: AuthoredHumanWait;
65
92
  /** `flows answer` only: the answer it recorded. */
@@ -91,6 +118,10 @@ export interface RunLifecycleOptions {
91
118
  onPtyReady?: (path: string) => void;
92
119
  reuseFromRunId?: string;
93
120
  onProgress?: (event: ProgressEvent) => void;
121
+ /** Stream the run's journal entries while it runs (YAML runs, via `run.start {watch}` / `run.watch`). */
122
+ onJournalEntry?: (entry: JournalEvent) => void;
123
+ /** An authored root was admitted: its id is known before its body runs. */
124
+ onRunStarted?: (run: { runId: string; flow: string; resumed?: boolean }) => void;
94
125
  localAgent?: boolean;
95
126
  /** `--agent-capacity`: the local workers' concurrency; the default is `DEFAULT_LOCAL_AGENT_CAPACITY`. */
96
127
  agentCapacity?: number;
@@ -158,7 +189,8 @@ async function executeCheckedFlow(
158
189
  const { attachCommunicationWorkers } = await import('../communication/local.js');
159
190
  communicationWorkers = await attachCommunicationWorkers(spec, socketPath, dataDir);
160
191
  }
161
- const outcome = await client.runStart(spec, options.reuseFromRunId);
192
+ if (options.onJournalEntry !== undefined) client.on('entry', options.onJournalEntry);
193
+ const outcome = await startWatched(client, spec, options);
162
194
  const execution = await classifyOutcome(client, 'run', outcome, base, socketPath, { ...options, dataDir });
163
195
  if (options.reuseFromRunId !== undefined) {
164
196
  execution.report.reuse = await reuseSummary(client, outcome.run_id, options.reuseFromRunId);
@@ -178,10 +210,33 @@ async function executeCheckedFlow(
178
210
  }
179
211
  return protocolFailure('run', base, socketPath, communicationWorkers?.failure ?? localAgent?.failure ?? error);
180
212
  } finally {
213
+ if (options.onJournalEntry !== undefined) client.off('entry', options.onJournalEntry);
181
214
  try { await communicationWorkers?.close(); } finally { try { await localAgent?.close(); } finally { client.close(); } }
182
215
  }
183
216
  }
184
217
 
218
+ /**
219
+ * `run.start`, streaming the run's entries to `onJournalEntry` when one is
220
+ * given. A daemon that predates `watch` refuses the field while decoding,
221
+ * before any run exists, so starting again without it is the same request
222
+ * minus the observation — never a second run.
223
+ */
224
+ async function startWatched(
225
+ client: JournalClient,
226
+ spec: ReturnType<typeof toKernelSpec>,
227
+ options: RunLifecycleOptions,
228
+ ): Promise<RunOutcome> {
229
+ const onEntry = options.onJournalEntry;
230
+ if (onEntry === undefined) return client.runStart(spec, options.reuseFromRunId);
231
+ try {
232
+ return await client.runStart(spec, options.reuseFromRunId, undefined, true);
233
+ } catch (error) {
234
+ if (!(error instanceof JournalProtocolError) || error.code !== 'bad_request'
235
+ || !/unknown field `watch`/.test(error.message)) throw error;
236
+ return await client.runStart(spec, options.reuseFromRunId);
237
+ }
238
+ }
239
+
185
240
  export async function resumeFlow(
186
241
  runId: string,
187
242
  dataDir: string,
@@ -196,16 +251,34 @@ export async function resumeFlow(
196
251
  let communicationWorkers: Awaited<ReturnType<typeof import('../communication/local.js').attachCommunicationWorkers>> | undefined;
197
252
  let authoredAgent: Awaited<ReturnType<typeof attachLocalAgent>> | undefined;
198
253
  let authoredLlm: LlmWorker | undefined;
254
+ let llmFailure: unknown;
199
255
  let authoredLlmClient: JournalClient | undefined;
256
+ // Hoisted so the catch below can build the authored park remedy from the same
257
+ // metadata this resume was admitted against — the journal is the only place
258
+ // the flow path and the original `--input` survive a process boundary.
259
+ let authoredRoot: AuthoredRootMetadata | undefined;
200
260
  const workerCapacity = options.agentCapacity ?? DEFAULT_LOCAL_AGENT_CAPACITY;
201
261
  try {
202
- const authoredRoot = await readAuthoredRootMetadata(client, runId);
262
+ authoredRoot = await readAuthoredRootMetadata(client, runId);
203
263
  if (authoredRoot !== undefined) {
264
+ // Both worker-surface mismatches are refusals, not protocol failures.
265
+ // They used to throw bare `Error`s, which landed on `protocol_error`
266
+ // ("RUN <id> unknown") and told nobody what to do instead; and the
267
+ // second one is the exact complaint this change answers — `--local-agent`
268
+ // accepted on a run that cannot grow one is worse than rejected, because
269
+ // the run parks again with a message that has not changed.
204
270
  if (authoredRoot.localAgentStream !== undefined && !options.localAgent) {
205
- throw new Error('authored root requires --local-agent to resume its pinned worker surface');
271
+ return localAgentRefusal(base, runId, socketPath,
272
+ `Run "${runId}" was started with a local agent worker surface, so resuming it needs the same flag.`
273
+ + ` To continue this run: ${resumeCommand(runId, dataDir, true)}.`);
206
274
  }
207
275
  if (authoredRoot.localAgentStream === undefined && options.localAgent) {
208
- throw new Error('authored root was started without a local agent worker surface');
276
+ return localAgentRefusal(base, runId, socketPath,
277
+ `Run "${runId}" was started without a local agent worker surface:`
278
+ + ' --local-agent is admitted at run start; start a new run.'
279
+ + localAgentRemedy(authoredWorkerRemedy('worker_unavailable', false, {
280
+ path: authoredRoot.flowPath, input: authoredInputArgument(authoredRoot), dataDir,
281
+ })));
209
282
  }
210
283
  if (options.localAgent) {
211
284
  authoredAgent = await attachLocalAgent(
@@ -215,6 +288,10 @@ export async function resumeFlow(
215
288
  await authoredLlmClient.connect();
216
289
  await authoredLlmClient.hello('flows-authored-resume-llm');
217
290
  authoredLlm = new LlmWorker(authoredLlmClient, `${authoredAgent.stream}-llm`, workerCapacity);
291
+ authoredLlm.on('error', onWorkerFailure('resume-llm', error => {
292
+ llmFailure = error;
293
+ client.close();
294
+ }));
218
295
  await authoredLlm.attach();
219
296
  }
220
297
  const result = await resumeDurableAuthoredFlow(runId, client, {
@@ -239,6 +316,13 @@ export async function resumeFlow(
239
316
  communicationWorkers = await attachCommunicationWorkers(spec, socketPath, dataDir);
240
317
  }
241
318
  }
319
+ const onEntry = options.onJournalEntry;
320
+ if (onEntry !== undefined) {
321
+ client.on('entry', onEntry);
322
+ // Observation never decides a resume: a watch the daemon refuses leaves
323
+ // the run unprojected, and the resume below reports the run's own fate.
324
+ await client.runWatch(runId).catch(() => client.off('entry', onEntry));
325
+ }
242
326
  let outcome = await client.runResume(runId, options.allowHumanInfluenced);
243
327
  if (await resumeHelperEffect(client, runId, dataDir)) {
244
328
  outcome = await client.runResume(runId, options.allowHumanInfluenced);
@@ -269,11 +353,41 @@ export async function resumeFlow(
269
353
  if (error instanceof AuthoredFlowExecutionError && (error.code === 'step_failed' || error.code === 'gate_failed')) {
270
354
  return authoredStepFailure('resume', base, socketPath, error, runId);
271
355
  }
356
+ // An authored resume that parks for want of a worker is a park, reported
357
+ // like the run path's — and with the same remedy, because an authored root
358
+ // admits its worker at run start: resuming again cannot attach one, so the
359
+ // only honest instruction is a new run carrying the same `--input`.
360
+ // `runId` here is the CHILD run holding the evidence; the root this resume
361
+ // named stays separate, so `flows` can still collect it.
362
+ if (error instanceof AuthoredFlowExecutionError && (error.code === 'agent_parked' || error.code === 'llm_parked')) {
363
+ return {
364
+ exitCode: 3,
365
+ report: {
366
+ ...base,
367
+ ok: false,
368
+ runId: error.runId ?? runId,
369
+ rootRunId: runId,
370
+ socketPath,
371
+ status: 'parked',
372
+ parkCause: error.parkCause,
373
+ diagnostics: [...base.diagnostics, {
374
+ severity: 'parked',
375
+ kind: 'run_parked',
376
+ message: error.message + localAgentRemedy(authoredWorkerRemedy(
377
+ error.parkCause, options.localAgent === true,
378
+ { path: authoredRoot?.flowPath, input: authoredInputArgument(authoredRoot), dataDir },
379
+ )),
380
+ }],
381
+ },
382
+ };
383
+ }
272
384
  if (error instanceof AuthoredHumanParked) {
273
385
  return authoredHumanParked('resume', base, socketPath, error, { dataDir, localAgent: options.localAgent === true });
274
386
  }
275
387
  if (!(error instanceof JournalProtocolError) || error.code !== 'run_not_found') {
276
- return protocolFailure('resume', base, socketPath, error, runId);
388
+ const cause = error instanceof AuthoredFlowExecutionError
389
+ ? error : llmFailure ?? authoredAgent?.failure ?? error;
390
+ return protocolFailure('resume', base, socketPath, cause, runId);
277
391
  }
278
392
  return {
279
393
  exitCode: 2,
@@ -290,6 +404,7 @@ export async function resumeFlow(
290
404
  };
291
405
  } finally {
292
406
  try {
407
+ if (options.onJournalEntry !== undefined) client.off('entry', options.onJournalEntry);
293
408
  await communicationWorkers?.close();
294
409
  await authoredLlm?.close();
295
410
  authoredLlmClient?.close();
@@ -300,6 +415,45 @@ export async function resumeFlow(
300
415
  }
301
416
  }
302
417
 
418
+ /**
419
+ * A `--local-agent` mismatch, refused before the resume touches the journal.
420
+ *
421
+ * Exit 2 and `ok` left false by `emptyReport`: nothing ran, so this is the same
422
+ * shape as every other pre-journal refusal on this surface (docs/SURFACE.md §5).
423
+ * It returns rather than throwing so no worker is attached on the way out.
424
+ */
425
+ function localAgentRefusal(
426
+ base: RunReport, runId: string, socketPath: string, message: string,
427
+ ): RunExecution {
428
+ return {
429
+ exitCode: 2,
430
+ report: { ...base, runId, socketPath,
431
+ diagnostics: [...base.diagnostics, { severity: 'refusal', kind: 'local_agent_unavailable', message }] },
432
+ };
433
+ }
434
+
435
+ /**
436
+ * The `--input` argument a new run of this authored root would have to repeat,
437
+ * rendered back from the journaled metadata.
438
+ *
439
+ * `absent` when the root recorded no input, and also when the recorded input is
440
+ * something `JSON.stringify` cannot render — the remedy formatter then says an
441
+ * input is required instead of printing a command that would be refused as
442
+ * `input_invalid`. What it must never do is substitute `{}`: that is a
443
+ * different invocation of the flow than the one that parked.
444
+ *
445
+ * Unlike `runDirectFlow`, this side has only the decoded value: the journal
446
+ * records the input, not the `--input` word that carried it, so a run started
447
+ * from a file comes back as inline JSON. `authoredInput` is what keeps that
448
+ * honest about its own size — a recorded input can be larger than a shell
449
+ * argument even when the file that supplied it was unremarkable.
450
+ */
451
+ function authoredInputArgument(metadata: AuthoredRootMetadata | undefined): AuthoredInput {
452
+ if (metadata === undefined || !metadata.inputPresent) return { kind: 'absent' };
453
+ const rendered = JSON.stringify(metadata.input);
454
+ return authoredInput(typeof rendered === 'string' ? rendered : undefined);
455
+ }
456
+
303
457
  /**
304
458
  * A step that ran and failed, reported as the run failure it is.
305
459
  *
@@ -405,15 +559,30 @@ export function authoredCompletion(
405
559
  command: RunCommand,
406
560
  base: RunReport,
407
561
  socketPath: string,
408
- result: { name: string; completionReason: LoweredCompletionReason; journalSteps: readonly unknown[] },
562
+ result: {
563
+ name: string;
564
+ completionReason: LoweredCompletionReason;
565
+ completionDetail?: string;
566
+ journalSteps: readonly unknown[];
567
+ },
409
568
  runId: string | undefined,
410
569
  ): RunExecution {
411
570
  const common: RunReport = {
412
571
  ...fromBase(command, base),
413
572
  ...(runId === undefined ? {} : { runId }),
414
573
  socketPath,
574
+ ...(result.completionDetail === undefined ? {} : { completionDetail: result.completionDetail }),
415
575
  completedSteps: result.journalSteps.length,
416
576
  };
577
+ // Two spellings of the same fact, on purpose. `detail` is the structured
578
+ // one — the `StepFailedDetails` key the diagnostic already declares, here
579
+ // describing the AUTHOR's verdict rather than the daemon's account of a
580
+ // failing step — and keeps the body's own line breaks. `said` is the same
581
+ // text folded onto one line for the message, because Cloud renders a run's
582
+ // `error` through a view that elides the middle of a long one.
583
+ const detail = result.completionDetail;
584
+ const evidence = detail === undefined ? {} : { detail };
585
+ const said = detail === undefined ? '' : singleLineCompletionDetail(detail);
417
586
  switch (result.completionReason) {
418
587
  case 'success':
419
588
  return {
@@ -427,7 +596,9 @@ export function authoredCompletion(
427
596
  ...common, ok: true, status: 'completed', completionReason: 'success',
428
597
  diagnostics: [...base.diagnostics, {
429
598
  severity: 'declined', kind: 'run_declined',
430
- message: 'Flow deliberately chose not to act on this input.',
599
+ ...evidence,
600
+ message: 'Flow deliberately chose not to act on this input.'
601
+ + (detail === undefined ? '' : ` ${said}`),
431
602
  }],
432
603
  },
433
604
  };
@@ -438,7 +609,9 @@ export function authoredCompletion(
438
609
  ...common, ok: false, status: 'parked',
439
610
  diagnostics: [...base.diagnostics, {
440
611
  severity: 'parked', kind: 'run_parked',
441
- message: `Flow "${result.name}" needs_human; see the journal for accumulated blockers.`,
612
+ ...evidence,
613
+ message: `Flow "${result.name}" needs_human; see the journal for accumulated blockers.`
614
+ + (detail === undefined ? '' : ` ${said}`),
442
615
  }],
443
616
  },
444
617
  };
@@ -454,9 +627,16 @@ export function authoredCompletion(
454
627
  ...common, ok: false, status: 'failed', completionReason: 'step_failed',
455
628
  diagnostics: [...base.diagnostics, {
456
629
  severity: 'failure', kind: 'step_failed',
457
- message: `Flow "${result.name}" declared done("step_failed"): its own checks did not pass. `
458
- + 'No step failed, so there is no step-level evidence to inspect; the journal holds '
459
- + 'every step the flow ran before it decided.',
630
+ ...evidence,
631
+ // With a detail, the flow's own account REPLACES the generic
632
+ // sentence: "no step-level evidence to inspect" is the only thing
633
+ // there is to say when the body said nothing, and saying it
634
+ // beside a real explanation would bury the explanation.
635
+ message: detail === undefined
636
+ ? `Flow "${result.name}" declared done("step_failed"): its own checks did not pass. `
637
+ + 'No step failed, so there is no step-level evidence to inspect; the journal holds '
638
+ + 'every step the flow ran before it decided.'
639
+ : `Flow "${result.name}" declared done("step_failed"): ${said}`,
460
640
  }],
461
641
  },
462
642
  };
@@ -664,25 +844,14 @@ export async function classifyOutcome(
664
844
  report: {
665
845
  ...report,
666
846
  parkedStep,
847
+ parkCause: needsHuman ? 'needs_human' : 'worker_unavailable',
667
848
  diagnostics: [...report.diagnostics, {
668
849
  severity: 'parked',
669
850
  kind: 'run_parked',
670
851
  message: needsHuman
671
852
  ? `Run "${current.run_id}" parked at step "${parkedStep.id}" (${parkedStep.type}): waiting for human recovery after the worker attempt failed.`
672
853
  : `Run "${current.run_id}" parked at step "${parkedStep.id}" (${parkedStep.type}): no worker is attached for step type "${parkedStep.type}".`
673
- // Only suggest the `--local-agent` remedy for YAML flows.
674
- // Authored TS flows require `--input`; the bare command below
675
- // would be refused (Cursor Bugbot flagged as LOW on flows#293).
676
- // For TS we omit the hint rather than fabricate a syntactically
677
- // valid but semantically wrong command — the direct-run refusal
678
- // for TS already names its own missing --input.
679
- + (command === 'run'
680
- && parkedStep.type === 'agent'
681
- && !options.localAgent
682
- && base.path !== undefined
683
- && !isAuthoredFlowPath(base.path)
684
- ? ` To start a new run with a local agent worker: flows run --local-agent '${base.path.replace(/'/g, "'\\''")}'. Declared workspace or stream surfaces require a worker that holds their pins.`
685
- : ''),
854
+ + localAgentRemedy(declarativeWorkerRemedy(command, current.run_id, parkedStep, base, options)),
686
855
  }],
687
856
  },
688
857
  };
@@ -692,6 +861,39 @@ export async function classifyOutcome(
692
861
  ), current.run_id);
693
862
  }
694
863
 
864
+ /**
865
+ * Which remedy a worker park gets from the declarative classifier.
866
+ *
867
+ * An authored `.flow.ts` is deliberately SILENT here, and the suppression is
868
+ * tested first on purpose. Its remedy is a new run carrying the same `--input`,
869
+ * an argument this function does not have; `runDirectFlow` and `resumeFlow`
870
+ * render it once at the authored boundary where the input (or the journaled
871
+ * metadata holding it) is in hand. `classifyOutcome` also runs once per
872
+ * authored `f.agent` CHILD run, so answering here as well would append a
873
+ * remedy to every child message on the way out — and, on resume, a second
874
+ * contradictory one after the attached-worker branch below.
875
+ *
876
+ * `llm` parks get nothing, unchanged: the declarative verbs attach an
877
+ * agent-only worker (local-agent.ts advertises `['agent']`), so `--local-agent`
878
+ * is not a remedy for an `llm` step outside the authored path, which attaches
879
+ * an `LlmWorker` of its own.
880
+ */
881
+ function declarativeWorkerRemedy(
882
+ command: RunCommand,
883
+ runId: string,
884
+ parkedStep: ParkedStep,
885
+ base: CheckReport | RunReport,
886
+ options: RunLifecycleOptions,
887
+ ): LocalAgentRemedy {
888
+ const dataDir = options.dataDir;
889
+ if (parkedStep.type !== 'agent') return { kind: 'none' };
890
+ if (base.path !== undefined && isAuthoredFlowPath(base.path)) return { kind: 'none' };
891
+ if (options.localAgent === true) return { kind: 'attached' };
892
+ if (command === 'resume') return { kind: 'spec-resume', runId, dataDir };
893
+ if (command === 'run' && base.path !== undefined) return { kind: 'spec-run', path: base.path, dataDir };
894
+ return { kind: 'none' };
895
+ }
896
+
695
897
  interface OutOfBandInspection {
696
898
  status: RunStatus;
697
899
  parkedStep?: ParkedStep;
@@ -742,6 +944,9 @@ async function inspectOutOfBandStep(
742
944
  };
743
945
  }
744
946
 
947
+ // Match kernel/relayflowd/src/server/client.rs: allow the lease sweep to dispatch a retry.
948
+ const LEASE_SWEEP_GRACE_MS = 5_000;
949
+
745
950
  async function waitForRunningStep(
746
951
  client: JournalClient,
747
952
  runId: string,
@@ -760,7 +965,7 @@ async function waitForRunningStep(
760
965
  });
761
966
  while (true) {
762
967
  throwIfCanceled(options.signal, runningStep.id);
763
- const remainingMs = leaseDeadlineMs - Date.now();
968
+ const remainingMs = leaseDeadlineMs + LEASE_SWEEP_GRACE_MS - Date.now();
764
969
  if (remainingMs <= 0) {
765
970
  throw new Error(
766
971
  `worker lease for step "${runningStep.id}" expired at ${leaseDeadlineMs} without completion`,
package/src/cli/status.ts CHANGED
@@ -13,6 +13,8 @@ import { AUTHORED_STEP_STREAM, foldAuthoredStepRecords, type AuthoredStepRecord
13
13
  import { canonicalize } from '../canonical.js';
14
14
  import { DEFAULT_DATA_DIR } from '../daemon-connection.js';
15
15
  import { JournalReadError, walkJournal, type JournalEvent } from '../journal-client.js';
16
+ import { singleLineCompletionDetail } from '../authored-completion.js';
17
+ import { authoredVerdictOf, type AuthoredVerdict } from '../authored-verdict.js';
16
18
  import { redact } from '../redact.js';
17
19
  import { foldRunState, RunStateError, type RunView, type StepView } from '../run-state.js';
18
20
  import { DATA_DIR_ENV, RUN_ID_ENV, STEP_ID_ENV } from '../step-env.js';
@@ -32,11 +34,25 @@ export interface StatusArgs {
32
34
  * module keeps its property of opening one file and no socket.
33
35
  */
34
36
  cloud?: true;
37
+ /**
38
+ * `--watch`: redraw the hosted page until the run is terminal.
39
+ *
40
+ * Only with `--cloud`. A local `flows status` reads one journal file and
41
+ * returns; there is no loop for it to hang in, so `--watch` without
42
+ * `--cloud` is a contradiction rather than a narrower request, and is
43
+ * refused with the rest of the invocation. Handled in `cli/cloud-live.ts`.
44
+ */
45
+ watch?: true;
35
46
  }
36
47
 
37
48
  export const DEFAULT_TAIL_LINES = 20;
38
49
  /** A gate's `detail` may be 2,000 chars (engine/remote.rs); the view shows the head. */
39
50
  const DETAIL_LIMIT = 1024;
51
+ // An authored completion detail is NOT held to `DETAIL_LIMIT`. That bound is
52
+ // the head of a gate render the journal holds in full; this text is already
53
+ // bounded to 2,000 code points at `done()`, it is the whole of what the flow
54
+ // said about its own verdict, and cutting it at 1,024 would drop the finding
55
+ // as often as not.
40
56
  const BUSY_RETRIES = 5;
41
57
  const BUSY_RETRY_DELAY_MS = 50;
42
58
 
@@ -64,6 +80,7 @@ export interface StatusOptions {
64
80
  export function parseStatusArgs(args: readonly string[]): StatusArgs | undefined {
65
81
  let json = false;
66
82
  let cloud = false;
83
+ let watch = false;
67
84
  let dataDir: string | undefined;
68
85
  let tail: number | undefined;
69
86
  const positionals: string[] = [];
@@ -75,6 +92,9 @@ export function parseStatusArgs(args: readonly string[]): StatusArgs | undefined
75
92
  } else if (argument === '--cloud') {
76
93
  if (cloud) return undefined;
77
94
  cloud = true;
95
+ } else if (argument === '--watch') {
96
+ if (watch) return undefined;
97
+ watch = true;
78
98
  } else if (argument === '--data-dir' || argument === '--tail') {
79
99
  const value = args[++index];
80
100
  if (value === undefined || value.length === 0 || value.startsWith('-')) return undefined;
@@ -98,9 +118,11 @@ export function parseStatusArgs(args: readonly string[]): StatusArgs | undefined
98
118
  // but a contradiction, and is refused as an invocation rather than silently
99
119
  // ignored. A hosted run id is mandatory: there is no ambient one.
100
120
  if (cloud && (dataDir !== undefined || tail !== undefined || positionals.length === 0)) return undefined;
121
+ if (watch && !cloud) return undefined;
101
122
  return {
102
123
  command: 'status', json,
103
124
  ...(cloud ? { cloud: true as const } : {}),
125
+ ...(watch ? { watch: true as const } : {}),
104
126
  ...(dataDir === undefined ? {} : { dataDir }),
105
127
  ...(tail === undefined ? {} : { tail }),
106
128
  ...(positionals[0] === undefined ? {} : { runId: positionals[0] }),
@@ -178,7 +200,7 @@ export async function runStatus(args: StatusArgs, io: CliIo, options: StatusOpti
178
200
  }
179
201
  }
180
202
 
181
- const presented = present(view, thisStep, tails, env);
203
+ const presented = present(view, thisStep, tails, env, authoredVerdictOf(taken.events));
182
204
  if (args.json) {
183
205
  const authored = authoredSteps(taken.events, env);
184
206
  io.stdout(canonicalize({
@@ -237,13 +259,41 @@ function authoredSteps(events: readonly JournalEvent[], env: NodeJS.ProcessEnv):
237
259
  }
238
260
 
239
261
  type PresentedStep = StepView & { tails: StepTails };
240
- type Presented = Omit<RunView, 'steps'> & { this_step: string | null; steps: PresentedStep[] };
262
+ type Presented = Omit<RunView, 'steps'> & {
263
+ this_step: string | null;
264
+ steps: PresentedStep[];
265
+ /**
266
+ * The verdict the BODY declared, beside — never instead of — the kernel
267
+ * facts above. `status`, `completion_reason` and every step stay exactly
268
+ * what the journal recorded, because they are true: an authored
269
+ * `step_failed` run completes with a root step that succeeded.
270
+ *
271
+ * Present only when the body actually said why. A one-argument `done()`
272
+ * adds nothing a reader could not already see from `completion_reason`, and
273
+ * emitting a null here for every legacy and non-authored run would change a
274
+ * shape that nobody asked to change.
275
+ */
276
+ authored_completion?: { reason: string; detail: string };
277
+ };
241
278
 
242
279
  /** Apply the redaction and size bounds the on-disk model does not have. */
243
- function present(view: RunView, thisStep: string | null, tails: Map<string, StepTails>, env: NodeJS.ProcessEnv): Presented {
280
+ function present(
281
+ view: RunView,
282
+ thisStep: string | null,
283
+ tails: Map<string, StepTails>,
284
+ env: NodeJS.ProcessEnv,
285
+ authored: AuthoredVerdict | null,
286
+ ): Presented {
244
287
  return {
245
288
  ...view,
246
289
  this_step: thisStep,
290
+ ...(authored?.detail === undefined ? {} : {
291
+ // Redacted again on the way out. It was redacted before it was
292
+ // journaled, by this same redactor against a different environment;
293
+ // doing it here too costs nothing and keeps this module the one place
294
+ // that decides what reaches an agent-facing page.
295
+ authored_completion: { reason: authored.reason, detail: redact(authored.detail, env) },
296
+ }),
247
297
  steps: view.steps.map((step) => ({
248
298
  ...step,
249
299
  // An agent names the files it writes, and it inherits this process's
@@ -307,6 +357,12 @@ function renderText(view: Presented, partial: string[], wantTails: boolean): str
307
357
  + (view.spend.dollars_unmetered ? ' (unmetered)' : '');
308
358
  const finished = view.completion_reason === null ? '' : ` finished ${view.completion_reason}`;
309
359
  lines.push(`RUN ${view.run_id} ${safe(view.name)} ${view.status} started ${formatDuration(view.now_ms - view.spawned_at_ms)} ago${finished} spend ${spend}`);
360
+ // Labelled, so nobody reads a failure explanation as a claim that the run's
361
+ // kernel status is anything other than the line above says it is.
362
+ if (view.authored_completion !== undefined) {
363
+ lines.push(`authored done("${safe(view.authored_completion.reason)}"): `
364
+ + safe(singleLineCompletionDetail(view.authored_completion.detail)));
365
+ }
310
366
  const counts = (['done', 'running', 'pending', 'backoff', 'waiting', 'needs_human'] as const)
311
367
  .filter((key) => view.counts[key] > 0).map((key) => `${view.counts[key]} ${key}`);
312
368
  // The denominator is always printed, even for zero steps, so a reader can