@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
@@ -1,4 +1,4 @@
1
- import { resolve } from 'node:path';
1
+ import { isAbsolute, relative, sep } from 'node:path';
2
2
  import type { AuthoredBudget } from './authored-budget.js';
3
3
  import { parseBudget } from './budget.js';
4
4
  import type { AgentOptions, AgentResult, LlmOptions, NamedGate } from '@relayflows/surface';
@@ -7,9 +7,10 @@ import { checkAuthoredFlow } from './cli/check.js';
7
7
  import { classifyOutcome, type RunLifecycleOptions, type RunReport } from './cli/run.js';
8
8
  import type { PreflightDiagnostic } from './preflight.js';
9
9
  import { AuthoredFlowExecutionError } from './authored-flow-error.js';
10
+ import { agentCwdDeclarationError, agentCwdTransportError } from './agent-cwd.js';
10
11
  import type { JournalClient } from './journal-client.js';
11
12
  import { SPEC_SCHEMA_VERSION, type FlowSpec, type PermissionsSpec, type StepSpec } from './spec.js';
12
- import { isSurfaceCompletionReason, readCompletedStepOutput, readSuccessfulOutput, type AuthoredStepContext } from './authored-step-output.js';
13
+ import { isSurfaceCompletionReason, readCompletedStepOutput, readRecordedOutcome, readSuccessfulOutput, type AuthoredStepContext, type RecordedRunOutcome } from './authored-step-output.js';
13
14
  import type { AuthoredFlowJournalStep } from './authored-flow-executor.js';
14
15
  import { snapshotJsonValue } from './json-value.js';
15
16
  import { authoredChildAdmissionKey } from './authored-admission.js';
