@relayflows/sdk 2.0.30 → 2.0.32

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 (347) 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 +54 -47
  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-preflight.d.ts +4 -0
  38. package/dist/authored-preflight.d.ts.map +1 -0
  39. package/dist/authored-preflight.js +50 -0
  40. package/dist/authored-preflight.js.map +1 -0
  41. package/dist/authored-root.d.ts +5 -0
  42. package/dist/authored-root.d.ts.map +1 -1
  43. package/dist/authored-root.js +20 -2
  44. package/dist/authored-root.js.map +1 -1
  45. package/dist/authored-step-output.d.ts +35 -10
  46. package/dist/authored-step-output.d.ts.map +1 -1
  47. package/dist/authored-step-output.js +108 -40
  48. package/dist/authored-step-output.js.map +1 -1
  49. package/dist/authored-verdict.d.ts +28 -0
  50. package/dist/authored-verdict.d.ts.map +1 -0
  51. package/dist/authored-verdict.js +79 -0
  52. package/dist/authored-verdict.js.map +1 -0
  53. package/dist/authored-worker-step.d.ts +5 -2
  54. package/dist/authored-worker-step.d.ts.map +1 -1
  55. package/dist/authored-worker-step.js +51 -13
  56. package/dist/authored-worker-step.js.map +1 -1
  57. package/dist/cli/check-worker-surface.d.ts +27 -0
  58. package/dist/cli/check-worker-surface.d.ts.map +1 -0
  59. package/dist/cli/check-worker-surface.js +63 -0
  60. package/dist/cli/check-worker-surface.js.map +1 -0
  61. package/dist/cli/check.d.ts +28 -3
  62. package/dist/cli/check.d.ts.map +1 -1
  63. package/dist/cli/check.js +27 -140
  64. package/dist/cli/check.js.map +1 -1
  65. package/dist/cli/cli-probe.d.ts +20 -0
  66. package/dist/cli/cli-probe.d.ts.map +1 -0
  67. package/dist/cli/cli-probe.js +166 -0
  68. package/dist/cli/cli-probe.js.map +1 -0
  69. package/dist/cli/cloud-format.d.ts +9 -0
  70. package/dist/cli/cloud-format.d.ts.map +1 -0
  71. package/dist/cli/cloud-format.js +77 -0
  72. package/dist/cli/cloud-format.js.map +1 -0
  73. package/dist/cli/cloud-live.d.ts +39 -0
  74. package/dist/cli/cloud-live.d.ts.map +1 -0
  75. package/dist/cli/cloud-live.js +325 -0
  76. package/dist/cli/cloud-live.js.map +1 -0
  77. package/dist/cli/cloud-mirror-session.d.ts +97 -0
  78. package/dist/cli/cloud-mirror-session.d.ts.map +1 -0
  79. package/dist/cli/cloud-mirror-session.js +264 -0
  80. package/dist/cli/cloud-mirror-session.js.map +1 -0
  81. package/dist/cli/cloud-read.d.ts +3 -7
  82. package/dist/cli/cloud-read.d.ts.map +1 -1
  83. package/dist/cli/cloud-read.js +14 -266
  84. package/dist/cli/cloud-read.js.map +1 -1
  85. package/dist/cli/cloud-refusal.d.ts +39 -0
  86. package/dist/cli/cloud-refusal.d.ts.map +1 -0
  87. package/dist/cli/cloud-refusal.js +95 -0
  88. package/dist/cli/cloud-refusal.js.map +1 -0
  89. package/dist/cli/cloud-status-view.d.ts +7 -0
  90. package/dist/cli/cloud-status-view.d.ts.map +1 -0
  91. package/dist/cli/cloud-status-view.js +214 -0
  92. package/dist/cli/cloud-status-view.js.map +1 -0
  93. package/dist/cli/direct-run.d.ts.map +1 -1
  94. package/dist/cli/direct-run.js +16 -2
  95. package/dist/cli/direct-run.js.map +1 -1
  96. package/dist/cli/hn-monitor.d.ts +0 -13
  97. package/dist/cli/hn-monitor.d.ts.map +1 -1
  98. package/dist/cli/hn-monitor.js +2 -1
  99. package/dist/cli/hn-monitor.js.map +1 -1
  100. package/dist/cli/local-agent-remedy.d.ts +80 -0
  101. package/dist/cli/local-agent-remedy.d.ts.map +1 -0
  102. package/dist/cli/local-agent-remedy.js +116 -0
  103. package/dist/cli/local-agent-remedy.js.map +1 -0
  104. package/dist/cli/observer-session.d.ts +41 -0
  105. package/dist/cli/observer-session.d.ts.map +1 -0
  106. package/dist/cli/observer-session.js +78 -0
  107. package/dist/cli/observer-session.js.map +1 -0
  108. package/dist/cli/run.d.ts +30 -2
  109. package/dist/cli/run.d.ts.map +1 -1
  110. package/dist/cli/run.js +186 -24
  111. package/dist/cli/run.js.map +1 -1
  112. package/dist/cli/status.d.ts +9 -0
  113. package/dist/cli/status.d.ts.map +1 -1
  114. package/dist/cli/status.js +31 -2
  115. package/dist/cli/status.js.map +1 -1
  116. package/dist/cli/step-evidence.d.ts +90 -0
  117. package/dist/cli/step-evidence.d.ts.map +1 -0
  118. package/dist/cli/step-evidence.js +259 -0
  119. package/dist/cli/step-evidence.js.map +1 -0
  120. package/dist/cli/step-failure.d.ts +14 -4
  121. package/dist/cli/step-failure.d.ts.map +1 -1
  122. package/dist/cli/step-failure.js +41 -76
  123. package/dist/cli/step-failure.js.map +1 -1
  124. package/dist/cli-commands.d.ts +12 -0
  125. package/dist/cli-commands.d.ts.map +1 -1
  126. package/dist/cli-commands.js +3 -0
  127. package/dist/cli-commands.js.map +1 -1
  128. package/dist/cli.d.ts +17 -0
  129. package/dist/cli.d.ts.map +1 -1
  130. package/dist/cli.js +134 -57
  131. package/dist/cli.js.map +1 -1
  132. package/dist/cloud-deploy.d.ts +1 -1
  133. package/dist/cloud-deploy.d.ts.map +1 -1
  134. package/dist/cloud-deploy.js +24 -8
  135. package/dist/cloud-deploy.js.map +1 -1
  136. package/dist/cloud-mirror-ledger.d.ts +40 -0
  137. package/dist/cloud-mirror-ledger.d.ts.map +1 -0
  138. package/dist/cloud-mirror-ledger.js +142 -0
  139. package/dist/cloud-mirror-ledger.js.map +1 -0
  140. package/dist/cloud-mirror-step.d.ts +168 -0
  141. package/dist/cloud-mirror-step.d.ts.map +1 -0
  142. package/dist/cloud-mirror-step.js +504 -0
  143. package/dist/cloud-mirror-step.js.map +1 -0
  144. package/dist/cloud-mirror-transport.d.ts +94 -0
  145. package/dist/cloud-mirror-transport.d.ts.map +1 -0
  146. package/dist/cloud-mirror-transport.js +163 -0
  147. package/dist/cloud-mirror-transport.js.map +1 -0
  148. package/dist/cloud-mirror.d.ts +132 -0
  149. package/dist/cloud-mirror.d.ts.map +1 -0
  150. package/dist/cloud-mirror.js +556 -0
  151. package/dist/cloud-mirror.js.map +1 -0
  152. package/dist/cloud-read.d.ts +15 -0
  153. package/dist/cloud-read.d.ts.map +1 -1
  154. package/dist/cloud-read.js +20 -1
  155. package/dist/cloud-read.js.map +1 -1
  156. package/dist/cloud-run-record.d.ts +27 -0
  157. package/dist/cloud-run-record.d.ts.map +1 -0
  158. package/dist/cloud-run-record.js +56 -0
  159. package/dist/cloud-run-record.js.map +1 -0
  160. package/dist/cloud-run.d.ts +2 -9
  161. package/dist/cloud-run.d.ts.map +1 -1
  162. package/dist/cloud-run.js +3 -20
  163. package/dist/cloud-run.js.map +1 -1
  164. package/dist/communication/local.d.ts.map +1 -1
  165. package/dist/communication/local.js +2 -1
  166. package/dist/communication/local.js.map +1 -1
  167. package/dist/communication/worker.d.ts +1 -1
  168. package/dist/communication/worker.d.ts.map +1 -1
  169. package/dist/communication/worker.js +13 -5
  170. package/dist/communication/worker.js.map +1 -1
  171. package/dist/compile.d.ts.map +1 -1
  172. package/dist/compile.js +28 -6
  173. package/dist/compile.js.map +1 -1
  174. package/dist/direct-input.d.ts.map +1 -1
  175. package/dist/direct-input.js +21 -1
  176. package/dist/direct-input.js.map +1 -1
  177. package/dist/failure-kinds.d.ts +96 -6
  178. package/dist/failure-kinds.d.ts.map +1 -1
  179. package/dist/failure-kinds.js +29 -2
  180. package/dist/failure-kinds.js.map +1 -1
  181. package/dist/flow-requirements.d.ts.map +1 -1
  182. package/dist/flow-requirements.js +24 -9
  183. package/dist/flow-requirements.js.map +1 -1
  184. package/dist/gate-contract.d.ts +7 -0
  185. package/dist/gate-contract.d.ts.map +1 -1
  186. package/dist/gate-contract.js +6 -1
  187. package/dist/gate-contract.js.map +1 -1
  188. package/dist/helper-preflight.d.ts.map +1 -1
  189. package/dist/helper-preflight.js +10 -3
  190. package/dist/helper-preflight.js.map +1 -1
  191. package/dist/helper-reference.d.ts +25 -0
  192. package/dist/helper-reference.d.ts.map +1 -0
  193. package/dist/helper-reference.js +129 -0
  194. package/dist/helper-reference.js.map +1 -0
  195. package/dist/hosted-extension-runtime.js +1 -1
  196. package/dist/index.d.ts +1 -1
  197. package/dist/index.d.ts.map +1 -1
  198. package/dist/index.js.map +1 -1
  199. package/dist/journal-client.d.ts +3 -1
  200. package/dist/journal-client.d.ts.map +1 -1
  201. package/dist/journal-client.js +17 -2
  202. package/dist/journal-client.js.map +1 -1
  203. package/dist/journal-projection.d.ts +16 -0
  204. package/dist/journal-projection.d.ts.map +1 -0
  205. package/dist/journal-projection.js +108 -0
  206. package/dist/journal-projection.js.map +1 -0
  207. package/dist/llm-worker.js +1 -1
  208. package/dist/llm-worker.js.map +1 -1
  209. package/dist/local-agent.d.ts +1 -1
  210. package/dist/local-agent.d.ts.map +1 -1
  211. package/dist/local-agent.js +4 -3
  212. package/dist/local-agent.js.map +1 -1
  213. package/dist/named-gate-lowering.js +35 -5
  214. package/dist/named-gate-lowering.js.map +1 -1
  215. package/dist/named-gate-preflight.d.ts +27 -0
  216. package/dist/named-gate-preflight.d.ts.map +1 -0
  217. package/dist/named-gate-preflight.js +47 -0
  218. package/dist/named-gate-preflight.js.map +1 -0
  219. package/dist/named-gates.d.ts +1 -1
  220. package/dist/named-gates.d.ts.map +1 -1
  221. package/dist/named-gates.js +3 -0
  222. package/dist/named-gates.js.map +1 -1
  223. package/dist/observer-link.d.ts +7 -0
  224. package/dist/observer-link.d.ts.map +1 -1
  225. package/dist/observer-link.js +3 -1
  226. package/dist/observer-link.js.map +1 -1
  227. package/dist/package-version.d.ts +10 -0
  228. package/dist/package-version.d.ts.map +1 -0
  229. package/dist/package-version.js +22 -0
  230. package/dist/package-version.js.map +1 -0
  231. package/dist/preflight.d.ts +14 -0
  232. package/dist/preflight.d.ts.map +1 -1
  233. package/dist/preflight.js +25 -3
  234. package/dist/preflight.js.map +1 -1
  235. package/dist/protocol.d.ts +2 -0
  236. package/dist/protocol.d.ts.map +1 -1
  237. package/dist/redact.d.ts +42 -0
  238. package/dist/redact.d.ts.map +1 -1
  239. package/dist/redact.js +128 -0
  240. package/dist/redact.js.map +1 -1
  241. package/dist/relay-cli.d.ts.map +1 -1
  242. package/dist/relay-cli.js +1 -21
  243. package/dist/relay-cli.js.map +1 -1
  244. package/dist/run-projection.d.ts +83 -0
  245. package/dist/run-projection.d.ts.map +1 -0
  246. package/dist/run-projection.js +199 -0
  247. package/dist/run-projection.js.map +1 -0
  248. package/dist/shell-word.d.ts +19 -0
  249. package/dist/shell-word.d.ts.map +1 -0
  250. package/dist/shell-word.js +23 -0
  251. package/dist/shell-word.js.map +1 -0
  252. package/dist/spec.d.ts +52 -3
  253. package/dist/spec.d.ts.map +1 -1
  254. package/dist/spec.js.map +1 -1
  255. package/dist/step-fields.d.ts +1 -1
  256. package/dist/step-fields.js +1 -1
  257. package/dist/step-fields.js.map +1 -1
  258. package/dist/steps-green.d.ts +27 -0
  259. package/dist/steps-green.d.ts.map +1 -0
  260. package/dist/steps-green.js +164 -0
  261. package/dist/steps-green.js.map +1 -0
  262. package/dist/validate.d.ts.map +1 -1
  263. package/dist/validate.js +39 -8
  264. package/dist/validate.js.map +1 -1
  265. package/dist/worker-lease.d.ts +8 -1
  266. package/dist/worker-lease.d.ts.map +1 -1
  267. package/dist/worker-lease.js +34 -4
  268. package/dist/worker-lease.js.map +1 -1
  269. package/dist/worker.d.ts +8 -0
  270. package/dist/worker.d.ts.map +1 -1
  271. package/dist/worker.js +17 -9
  272. package/dist/worker.js.map +1 -1
  273. package/package.json +3 -7
  274. package/src/agent-artifacts.ts +2 -10
  275. package/src/agent-cwd.ts +139 -0
  276. package/src/artifact-scan-policy.ts +51 -0
  277. package/src/authored-completion-record.ts +119 -0
  278. package/src/authored-completion.ts +203 -0
  279. package/src/authored-flow-error.ts +13 -1
  280. package/src/authored-flow-executor.ts +91 -58
  281. package/src/authored-human.ts +1 -4
  282. package/src/authored-node-entry.ts +4 -1
  283. package/src/authored-node-runner.ts +96 -22
  284. package/src/authored-preflight.ts +46 -0
  285. package/src/authored-root.ts +20 -2
  286. package/src/authored-step-output.ts +139 -42
  287. package/src/authored-verdict.ts +87 -0
  288. package/src/authored-worker-step.ts +63 -14
  289. package/src/cli/check-worker-surface.ts +66 -0
  290. package/src/cli/check.ts +59 -166
  291. package/src/cli/cli-probe.ts +194 -0
  292. package/src/cli/cloud-format.ts +82 -0
  293. package/src/cli/cloud-live.ts +366 -0
  294. package/src/cli/cloud-mirror-session.ts +329 -0
  295. package/src/cli/cloud-read.ts +13 -282
  296. package/src/cli/cloud-refusal.ts +113 -0
  297. package/src/cli/cloud-status-view.ts +213 -0
  298. package/src/cli/direct-run.ts +19 -2
  299. package/src/cli/hn-monitor.ts +2 -1
  300. package/src/cli/local-agent-remedy.ts +160 -0
  301. package/src/cli/observer-session.ts +107 -0
  302. package/src/cli/run.ts +233 -28
  303. package/src/cli/status.ts +59 -3
  304. package/src/cli/step-evidence.ts +310 -0
  305. package/src/cli/step-failure.ts +57 -81
  306. package/src/cli-commands.ts +3 -0
  307. package/src/cli.ts +155 -60
  308. package/src/cloud-deploy.ts +26 -8
  309. package/src/cloud-mirror-ledger.ts +161 -0
  310. package/src/cloud-mirror-step.ts +618 -0
  311. package/src/cloud-mirror-transport.ts +261 -0
  312. package/src/cloud-mirror.ts +633 -0
  313. package/src/cloud-read.ts +25 -1
  314. package/src/cloud-run-record.ts +64 -0
  315. package/src/cloud-run.ts +5 -24
  316. package/src/communication/local.ts +2 -1
  317. package/src/communication/worker.ts +13 -5
  318. package/src/compile.ts +28 -6
  319. package/src/direct-input.ts +22 -1
  320. package/src/failure-kinds.ts +104 -2
  321. package/src/flow-requirements.ts +24 -9
  322. package/src/gate-contract.ts +13 -1
  323. package/src/helper-preflight.ts +10 -3
  324. package/src/helper-reference.ts +130 -0
  325. package/src/hosted-extension-runtime.ts +1 -1
  326. package/src/index.ts +2 -0
  327. package/src/journal-client.ts +19 -2
  328. package/src/journal-projection.ts +106 -0
  329. package/src/llm-worker.ts +1 -1
  330. package/src/local-agent.ts +4 -2
  331. package/src/named-gate-lowering.ts +35 -5
  332. package/src/named-gate-preflight.ts +47 -0
  333. package/src/named-gates.ts +3 -0
  334. package/src/observer-link.ts +10 -1
  335. package/src/package-version.ts +23 -0
  336. package/src/preflight.ts +38 -7
  337. package/src/protocol.ts +2 -0
  338. package/src/redact.ts +111 -0
  339. package/src/relay-cli.ts +1 -24
  340. package/src/run-projection.ts +247 -0
  341. package/src/shell-word.ts +24 -0
  342. package/src/spec.ts +54 -3
  343. package/src/step-fields.ts +1 -1
  344. package/src/steps-green.ts +168 -0
  345. package/src/validate.ts +35 -8
  346. package/src/worker-lease.ts +35 -5
  347. package/src/worker.ts +27 -11