@@ -77,7 +78,7 @@ export function authoredWorkerRunner(
77
78
  const execution = await classifyOutcome(journal, 'run', outcome, report, '', waitOptions);
78
79
  if (execution.exitCode === 3) {
79
80
  const parked = execution.report.parkedStep;
80
- throw new AuthoredFlowExecutionError(
81
+ const error = new AuthoredFlowExecutionError(
81
82
  step.type === 'llm' ? 'llm_parked' : 'agent_parked',
82
83
  execution.report.diagnostics.at(-1)?.message
83
84
  ?? `flow "${definition.name}" step "${id}" parked`
@@ -86,6 +87,14 @@ export function authoredWorkerRunner(
86
87
  undefined,
87
88
  outcome.run_id,
88
89
  );
90
+ // Both `agent_parked` and `llm_parked` cover two unrelated situations —
91
+ // nothing attached to run the step, and the kernel's `needs_human`
92
+ // recovery wait after a worker attempt failed — and only the first is
93
+ // fixed by attaching a worker. The child's classification already knows
94
+ // which; carry it so the CLI boundary can name a remedy without
95
+ // re-deriving one from this message's wording.
96
+ error.parkCause = execution.report.parkCause;
97
+ throw error;
89
98
  }
90
99
  if (execution.exitCode !== 0) {
91
100
  // `execution.report.completionReason` is the RUN's reason (normally
@@ -161,10 +170,27 @@ export function authoredWorkerRunner(
161
170
  `f.agent options.model must be a string when set (got ${typeof options.model}).`,
162
171
  );
163
172
  }
164
- if (options.cwd !== undefined && typeof options.cwd !== 'string') {
173
+ // The same lexical rule the kernel and `flows check` apply, raised here
174
+ // so an authored body is refused before a run exists rather than at
175
+ // dispatch. Whether the directory is there is the worker's question.
176
+ // An absolute cwd is honored when it names a directory inside the run
177
+ // root — it lowers to the same relative declaration — and refused when
178
+ // it escapes, because a declaration this contract cannot contain is
179
+ // worse than none.
180
+ const declaredCwd = options.cwd === undefined || !isAbsolute(options.cwd) ? options.cwd
181
+ : relative(process.cwd(), options.cwd) || undefined;
182
+ if (options.cwd !== undefined && isAbsolute(options.cwd) && declaredCwd !== undefined
183
+ && (declaredCwd === '..' || declaredCwd.startsWith(`..${sep}`) || isAbsolute(declaredCwd))) {
184
+ throw new AuthoredFlowExecutionError(
185
+ 'agent_cli_unresolved',
186
+ `f.agent options.cwd must name a directory inside the run root ${JSON.stringify(process.cwd())} (got ${JSON.stringify(options.cwd)}).`,
187
+ );
188
+ }
189
+ const cwdProblem = declaredCwd === undefined ? undefined : agentCwdDeclarationError(declaredCwd);
190
+ if (cwdProblem !== undefined) {
165
191
  throw new AuthoredFlowExecutionError(
166
192
  'agent_cli_unresolved',
167
- `f.agent options.cwd must be a string when set (got ${typeof options.cwd}).`,
193
+ `f.agent options.cwd: ${cwdProblem}.`,
168
194
  );
169
195
  }
170
196
  if (options.transport !== undefined && options.transport !== 'direct' && options.transport !== 'relay') {
@@ -173,6 +199,10 @@ export function authoredWorkerRunner(
173
199
  `f.agent options.transport must be 'direct' or 'relay' (got ${JSON.stringify(options.transport)}).`,
174
200
  );
175
201
  }
202
+ const cwdTransport = agentCwdTransportError(declaredCwd, options.transport);
203
+ if (cwdTransport !== undefined) {
204
+ throw new AuthoredFlowExecutionError('agent_cli_unresolved', `f.agent options.${cwdTransport}.`);
205
+ }
176
206
  const permissions = options.permissions;
177
207
  const permissionsSnapshot = permissions === undefined ? undefined
178
208
  : snapshotJsonValue(permissions, 'f.agent options.permissions') as unknown as PermissionsSpec;
@@ -183,8 +213,7 @@ export function authoredWorkerRunner(
183
213
  ...(options.workspace === undefined ? {} : { surfaces: { workspace: [{ surface: options.workspace }] } }),
184
214
  ...(options.cli === undefined ? {} : { cli: options.cli }),
185
215
  ...(options.model === undefined ? {} : { model: options.model }),
186
- // The kernel takes only an absolute cwd; a relative one means the runner's own directory.
187
- ...(options.cwd === undefined ? {} : { cwd: resolve(options.cwd) }),
216
+ ...(declaredCwd === undefined ? {} : { cwd: declaredCwd }),
188
217
  ...(options.transport === undefined ? {} : { transport: options.transport }),
189
218
  ...(verification === undefined ? {} : { verification }),
190
219
  });
@@ -234,26 +263,43 @@ export function authoredDeterministicRunner(
234
263
  context: AuthoredStepContext = {},
235
264
  ) {
236
265
  const rootRunId = context.rootRunId;
237
- return async (id: string, command: string, terminal = false, leaseMs?: number, verification?: NamedGate): Promise<string> => {
266
+ // One lowering, two readers. `record` changes what the child run's verdict
267
+ // MEANS, so the reader changes with it; everything before the read — spec,
268
+ // admission key, budget, child index — is deliberately shared, because a
269
+ // recorded red command is still an ordinary child run.
270
+ function lower<T>(
271
+ id: string, command: string, terminal: boolean, leaseMs: number | undefined,
272
+ verification: NamedGate | undefined, onNonZero: 'record' | undefined,
273
+ read: (outcome: import('./protocol.js').RunOutcome) => Promise<T>,
274
+ ): Promise<T> {
238
275
  const spec = toKernelSpec(compileSpec({
239
276
  version: SPEC_SCHEMA_VERSION,
240
277
  name: `${name}/${id}`,
241
278
  steps: [{
242
279
  id, type: 'deterministic', command,
243
280
  ...(leaseMs === undefined ? {} : { lease_ms: leaseMs }),
281
+ ...(onNonZero === undefined ? {} : { onNonZero }),
244
282
  ...(verification === undefined ? {} : { verification }),
245
283
  }],
246
284
  }));
247
285
  const admissionKey = authoredChildAdmissionKey(rootRunId, id);
248
- const consume = async (outcome: import('./protocol.js').RunOutcome): Promise<string> => {
286
+ const consume = async (outcome: import('./protocol.js').RunOutcome): Promise<T> => {
249
287
  await recordAuthoredChild(journal, rootRunId, {
250
288
  step: id, runId: outcome.run_id, state: 'admitted', ...context.stepEdges?.(id),
251
289
  });
252
- return readSuccessfulOutput(journal, outcome, id, journalSteps, context);
290
+ return read(outcome);
253
291
  };
254
- if (terminal) return consume(await journal.runStart(spec, undefined, admissionKey));
292
+ if (terminal) return journal.runStart(spec, undefined, admissionKey).then(consume);
255
293
  return budget.execute(journal, spec, consume, admissionKey);
256
- };
294
+ }
295
+ const lowerRun = (id: string, command: string, terminal = false, leaseMs?: number, verification?: NamedGate): Promise<string> =>
296
+ lower(id, command, terminal, leaseMs, verification, undefined,
297
+ outcome => readSuccessfulOutput(journal, outcome, id, journalSteps, context));
298
+ /** `f.run(..., { onNonZero: 'record' })`: the exit code is the result, not a throw. */
299
+ lowerRun.recording = (id: string, command: string, leaseMs?: number, verification?: NamedGate): Promise<RecordedRunOutcome> =>
300
+ lower(id, command, false, leaseMs, verification, 'record',
301
+ outcome => readRecordedOutcome(journal, outcome, id, journalSteps, context));
302
+ return lowerRun;
257
303
  }
258
304
 
259
305
  /**
@@ -284,6 +330,8 @@ function stepDetails(
284
330
  ...(found.stderrTail === undefined ? {} : { stderrTail: found.stderrTail }),
285
331
  ...(found.detail === undefined ? {} : { detail: found.detail }),
286
332
  ...(found.transcriptPath === undefined ? {} : { transcriptPath: found.transcriptPath }),
333
+ ...(found.attempts === undefined ? {} : { attempts: found.attempts }),
334
+ ...(found.attemptEvidence === undefined ? {} : { attemptEvidence: found.attemptEvidence }),
287
335
  ...(found.hint === undefined ? {} : { hint: found.hint }),
288
336
  ...(found.journalPath === undefined ? {} : { journalPath: found.journalPath }),
289
337
  };
@@ -5,7 +5,7 @@ import { safePath, sha256 } from './bundle.js';
5
5
  import { findPluginProject } from './plugin-loader.js';
6
6
  import { parsePluginLock, readPluginLock, reconcileDeclaredExtensions, type PluginLock } from './plugin-lock.js';
7
7
  import { PluginError } from './plugin-manifest.js';
8
- import { pluginStoreDirectory, readStoredPluginFiles, verifyStoredPlugin } from './plugin-store.js';
8
+ import { pluginStoreDirectory, readStoredPluginFiles } from './plugin-store.js';
9
9
 
10
10
  const EMPTY_LOCK: PluginLock = Object.freeze({ version: 2, plugins: Object.freeze([]) });
11
11
 
@@ -50,10 +50,11 @@ export async function verifyBundlePluginLock(bundle: string): Promise<void> {
50
50
  throw new Error(`lockfile.json: plugin name ${entry.name} is not a safe path component`);
51
51
  }
52
52
  const directory = join(bundle, 'plugins', entry.name);
53
- await verifyStoredPlugin(directory, entry.digest);
54
- let pluginManifest: Buffer;
55
- try { pluginManifest = await readFile(join(directory, 'flows-plugin.json')); }
56
- catch { throw new Error(`lockfile.json: plugin ${entry.name} is missing plugins/${entry.name}/flows-plugin.json`); }
53
+ const stored = await readStoredPluginFiles(directory, entry.digest);
54
+ const pluginManifest = stored.find(file => file.path === 'flows-plugin.json')?.data;
55
+ if (pluginManifest === undefined) {
56
+ throw new Error(`lockfile.json: plugin ${entry.name} is missing plugins/${entry.name}/flows-plugin.json`);
57
+ }
57
58
  if (sha256(pluginManifest) !== entry.manifestSha256) {
58
59
  throw new Error(`lockfile.json: plugin ${entry.name} flows-plugin.json does not match the lockfile manifest hash`);
59
60
  }
package/src/bundle.ts CHANGED
@@ -2,6 +2,27 @@ import { createHash, createPrivateKey, createPublicKey, randomBytes, sign, verif
2
2
  import { lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
3
3
  import { basename, dirname, join, resolve } from 'node:path';
4
4
  import { canonicalize } from './canonical.js';
5
+ import { appendIntrinsicArray } from './intrinsic-array.js';
6
+
7
+ type Hash = ReturnType<typeof createHash>;
8
+ const CREATE_HASH = createHash;
9
+ const HASH_UPDATE = Function.prototype.call.bind(CREATE_HASH('sha256').update) as (
10
+ hash: Hash,
11
+ data: Uint8Array | string,
12
+ ) => Hash;
13
+ const HASH_DIGEST = Function.prototype.call.bind(CREATE_HASH('sha256').digest) as (
14
+ hash: Hash,
15
+ encoding: 'hex',
16
+ ) => string;
17
+ const STRING_INCLUDES = Function.prototype.call.bind(String.prototype.includes) as (
18
+ value: string, search: string,
19
+ ) => boolean;
20
+ const STRING_SPLIT = Function.prototype.call.bind(String.prototype.split) as (
21
+ value: string, separator: string,
22
+ ) => string[];
23
+ const TYPED_ARRAY_LENGTH = Function.prototype.call.bind(
24
+ Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Uint8Array.prototype) as object, 'length')!.get!,
25
+ ) as (value: Uint8Array) => number;
5
26
 
6
27
  export interface BundleEntry { path: string; sha256: string; bytes: number }
7
28
  export interface BundleFile { path: string; data: Uint8Array | string; executable?: boolean }
@@ -15,14 +36,21 @@ export interface BundleOptions {
15
36
  }
16
37
 
17
38
  export function sha256(data: Uint8Array | string): string {
18
- return createHash('sha256').update(data).digest('hex');
39
+ const hash = CREATE_HASH('sha256');
40
+ HASH_UPDATE(hash, data);
41
+ return HASH_DIGEST(hash, 'hex');
19
42
  }
20
43
 
21
44
  /** A bundle-relative path: no empty, `.`, or `..` component, no backslash, NUL, or drive colon. */
22
45
  export function safePath(path: string): boolean {
23
- return path.length > 0 && !path.includes('\\') && !path.includes('\0')
24
- && path.split('/').every(part => part !== '' && part !== '.' && part !== '..')
25
- && !path.includes(':');
46
+ if (path.length === 0 || STRING_INCLUDES(path, '\\') || STRING_INCLUDES(path, '\0')
47
+ || STRING_INCLUDES(path, ':')) return false;
48
+ const parts = STRING_SPLIT(path, '/');
49
+ for (let index = 0; index < parts.length; index += 1) {
50
+ const part = parts[index]!;
51
+ if (part === '' || part === '.' || part === '..') return false;
52
+ }
53
+ return true;
26
54
  }
27
55
 
28
56
  /**
@@ -31,9 +59,26 @@ export function safePath(path: string): boolean {
31
59
  * thing everywhere: the digest of `[{bytes,path,sha256}]`, sorted by path.
32
60
  */
33
61
  export function payloadManifest(files: readonly { path: string; data: Uint8Array }[]): string {
34
- return canonicalize([...files]
35
- .sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
36
- .map(file => ({ path: file.path, sha256: sha256(file.data), bytes: file.data.length })));
62
+ const sorted: { path: string; data: Uint8Array }[] = [];
63
+ for (let index = 0; index < files.length; index += 1) appendIntrinsicArray(sorted, files[index]!);
64
+ // Do not consult mutable Array prototype methods here. Authored base code
65
+ // executes in this process before hosted artifact staging, so a live
66
+ // sort/map lookup would let it substitute bytes or manifest records.
67
+ for (let index = 1; index < sorted.length; index += 1) {
68
+ const current = sorted[index]!;
69
+ let position = index;
70
+ while (position > 0 && sorted[position - 1]!.path > current.path) {
71
+ sorted[position] = sorted[position - 1]!;
72
+ position -= 1;
73
+ }
74
+ sorted[position] = current;
75
+ }
76
+ const entries: { path: string; sha256: string; bytes: number }[] = [];
77
+ for (let index = 0; index < sorted.length; index += 1) {
78
+ const file = sorted[index]!;
79
+ appendIntrinsicArray(entries, { path: file.path, sha256: sha256(file.data), bytes: TYPED_ARRAY_LENGTH(file.data) });
80
+ }
81
+ return canonicalize(entries);
37
82
  }
38
83
 
39
84
  /** Manifest and identity are envelopes, excluded to avoid circular hashing. */
package/src/canonical.ts CHANGED
@@ -9,6 +9,15 @@
9
9
 
10
10
  import { createHash } from 'node:crypto';
11
11
  import { snapshotJsonValue, type JsonValue } from './json-value.js';
12
+ import { appendIntrinsicArray } from './intrinsic-array.js';
13
+
14
+ const ARRAY_IS_ARRAY = Array.isArray;
15
+ const ARRAY_JOIN = Function.prototype.call.bind(Array.prototype.join) as (
16
+ array: readonly string[], separator?: string,
17
+ ) => string;
18
+ const ARRAY_SORT = Function.prototype.call.bind(Array.prototype.sort) as <T>(array: T[]) => T[];
19
+ const JSON_STRINGIFY = JSON.stringify;
20
+ const OBJECT_KEYS = Object.keys;
12
21
 
13
22
  /**
14
23
  * Serialize a value as canonical JSON: object keys sorted recursively,
@@ -42,16 +51,21 @@ export function specHash(spec: unknown): string {
42
51
  */
43
52
  function serialize(value: JsonValue): string {
44
53
  if (value === null || typeof value !== 'object') {
45
- return JSON.stringify(value);
54
+ return JSON_STRINGIFY(value);
46
55
  }
47
- if (Array.isArray(value)) {
48
- return '[' + value.map(serialize).join(',') + ']';
56
+ if (ARRAY_IS_ARRAY(value)) {
57
+ const items: string[] = [];
58
+ for (let index = 0; index < value.length; index += 1) appendIntrinsicArray(items, serialize(value[index]!));
59
+ return '[' + ARRAY_JOIN(items, ',') + ']';
49
60
  }
50
61
  const parts: string[] = [];
51
- for (const key of Object.keys(value).sort()) {
62
+ const keys = OBJECT_KEYS(value);
63
+ ARRAY_SORT(keys);
64
+ for (let index = 0; index < keys.length; index += 1) {
65
+ const key = keys[index]!;
52
66
  const child = value[key];
53
67
  if (child === undefined) continue;
54
- parts.push(JSON.stringify(key) + ':' + serialize(child));
68
+ appendIntrinsicArray(parts, JSON_STRINGIFY(key) + ':' + serialize(child));
55
69
  }
56
- return '{' + parts.join(',') + '}';
70
+ return '{' + ARRAY_JOIN(parts, ',') + '}';
57
71
  }
@@ -0,0 +1,66 @@
1
+ import { compileSpec } from '../compile.js';
2
+ import type { FlowSpec } from '../spec.js';
3
+ import type { CheckWarningDiagnostic } from './check.js';
4
+
5
+ /** Enough ids to recognise the flow, not enough to bury the remedy. */
6
+ const NAMED_STEP_LIMIT = 3;
7
+
8
+ /**
9
+ * `agent` steps need a worker attached for step type `agent`, and nothing in a
10
+ * spec says whether one will be. `flows run` attaches the SDK's `AgentWorker`
11
+ * only under `--local-agent` (`cli/run.ts`); with no worker the run parks at
12
+ * the first agent step with `no worker is attached for step type "agent"`.
13
+ * `flows check` already walks these steps to print its `REQUIRES` line, so the
14
+ * fact is in hand — this spends it.
15
+ *
16
+ * Warning, never a refusal, and worded as a requirement rather than a
17
+ * prediction: worker attachment is *unknown* here, not absent. A worker can be
18
+ * attached out of band (an SDK `AgentWorker` against the same daemon, Cloud's
19
+ * workers for a hosted run), and `flows check` is daemon-free by construction,
20
+ * so it cannot see one either way.
21
+ *
22
+ * Scoped to `agent` steps. `--local-agent` attaches no `llm` worker on the
23
+ * YAML path, so naming it for `llm` steps would be false; those steps are not
24
+ * counted and not mentioned.
25
+ *
26
+ * Blind spot, shared with `permissions_unenforced`: an authored `.flow.ts`
27
+ * flow is checked through `checkMcpHeader`, which preflights a synthetic
28
+ * one-step header spec without executing the body. There are no compiled
29
+ * `agent` steps to count, so this diagnostic is silent for `.flow.ts`.
30
+ */
31
+ export function agentWorkerDiagnostics(authoring: FlowSpec): CheckWarningDiagnostic[] {
32
+ const steps = agentStepIds(authoring);
33
+ if (steps.length === 0) return [];
34
+ return [{ severity: 'warning', kind: 'agent_worker_unresolved', message: message(steps) }];
35
+ }
36
+
37
+ /**
38
+ * Compiled `agent` steps, by id. Compilation is the only walk that sees them
39
+ * all: a YAML helper step (`slack: { post: … }`) is an `agent` step carrying a
40
+ * helper envelope and needs the same SDK agent worker, and only the compiler
41
+ * lowers it into one. `requirements.harnesses` is the wrong source — it
42
+ * deduplicates by harness, counts `llm` use, and omits helper steps entirely.
43
+ *
44
+ * A spec that does not compile yields no diagnostic rather than a thrown
45
+ * check: the compile refusal is owned by the existing preflight path and is
46
+ * the whole report in that case.
47
+ */
48
+ function agentStepIds(authoring: FlowSpec): string[] {
49
+ try {
50
+ return compileSpec(authoring).steps.filter((step) => step.type === 'agent').map((step) => step.id);
51
+ } catch {
52
+ return [];
53
+ }
54
+ }
55
+
56
+ function message(steps: readonly string[]): string {
57
+ const named = steps.slice(0, NAMED_STEP_LIMIT).map((id) => `"${id}"`).join(', ');
58
+ const rest = steps.length - NAMED_STEP_LIMIT;
59
+ const list = rest > 0 ? `${named} and ${rest} more` : named;
60
+ const subject = steps.length === 1
61
+ ? `1 agent step (${list}) requires`
62
+ : `${steps.length} agent steps (${list}) require`;
63
+ return `${subject} an attached worker. For a local run, use \`flows run --local-agent <flow>\`, `
64
+ + 'unless you already attach an agent worker for this daemon. `flows check` does not verify worker '
65
+ + 'attachment: with no worker attached the run parks at the first agent step.';
66
+ }
package/src/cli/check.ts CHANGED
@@ -6,6 +6,7 @@ import { dirname, isAbsolute, join, parse as parsePath, resolve } from 'node:pat
6
6
  import { spawnSync } from 'node:child_process';
7
7
  import { parse as parseYaml } from 'yaml';
8
8
  import { CompileError, compileSpec, kernelToAuthoring } from '../compile.js';
9
+ import { agentWorkerDiagnostics } from './check-worker-surface.js';
9
10
  import { helperReady } from '../yaml-helper-effect.js';
10
11
  import { flowRequirements, type FlowRequirements } from '../flow-requirements.js';
11
12
  import {
@@ -117,6 +118,24 @@ export interface CheckExecution {
117
118
  flow?: FlowSpec;
118
119
  }
119
120
 
121
+ /**
122
+ * Facts about the *caller*, not about the spec, that change which diagnostics
123
+ * apply. Preflight stays a pure function of the spec plus environment probes;
124
+ * anything that depends on how the flow is about to be invoked opts in here.
125
+ */
126
+ export interface CheckInvocation {
127
+ /**
128
+ * Report `agent_worker_unresolved` when the spec has `agent` steps.
129
+ *
130
+ * Only `flows check` sets this. `flows run` attaches its own worker under
131
+ * `--local-agent` and knows the answer, `flows build` and `flows deploy`
132
+ * check a spec that will run elsewhere, and an SDK caller that reaches
133
+ * `checkAuthoredFlow` directly is generally running a worker already —
134
+ * warning any of them would be noise about a question they have answered.
135
+ */
136
+ warnUnresolvedAgentWorker?: boolean;
137
+ }
138
+
120
139
  export class CheckFailure extends Error {
121
140
  constructor(readonly kind: CheckFailureKind, message: string) {
122
141
  super(message);
@@ -124,7 +143,7 @@ export class CheckFailure extends Error {
124
143
  }
125
144
 
126
145
  /** Validate and preflight one working-tree spec without starting a run. */
127
- export function checkFlow(path: string): CheckExecution {
146
+ export function checkFlow(path: string, invocation: CheckInvocation = {}): CheckExecution {
128
147
  const absolutePath = resolve(path);
129
148
  try {
130
149
  const source = readFlowSource(absolutePath);
@@ -134,7 +153,7 @@ export function checkFlow(path: string): CheckExecution {
134
153
  : [];
135
154
  let execution: CheckExecution;
136
155
  try {
137
- execution = checkAuthoredFlow(readFlow(source, absolutePath), path);
156
+ execution = checkAuthoredFlow(readFlow(source, absolutePath), path, undefined, invocation);
138
157
  } catch (error) {
139
158
  if (!(error instanceof CheckFailure)) throw error;
140
159
  execution = { report: inputFailureReport(error, path) };
@@ -164,7 +183,12 @@ function safeRequirements(authoring: FlowSpec, projectCli: string | undefined):
164
183
  }
165
184
 
166
185
  /** Preflight a validated authored flow through the same path as YAML/JSON. */
167
- export function checkAuthoredFlow(authoring: FlowSpec, path: string, projectConfig?: ProjectConfig): CheckExecution {
186
+ export function checkAuthoredFlow(
187
+ authoring: FlowSpec,
188
+ path: string,
189
+ projectConfig?: ProjectConfig,
190
+ invocation: CheckInvocation = {},
191
+ ): CheckExecution {
168
192
  const absolutePath = resolve(path);
169
193
  try {
170
194
  const config = projectConfig ?? readProjectConfig(dirname(absolutePath));
@@ -195,6 +219,13 @@ export function checkAuthoredFlow(authoring: FlowSpec, path: string, projectConf
195
219
  result.diagnostics.push({ severity: 'warning', kind: 'budget_unmetered',
196
220
  message: 'Managed communication sessions do not report token or dollar usage. Budget ceilings cannot bound their spend; communication.timeoutMs bounds their duration.' });
197
221
  }
222
+ // Reported whatever preflight concluded. An environment refusal (a missing
223
+ // CLI, an unknown model) is fixed and rerun; the worker question is still
224
+ // open on the next pass, and staying silent about it here is what made an
225
+ // author meet it one dead run at a time.
226
+ const workerSurface = invocation.warnUnresolvedAgentWorker === true
227
+ ? agentWorkerDiagnostics(authoring)
228
+ : [];
198
229
  return {
199
230
  report: {
200
231
  ok: result.ok,
@@ -202,7 +233,7 @@ export function checkAuthoredFlow(authoring: FlowSpec, path: string, projectConf
202
233
  ...(config.path !== undefined ? { projectConfigPath: config.path } : {}),
203
234
  gates: result.gates,
204
235
  resolutions: result.resolutions,
205
- diagnostics: result.diagnostics,
236
+ diagnostics: [...result.diagnostics, ...workerSurface],
206
237
  requirements: safeRequirements(authoring, config.cli),
207
238
  },
208
239
  ...(result.ok && flow !== undefined ? { flow } : {}),
@@ -0,0 +1,82 @@
1
+ // The cells every hosted-run page is built from.
2
+ //
3
+ // Moved out of `cli/cloud-read.ts` verbatim when the live views (`--watch`,
4
+ // `--follow`) needed the same formatting: the run list, the status page and
5
+ // the watched page must render a count, a cost and an instant identically, or
6
+ // a reader would have to learn which verb they were looking at first.
7
+ //
8
+ // Depends on nothing in `cli/` but `formatDuration`, so every other Cloud CLI
9
+ // module can import it without an import cycle.
10
+
11
+ import { formatDuration } from './status.js';
12
+
13
+ export function thousands(value: number): string {
14
+ return String(value).replace(/\B(?=(\d{3})+(?!\d))/gu, ',');
15
+ }
16
+
17
+ export function dollars(value: number): string {
18
+ return `$${value.toFixed(6).replace(/0+$/u, '').replace(/\.$/u, '')}`;
19
+ }
20
+
21
+ /** Agent- and flow-authored names cannot inject terminal control sequences. */
22
+ export function safe(text: string): string {
23
+ return text.replace(/[\u0000-\u001F\u007F-\u009F]/gu, '?');
24
+ }
25
+
26
+ /**
27
+ * Render a run's `error` as readable lines rather than one control-char smear.
28
+ *
29
+ * Cloud stores the runner's terminal output in this field, newlines and all, so
30
+ * passing it through `safe()` alone turns a 200-line tail into a single line of
31
+ * `?` separators. Long runs are dominated by lease renewals — one line every
32
+ * 10s for the life of every agent step, differing only in the deadline — which
33
+ * are worth counting, not reading.
34
+ *
35
+ * Collapsing is deliberately narrow: two adjacent lines merge only when they
36
+ * are character-for-character identical once a trailing integer is masked. A
37
+ * shared prefix is NOT line identity — runner lines put the step name, reason
38
+ * or message after a long fixed prefix, so collapsing on a prefix would hide
39
+ * distinct diagnostics behind a similarity count. The final line never
40
+ * collapses into an earlier one, because that is where the failure is.
41
+ */
42
+ const TRAILING_NUMBER = /\d+(?=\D{0,2}$)/u;
43
+
44
+ export function errorLines(text: string, indent: string): string[] {
45
+ const raw = text.split(/\r\n|\r|\n/u).map((line) => line.trimEnd()).filter((line) => line !== '');
46
+ if (raw.length === 0) return [];
47
+
48
+ const key = (line: string): string => line.replace(TRAILING_NUMBER, '#');
49
+ const collapsed: { line: string; count: number }[] = [];
50
+ raw.forEach((line, index) => {
51
+ const previous = collapsed.at(-1);
52
+ const isLast = index === raw.length - 1;
53
+ if (previous !== undefined && !isLast && key(previous.line) === key(line)) previous.count += 1;
54
+ else collapsed.push({ line, count: 1 });
55
+ });
56
+
57
+ const rendered = collapsed.map(({ line, count }) =>
58
+ count === 1 ? safe(line) : `${safe(line)} (${count} times, differing only in a number)`);
59
+
60
+ const HEAD = 2;
61
+ const TAIL = 12;
62
+ if (rendered.length <= HEAD + TAIL + 1) return rendered.map((line) => `${indent}${line}`);
63
+ const elided = rendered.length - HEAD - TAIL;
64
+ return [
65
+ ...rendered.slice(0, HEAD),
66
+ `… ${elided} more line${elided === 1 ? '' : 's'} (full text: --json)`,
67
+ ...rendered.slice(-TAIL),
68
+ ].map((line) => `${indent}${line}`);
69
+ }
70
+
71
+ /** ISO-8601 to the second: a list column, not a timestamp to do arithmetic on. */
72
+ export function instant(value: string | null): string {
73
+ if (value === null) return 'unknown';
74
+ const parsed = Date.parse(value);
75
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString().replace(/\.\d{3}Z$/u, 'Z') : 'unknown';
76
+ }
77
+
78
+ export function ago(value: string | null, now: number): string {
79
+ if (value === null) return 'unknown';
80
+ const parsed = Date.parse(value);
81
+ return Number.isFinite(parsed) ? `${formatDuration(now - parsed)} ago` : 'unknown';
82
+ }