package/src/cli/check.ts CHANGED
@@ -1,22 +1,13 @@
1
1
  import { communicationInstruction } from '../communication/spec.js';
2
2
  import { checkCommunicationEnvironment } from '../communication/preflight.js';
3
- import { agentEnvironment, brokerEnvironment } from '../communication/environment.js';
4
3
  import { accessSync, constants, readFileSync } from 'node:fs';
5
4
  import { dirname, isAbsolute, join, parse as parsePath, resolve } from 'node:path';
6
- import { spawnSync } from 'node:child_process';
7
5
  import { parse as parseYaml } from 'yaml';
8
6
  import { CompileError, compileSpec, kernelToAuthoring } from '../compile.js';
7
+ import { agentWorkerDiagnostics } from './check-worker-surface.js';
9
8
  import { helperReady } from '../yaml-helper-effect.js';
10
9
  import { flowRequirements, type FlowRequirements } from '../flow-requirements.js';
11
- import {
12
- adapterIdentification,
13
- authenticationProbe,
14
- cliAdapterKind,
15
- displayInvocation,
16
- modelReadinessProbe,
17
- type CliInvocation,
18
- } from '../cli-adapter.js';
19
- import { MODEL_ENV } from '../worker-cli.js';
10
+ import { probeCli, resolveExecutable } from './cli-probe.js';
20
11
  import { modelNameError } from '../model-name.js';
21
12
  import type { FlowSpec } from '../spec.js';
22
13
  import type { McpServerConfig } from '../spec.js';
@@ -25,9 +16,9 @@ import type { StepGateInspection } from '../gate-contract.js';
25
16
  import type { CheckFailureKind, CheckWarningKind } from '../failure-kinds.js';
26
17
  import {
27
18
  preflight,
28
- CliProbeError,
29
19
  type CliResolution,
30
20
  type CliProbeResult,
21
+ type CliProbeOutcome,
31
22
  type PreflightDiagnostic,
32
23
  type PreflightProbes,
33
24
  } from '../preflight.js';
@@ -117,6 +108,33 @@ export interface CheckExecution {
117
108
  flow?: FlowSpec;
118
109
  }
119
110
 
111
+ /** Compatibility options accepted by older authored callers. */
112
+ export interface AuthoredCheckOptions {
113
+ projectConfig?: ProjectConfig;
114
+ probeCache?: CliProbeOutcomeMap;
115
+ communicationChecked?: boolean;
116
+ }
117
+
118
+ type CliProbeOutcomeMap = Map<string, CliProbeOutcome>;
119
+
120
+ /**
121
+ * Facts about the *caller*, not about the spec, that change which diagnostics
122
+ * apply. Preflight stays a pure function of the spec plus environment probes;
123
+ * anything that depends on how the flow is about to be invoked opts in here.
124
+ */
125
+ export interface CheckInvocation {
126
+ /**
127
+ * Report `agent_worker_unresolved` when the spec has `agent` steps.
128
+ *
129
+ * Only `flows check` sets this. `flows run` attaches its own worker under
130
+ * `--local-agent` and knows the answer, `flows build` and `flows deploy`
131
+ * check a spec that will run elsewhere, and an SDK caller that reaches
132
+ * `checkAuthoredFlow` directly is generally running a worker already —
133
+ * warning any of them would be noise about a question they have answered.
134
+ */
135
+ warnUnresolvedAgentWorker?: boolean;
136
+ }
137
+
120
138
  export class CheckFailure extends Error {
121
139
  constructor(readonly kind: CheckFailureKind, message: string) {
122
140
  super(message);
@@ -124,7 +142,7 @@ export class CheckFailure extends Error {
124
142
  }
125
143
 
126
144
  /** Validate and preflight one working-tree spec without starting a run. */
127
- export function checkFlow(path: string): CheckExecution {
145
+ export function checkFlow(path: string, invocation: CheckInvocation = {}): CheckExecution {
128
146
  const absolutePath = resolve(path);
129
147
  try {
130
148
  const source = readFlowSource(absolutePath);
@@ -134,7 +152,7 @@ export function checkFlow(path: string): CheckExecution {
134
152
  : [];
135
153
  let execution: CheckExecution;
136
154
  try {
137
- execution = checkAuthoredFlow(readFlow(source, absolutePath), path);
155
+ execution = checkAuthoredFlow(readFlow(source, absolutePath), path, undefined, invocation);
138
156
  } catch (error) {
139
157
  if (!(error instanceof CheckFailure)) throw error;
140
158
  execution = { report: inputFailureReport(error, path) };
@@ -164,12 +182,29 @@ function safeRequirements(authoring: FlowSpec, projectCli: string | undefined):
164
182
  }
165
183
 
166
184
  /** Preflight a validated authored flow through the same path as YAML/JSON. */
167
- export function checkAuthoredFlow(authoring: FlowSpec, path: string, projectConfig?: ProjectConfig): CheckExecution {
185
+ export function checkAuthoredFlow(
186
+ authoring: FlowSpec,
187
+ path: string,
188
+ projectConfigOrOptions?: ProjectConfig | AuthoredCheckOptions,
189
+ invocation: CheckInvocation = {},
190
+ cliProbeCache?: Map<string, CliProbeOutcome>,
191
+ ): CheckExecution {
168
192
  const absolutePath = resolve(path);
169
193
  try {
194
+ const options = projectConfigOrOptions !== undefined
195
+ && ('projectConfig' in projectConfigOrOptions
196
+ || 'probeCache' in projectConfigOrOptions
197
+ || 'communicationChecked' in projectConfigOrOptions)
198
+ ? projectConfigOrOptions
199
+ : undefined;
200
+ const projectConfig = options !== undefined
201
+ ? options.projectConfig
202
+ : projectConfigOrOptions as ProjectConfig | undefined;
203
+ const effectiveProbeCache = cliProbeCache ?? options?.probeCache;
170
204
  const config = projectConfig ?? readProjectConfig(dirname(absolutePath));
171
205
  const probes = systemProbes(dirname(absolutePath), config);
172
206
  const result = preflight(authoring, {
207
+ ...(effectiveProbeCache === undefined ? {} : { cliProbeCache: effectiveProbeCache }),
173
208
  projectCli: config.cli,
174
209
  projectConfigPath: config.path,
175
210
  projectSearchStart: dirname(absolutePath),
@@ -186,7 +221,7 @@ export function checkAuthoredFlow(authoring: FlowSpec, path: string, projectConf
186
221
  )
187
222
  : undefined;
188
223
  if (flow?.steps.some(step => step.type === 'agent' && communicationInstruction(step.instruction))) {
189
- try { checkCommunicationEnvironment(flow); }
224
+ try { if (options?.communicationChecked !== true) checkCommunicationEnvironment(flow); }
190
225
  catch (error) {
191
226
  result.ok = false;
192
227
  result.diagnostics.push({ severity: 'refusal', kind: 'probe_failed',
@@ -195,6 +230,13 @@ export function checkAuthoredFlow(authoring: FlowSpec, path: string, projectConf
195
230
  result.diagnostics.push({ severity: 'warning', kind: 'budget_unmetered',
196
231
  message: 'Managed communication sessions do not report token or dollar usage. Budget ceilings cannot bound their spend; communication.timeoutMs bounds their duration.' });
197
232
  }
233
+ // Reported whatever preflight concluded. An environment refusal (a missing
234
+ // CLI, an unknown model) is fixed and rerun; the worker question is still
235
+ // open on the next pass, and staying silent about it here is what made an
236
+ // author meet it one dead run at a time.
237
+ const workerSurface = invocation.warnUnresolvedAgentWorker === true
238
+ ? agentWorkerDiagnostics(authoring)
239
+ : [];
198
240
  return {
199
241
  report: {
200
242
  ok: result.ok,
@@ -202,7 +244,7 @@ export function checkAuthoredFlow(authoring: FlowSpec, path: string, projectConf
202
244
  ...(config.path !== undefined ? { projectConfigPath: config.path } : {}),
203
245
  gates: result.gates,
204
246
  resolutions: result.resolutions,
205
- diagnostics: result.diagnostics,
247
+ diagnostics: [...result.diagnostics, ...workerSurface],
206
248
  requirements: safeRequirements(authoring, config.cli),
207
249
  },
208
250
  ...(result.ok && flow !== undefined ? { flow } : {}),
@@ -448,155 +490,6 @@ function canonicalCli(cli: string, directory: string): string {
448
490
  return resolve(directory, cli);
449
491
  }
450
492
 
451
- function probeCli(
452
- cli: string,
453
- directory: string,
454
- model?: string,
455
- execution?: 'managed',
456
- ): CliProbeResult {
457
- const executable = resolveExecutable(cli, directory);
458
- if (executable === undefined) return { exists: false, authenticated: false };
459
- const kind = cliAdapterKind(executable);
460
- // Relay owns interactive CLI launch/injection. Its generic PTY path is not
461
- // the headless wrapper protocol; do not demand that protocol from Gemini,
462
- // Cursor, OpenCode, or other interactive tools. Never invent an auth pass.
463
- if (execution === 'managed' && kind === 'relayflows-wrapper-v1') {
464
- return { exists: true, supported: true, authenticated: 'unverified' };
465
- }
466
- const environment = execution === 'managed'
467
- ? { ...brokerEnvironment(process.env), ...agentEnvironment(executable) } : process.env;
468
- const probe = (invocation: CliInvocation) => runProbe(executable, directory, invocation, environment);
469
- const identification = adapterIdentification(kind);
470
- const identified = probe(identification.invocation);
471
- if (
472
- identified.status !== 0
473
- || (identification.expectedStdout !== undefined
474
- && identified.stdout.trim() !== identification.expectedStdout)
475
- ) {
476
- return { exists: true, supported: false, authenticated: false };
477
- }
478
- const auth = authenticationProbe(kind);
479
- const authCommand = displayInvocation(cli, auth);
480
- if (model === undefined) {
481
- return {
482
- exists: true,
483
- supported: true,
484
- authenticated: probe(auth).status === 0,
485
- authCommand,
486
- };
487
- }
488
-
489
- const scoped = modelReadinessProbe(kind, model);
490
- const modelCommand = displayInvocation(cli, scoped);
491
- // A successful real provider round trip (or identified wrapper probe)
492
- // proves both auth and exact-model access. On failure, run the adapter's
493
- // actual auth command solely to classify auth vs model access truthfully.
494
- if (probe(scoped).status === 0) {
495
- return {
496
- exists: true,
497
- supported: true,
498
- authenticated: true,
499
- modelAvailable: true,
500
- authCommand,
501
- modelCommand,
502
- };
503
- }
504
- const authProbe = probe(auth);
505
- const authenticated = authProbe.status === 0;
506
- return {
507
- exists: true,
508
- supported: true,
509
- authenticated,
510
- modelAvailable: false,
511
- authCommand,
512
- modelCommand,
513
- // Only on failure: on success there is nothing to explain, and the output
514
- // is the most identity-bearing thing this function touches.
515
- ...(authenticated
516
- ? {}
517
- : {
518
- authExitCode: authProbe.status,
519
- authFailureDetail: redactProbeOutput(
520
- `${authProbe.stderr}${authProbe.stdout}`,
521
- ).trim().slice(0, 500),
522
- }),
523
- };
524
- }
525
-
526
- /**
527
- * Redact anything that looks like a credential or an account identifier.
528
- *
529
- * `auth status` output is diagnostic, but it is also the one place an account
530
- * email, org id or token fragment can appear. The point of surfacing it is to
531
- * say WHY a probe failed, which survives redaction; leaking an identity into a
532
- * refusal message that gets pasted into issues does not.
533
- */
534
- function redactProbeOutput(text: string): string {
535
- return text
536
- .replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, '<redacted-email>')
537
- .replace(/\b(sk|pk|oat|rt)[-_][A-Za-z0-9._-]{8,}/gi, '<redacted-token>')
538
- .replace(/\b[A-Fa-f0-9]{32,}\b/g, '<redacted-hex>');
539
- }
540
-
541
- function runProbe(
542
- executable: string,
543
- directory: string,
544
- invocation: CliInvocation,
545
- environment: NodeJS.ProcessEnv = process.env,
546
- ): { status: number | null; stdout: string; stderr: string } {
547
- const env = { ...environment };
548
- delete env[MODEL_ENV];
549
- if (invocation.modelEnv !== undefined) env[MODEL_ENV] = invocation.modelEnv;
550
- const result = spawnSync(executable, invocation.args, {
551
- cwd: directory,
552
- encoding: 'utf8',
553
- // stderr was 'ignore'. A failing `auth status` writes its reason there, so
554
- // discarding it made every authentication refusal structurally
555
- // undiagnosable: the refusal could say a probe exited non-zero and never
556
- // what it said. Captured, then redacted at the point of use.
557
- stdio: ['ignore', 'pipe', 'pipe'],
558
- timeout: invocation.timeoutMs,
559
- env,
560
- });
561
- const failure = classifySpawnFailure(result.error, result.signal, invocation.timeoutMs);
562
- if (failure !== undefined) throw failure;
563
- return {
564
- status: result.status,
565
- stdout: result.stdout,
566
- stderr: result.stderr ?? '',
567
- };
568
- }
569
-
570
- function resolveExecutable(command: string, directory: string): string | undefined {
571
- if (command.includes('/') || isAbsolute(command)) {
572
- const path = isAbsolute(command) ? command : resolve(directory, command);
573
- try {
574
- accessSync(path, constants.X_OK);
575
- return path;
576
- } catch {
577
- return undefined;
578
- }
579
- }
580
- const result = spawnSync('which', [command], { encoding: 'utf8', timeout: 5_000 });
581
- const failure = classifySpawnFailure(result.error, result.signal, 5_000);
582
- if (failure !== undefined) throw failure;
583
- return result.status === 0 ? result.stdout.trim() : undefined;
584
- }
585
-
586
- function classifySpawnFailure(
587
- error: Error | undefined,
588
- signal: NodeJS.Signals | null,
589
- timeoutMs: number,
590
- ): CliProbeError | undefined {
591
- if (error !== undefined) {
592
- const detail = (error as NodeJS.ErrnoException).code === 'ETIMEDOUT'
593
- ? `timeout:${timeoutMs}ms` as const
594
- : 'spawn_failed' as const;
595
- return new CliProbeError(detail);
596
- }
597
- return signal === null ? undefined : new CliProbeError(`signal:${signal}`);
598
- }
599
-
600
493
  function executableExists(command: string, directory: string): boolean {
601
494
  return resolveExecutable(command, directory) !== undefined;
602
495
  }
@@ -0,0 +1,194 @@
1
+ import { accessSync, constants } from 'node:fs';
2
+ import { isAbsolute, resolve } from 'node:path';
3
+ import { execFile, spawnSync } from 'node:child_process';
4
+ import { agentEnvironment, brokerEnvironment } from '../communication/environment.js';
5
+ import { adapterIdentification, authenticationProbe, cliAdapterKind, displayInvocation,
6
+ modelReadinessProbe, type CliInvocation } from '../cli-adapter.js';
7
+ import { MODEL_ENV } from '../worker-cli.js';
8
+ import { CliProbeError, type CliProbeResult } from '../preflight.js';
9
+
10
+ interface ProbeRequest {
11
+ executable: string;
12
+ directory: string;
13
+ invocation: CliInvocation;
14
+ environment: NodeJS.ProcessEnv;
15
+ }
16
+ interface ProbeOutput { status: number | null; stdout: string; stderr: string }
17
+
18
+ /** One decision sequence; checking uses synchronous I/O, live flows yield the loop. */
19
+ export function probeCli(...args: Parameters<typeof probeSequence>): CliProbeResult {
20
+ return driveSync(probeSequence(...args));
21
+ }
22
+
23
+ export async function probeCliAsync(...args: Parameters<typeof probeSequence>): Promise<CliProbeResult> {
24
+ const sequence = probeSequence(...args);
25
+ let next = sequence.next();
26
+ while (!next.done) next = sequence.next(await runProbeAsync(next.value));
27
+ return next.value;
28
+ }
29
+
30
+ export function resolveExecutable(command: string, directory: string): string | undefined {
31
+ return driveSync(executableSequence(command, directory));
32
+ }
33
+
34
+ function driveSync<T>(sequence: Generator<ProbeRequest, T, ProbeOutput>): T {
35
+ let next = sequence.next();
36
+ while (!next.done) {
37
+ const request = next.value;
38
+ const result = spawnSync(request.executable, request.invocation.args, {
39
+ ...probeOptions(request),
40
+ // Preserve the old synchronous probe contract: provider CLIs must not
41
+ // inherit a readable stdin that can block auth/identify probes.
42
+ stdio: ['ignore', 'pipe', 'pipe'],
43
+ });
44
+ const failure = classifySpawnFailure(result.error, result.signal, request.invocation.timeoutMs);
45
+ if (failure !== undefined) throw failure;
46
+ next = sequence.next({ status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? '' });
47
+ }
48
+ return next.value;
49
+ }
50
+
51
+ function probeOptions({ directory, invocation, environment }: ProbeRequest) {
52
+ const env = { ...environment };
53
+ delete env[MODEL_ENV];
54
+ if (invocation.modelEnv !== undefined) env[MODEL_ENV] = invocation.modelEnv;
55
+ return { cwd: directory, encoding: 'utf8' as const, timeout: invocation.timeoutMs,
56
+ maxBuffer: 1024 * 1024, env };
57
+ }
58
+
59
+ function runProbeAsync(request: ProbeRequest): Promise<ProbeOutput> {
60
+ return new Promise((resolve, reject) => {
61
+ const child = execFile(request.executable, request.invocation.args, probeOptions(request),
62
+ (error, stdout, stderr) => {
63
+ // Numeric exit codes are probe results; launch, timeout, signal and buffer
64
+ // errors are failures to collect a fact, just as in the synchronous driver.
65
+ if (error?.killed && typeof error.code !== 'string') return reject(new CliProbeError(`timeout:${request.invocation.timeoutMs}ms`));
66
+ const failure = classifySpawnFailure(
67
+ error !== null && typeof error.code !== 'number' && error.signal == null ? error : undefined,
68
+ error?.signal ?? null, request.invocation.timeoutMs);
69
+ if (failure !== undefined) return reject(failure);
70
+ resolve({ status: error === null ? 0 : typeof error.code === 'number' ? error.code : null,
71
+ stdout, stderr });
72
+ });
73
+ child.stdin?.end();
74
+ });
75
+ }
76
+
77
+ function* probeSequence(
78
+ cli: string,
79
+ directory: string,
80
+ model?: string,
81
+ execution?: 'managed',
82
+ ): Generator<ProbeRequest, CliProbeResult, ProbeOutput> {
83
+ const executable = yield* executableSequence(cli, directory);
84
+ if (executable === undefined) return { exists: false, authenticated: false };
85
+ const kind = cliAdapterKind(executable);
86
+ // Relay owns interactive CLI launch/injection. Its generic PTY path is not
87
+ // the headless wrapper protocol; do not demand that protocol from Gemini,
88
+ // Cursor, OpenCode, or other interactive tools. Never invent an auth pass.
89
+ if (execution === 'managed' && kind === 'relayflows-wrapper-v1') {
90
+ return { exists: true, supported: true, authenticated: 'unverified' };
91
+ }
92
+ const environment = execution === 'managed'
93
+ ? { ...brokerEnvironment(process.env), ...agentEnvironment(executable) } : process.env;
94
+ const probe = (invocation: CliInvocation): ProbeRequest => ({ executable, directory, invocation, environment });
95
+ const identification = adapterIdentification(kind);
96
+ const identified = yield probe(identification.invocation);
97
+ if (
98
+ identified.status !== 0
99
+ || (identification.expectedStdout !== undefined
100
+ && identified.stdout.trim() !== identification.expectedStdout)
101
+ ) {
102
+ return { exists: true, supported: false, authenticated: false };
103
+ }
104
+ const auth = authenticationProbe(kind);
105
+ const authCommand = displayInvocation(cli, auth);
106
+ if (model === undefined) {
107
+ return {
108
+ exists: true,
109
+ supported: true,
110
+ authenticated: (yield probe(auth)).status === 0,
111
+ authCommand,
112
+ };
113
+ }
114
+
115
+ const scoped = modelReadinessProbe(kind, model);
116
+ const modelCommand = displayInvocation(cli, scoped);
117
+ // A successful real provider round trip (or identified wrapper probe)
118
+ // proves both auth and exact-model access. On failure, run the adapter's
119
+ // actual auth command solely to classify auth vs model access truthfully.
120
+ if ((yield probe(scoped)).status === 0) {
121
+ return {
122
+ exists: true,
123
+ supported: true,
124
+ authenticated: true,
125
+ modelAvailable: true,
126
+ authCommand,
127
+ modelCommand,
128
+ };
129
+ }
130
+ const authProbe = yield probe(auth);
131
+ const authenticated = authProbe.status === 0;
132
+ return {
133
+ exists: true,
134
+ supported: true,
135
+ authenticated,
136
+ modelAvailable: false,
137
+ authCommand,
138
+ modelCommand,
139
+ // Only on failure: on success there is nothing to explain, and the output
140
+ // is the most identity-bearing thing this function touches.
141
+ ...(authenticated
142
+ ? {}
143
+ : {
144
+ authExitCode: authProbe.status,
145
+ authFailureDetail: redactProbeOutput(
146
+ `${authProbe.stderr}${authProbe.stdout}`,
147
+ ).trim().slice(0, 500),
148
+ }),
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Redact anything that looks like a credential or an account identifier.
154
+ *
155
+ * `auth status` output is diagnostic, but it is also the one place an account
156
+ * email, org id or token fragment can appear. The point of surfacing it is to
157
+ * say WHY a probe failed, which survives redaction; leaking an identity into a
158
+ * refusal message that gets pasted into issues does not.
159
+ */
160
+ function redactProbeOutput(text: string): string {
161
+ return text
162
+ .replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, '<redacted-email>')
163
+ .replace(/\b(sk|pk|oat|rt)[-_][A-Za-z0-9._-]{8,}/gi, '<redacted-token>')
164
+ .replace(/\b[A-Fa-f0-9]{32,}\b/g, '<redacted-hex>');
165
+ }
166
+
167
+ function* executableSequence(command: string, directory: string): Generator<ProbeRequest, string | undefined, ProbeOutput> {
168
+ if (command.includes('/') || isAbsolute(command)) {
169
+ const path = isAbsolute(command) ? command : resolve(directory, command);
170
+ try {
171
+ accessSync(path, constants.X_OK);
172
+ return path;
173
+ } catch {
174
+ return undefined;
175
+ }
176
+ }
177
+ const result = yield { executable: 'which', directory: process.cwd(),
178
+ invocation: { args: [command], timeoutMs: 5_000 }, environment: process.env };
179
+ return result.status === 0 ? result.stdout.trim() : undefined;
180
+ }
181
+
182
+ function classifySpawnFailure(
183
+ error: Error | undefined,
184
+ signal: NodeJS.Signals | null,
185
+ timeoutMs: number,
186
+ ): CliProbeError | undefined {
187
+ if (error !== undefined) {
188
+ const detail = (error as NodeJS.ErrnoException).code === 'ETIMEDOUT'
189
+ ? `timeout:${timeoutMs}ms` as const
190
+ : 'spawn_failed' as const;
191
+ return new CliProbeError(detail);
192
+ }
193
+ return signal === null ? undefined : new CliProbeError(`signal:${signal}`);
194
+ }
@@ -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
+ }