@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.
- package/dist/agent-artifacts.d.ts.map +1 -1
- package/dist/agent-artifacts.js +2 -9
- package/dist/agent-artifacts.js.map +1 -1
- package/dist/agent-cwd.d.ts +69 -0
- package/dist/agent-cwd.d.ts.map +1 -0
- package/dist/agent-cwd.js +140 -0
- package/dist/agent-cwd.js.map +1 -0
- package/dist/artifact-scan-policy.d.ts +35 -0
- package/dist/artifact-scan-policy.d.ts.map +1 -0
- package/dist/artifact-scan-policy.js +49 -0
- package/dist/artifact-scan-policy.js.map +1 -0
- package/dist/authored-completion-record.d.ts +35 -0
- package/dist/authored-completion-record.d.ts.map +1 -0
- package/dist/authored-completion-record.js +92 -0
- package/dist/authored-completion-record.js.map +1 -0
- package/dist/authored-completion.d.ts +99 -0
- package/dist/authored-completion.d.ts.map +1 -0
- package/dist/authored-completion.js +184 -0
- package/dist/authored-completion.js.map +1 -0
- package/dist/authored-flow-error.d.ts +13 -1
- package/dist/authored-flow-error.d.ts.map +1 -1
- package/dist/authored-flow-error.js +12 -0
- package/dist/authored-flow-error.js.map +1 -1
- package/dist/authored-flow-executor.d.ts +7 -30
- package/dist/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +64 -48
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/authored-human.d.ts.map +1 -1
- package/dist/authored-human.js +1 -3
- package/dist/authored-human.js.map +1 -1
- package/dist/authored-node-entry.js +4 -1
- package/dist/authored-node-entry.js.map +1 -1
- package/dist/authored-node-runner.d.ts +9 -1
- package/dist/authored-node-runner.d.ts.map +1 -1
- package/dist/authored-node-runner.js +92 -17
- package/dist/authored-node-runner.js.map +1 -1
- package/dist/authored-root.d.ts +5 -0
- package/dist/authored-root.d.ts.map +1 -1
- package/dist/authored-root.js +20 -2
- package/dist/authored-root.js.map +1 -1
- package/dist/authored-step-output.d.ts +35 -10
- package/dist/authored-step-output.d.ts.map +1 -1
- package/dist/authored-step-output.js +108 -40
- package/dist/authored-step-output.js.map +1 -1
- package/dist/authored-verdict.d.ts +28 -0
- package/dist/authored-verdict.d.ts.map +1 -0
- package/dist/authored-verdict.js +79 -0
- package/dist/authored-verdict.js.map +1 -0
- package/dist/authored-worker-step.d.ts +5 -2
- package/dist/authored-worker-step.d.ts.map +1 -1
- package/dist/authored-worker-step.js +48 -11
- package/dist/authored-worker-step.js.map +1 -1
- package/dist/bundle-extensions.d.ts.map +1 -1
- package/dist/bundle-extensions.js +4 -7
- package/dist/bundle-extensions.js.map +1 -1
- package/dist/bundle.d.ts.map +1 -1
- package/dist/bundle.js +41 -7
- package/dist/bundle.js.map +1 -1
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +18 -6
- package/dist/canonical.js.map +1 -1
- package/dist/cli/check-worker-surface.d.ts +27 -0
- package/dist/cli/check-worker-surface.d.ts.map +1 -0
- package/dist/cli/check-worker-surface.js +63 -0
- package/dist/cli/check-worker-surface.js.map +1 -0
- package/dist/cli/check.d.ts +19 -2
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +12 -4
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/cloud-format.d.ts +9 -0
- package/dist/cli/cloud-format.d.ts.map +1 -0
- package/dist/cli/cloud-format.js +77 -0
- package/dist/cli/cloud-format.js.map +1 -0
- package/dist/cli/cloud-live.d.ts +39 -0
- package/dist/cli/cloud-live.d.ts.map +1 -0
- package/dist/cli/cloud-live.js +325 -0
- package/dist/cli/cloud-live.js.map +1 -0
- package/dist/cli/cloud-read.d.ts +3 -7
- package/dist/cli/cloud-read.d.ts.map +1 -1
- package/dist/cli/cloud-read.js +14 -266
- package/dist/cli/cloud-read.js.map +1 -1
- package/dist/cli/cloud-refusal.d.ts +39 -0
- package/dist/cli/cloud-refusal.d.ts.map +1 -0
- package/dist/cli/cloud-refusal.js +95 -0
- package/dist/cli/cloud-refusal.js.map +1 -0
- package/dist/cli/cloud-status-view.d.ts +7 -0
- package/dist/cli/cloud-status-view.d.ts.map +1 -0
- package/dist/cli/cloud-status-view.js +214 -0
- package/dist/cli/cloud-status-view.js.map +1 -0
- package/dist/cli/direct-run.d.ts.map +1 -1
- package/dist/cli/direct-run.js +16 -2
- package/dist/cli/direct-run.js.map +1 -1
- package/dist/cli/hn-monitor.d.ts +0 -13
- package/dist/cli/hn-monitor.d.ts.map +1 -1
- package/dist/cli/hn-monitor.js +2 -1
- package/dist/cli/hn-monitor.js.map +1 -1
- package/dist/cli/local-agent-remedy.d.ts +80 -0
- package/dist/cli/local-agent-remedy.d.ts.map +1 -0
- package/dist/cli/local-agent-remedy.js +116 -0
- package/dist/cli/local-agent-remedy.js.map +1 -0
- package/dist/cli/observer-session.d.ts +41 -0
- package/dist/cli/observer-session.d.ts.map +1 -0
- package/dist/cli/observer-session.js +78 -0
- package/dist/cli/observer-session.js.map +1 -0
- package/dist/cli/run.d.ts +30 -2
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +186 -24
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/status.d.ts +9 -0
- package/dist/cli/status.d.ts.map +1 -1
- package/dist/cli/status.js +31 -2
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/step-evidence.d.ts +90 -0
- package/dist/cli/step-evidence.d.ts.map +1 -0
- package/dist/cli/step-evidence.js +259 -0
- package/dist/cli/step-evidence.js.map +1 -0
- package/dist/cli/step-failure.d.ts +14 -4
- package/dist/cli/step-failure.d.ts.map +1 -1
- package/dist/cli/step-failure.js +41 -76
- package/dist/cli/step-failure.js.map +1 -1
- package/dist/cli-commands.d.ts +6 -0
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +2 -0
- package/dist/cli-commands.js.map +1 -1
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +53 -42
- package/dist/cli.js.map +1 -1
- package/dist/cloud-read.d.ts +15 -0
- package/dist/cloud-read.d.ts.map +1 -1
- package/dist/cloud-read.js +20 -1
- package/dist/cloud-read.js.map +1 -1
- package/dist/cloud-run-record.d.ts +27 -0
- package/dist/cloud-run-record.d.ts.map +1 -0
- package/dist/cloud-run-record.js +56 -0
- package/dist/cloud-run-record.js.map +1 -0
- package/dist/cloud-run.d.ts +2 -9
- package/dist/cloud-run.d.ts.map +1 -1
- package/dist/cloud-run.js +3 -20
- package/dist/cloud-run.js.map +1 -1
- package/dist/communication/local.d.ts.map +1 -1
- package/dist/communication/local.js +2 -1
- package/dist/communication/local.js.map +1 -1
- package/dist/communication/worker.d.ts +1 -1
- package/dist/communication/worker.d.ts.map +1 -1
- package/dist/communication/worker.js +13 -5
- package/dist/communication/worker.js.map +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +28 -6
- package/dist/compile.js.map +1 -1
- package/dist/direct-input.d.ts.map +1 -1
- package/dist/direct-input.js +21 -1
- package/dist/direct-input.js.map +1 -1
- package/dist/failure-kinds.d.ts +96 -6
- package/dist/failure-kinds.d.ts.map +1 -1
- package/dist/failure-kinds.js +29 -2
- package/dist/failure-kinds.js.map +1 -1
- package/dist/flow-extension-compat.d.ts.map +1 -1
- package/dist/flow-extension-compat.js +26 -8
- package/dist/flow-extension-compat.js.map +1 -1
- package/dist/flow-extension-loader.d.ts +7 -0
- package/dist/flow-extension-loader.d.ts.map +1 -1
- package/dist/flow-extension-loader.js +48 -22
- package/dist/flow-extension-loader.js.map +1 -1
- package/dist/flow-requirements.d.ts.map +1 -1
- package/dist/flow-requirements.js +24 -9
- package/dist/flow-requirements.js.map +1 -1
- package/dist/fs-descriptor.d.ts +17 -0
- package/dist/fs-descriptor.d.ts.map +1 -0
- package/dist/fs-descriptor.js +96 -0
- package/dist/fs-descriptor.js.map +1 -0
- package/dist/gate-contract.d.ts +7 -0
- package/dist/gate-contract.d.ts.map +1 -1
- package/dist/gate-contract.js +6 -1
- package/dist/gate-contract.js.map +1 -1
- package/dist/helper-preflight.d.ts.map +1 -1
- package/dist/helper-preflight.js +10 -3
- package/dist/helper-preflight.js.map +1 -1
- package/dist/helper-reference.d.ts +25 -0
- package/dist/helper-reference.d.ts.map +1 -0
- package/dist/helper-reference.js +129 -0
- package/dist/helper-reference.js.map +1 -0
- package/dist/hosted-base-snapshot.d.ts +25 -0
- package/dist/hosted-base-snapshot.d.ts.map +1 -0
- package/dist/hosted-base-snapshot.js +278 -0
- package/dist/hosted-base-snapshot.js.map +1 -0
- package/dist/hosted-extension-declarations.d.ts +10 -0
- package/dist/hosted-extension-declarations.d.ts.map +1 -0
- package/dist/hosted-extension-declarations.js +272 -0
- package/dist/hosted-extension-declarations.js.map +1 -0
- package/dist/hosted-extension-isolation.d.ts +82 -0
- package/dist/hosted-extension-isolation.d.ts.map +1 -0
- package/dist/hosted-extension-isolation.js +322 -0
- package/dist/hosted-extension-isolation.js.map +1 -0
- package/dist/hosted-extension-manifest.d.ts +9 -0
- package/dist/hosted-extension-manifest.d.ts.map +1 -0
- package/dist/hosted-extension-manifest.js +235 -0
- package/dist/hosted-extension-manifest.js.map +1 -0
- package/dist/hosted-extension-protocol.d.ts +16 -0
- package/dist/hosted-extension-protocol.d.ts.map +1 -0
- package/dist/hosted-extension-protocol.js +224 -0
- package/dist/hosted-extension-protocol.js.map +1 -0
- package/dist/hosted-extension-runtime.d.ts +30 -0
- package/dist/hosted-extension-runtime.d.ts.map +1 -0
- package/dist/hosted-extension-runtime.js +167 -0
- package/dist/hosted-extension-runtime.js.map +1 -0
- package/dist/hosted-extension-sandbox-source.d.ts +9 -0
- package/dist/hosted-extension-sandbox-source.d.ts.map +1 -0
- package/dist/hosted-extension-sandbox-source.js +95 -0
- package/dist/hosted-extension-sandbox-source.js.map +1 -0
- package/dist/hosted-extension-sandbox.d.ts +39 -0
- package/dist/hosted-extension-sandbox.d.ts.map +1 -0
- package/dist/hosted-extension-sandbox.js +418 -0
- package/dist/hosted-extension-sandbox.js.map +1 -0
- package/dist/hosted-project.d.ts +3 -0
- package/dist/hosted-project.d.ts.map +1 -0
- package/dist/hosted-project.js +19 -0
- package/dist/hosted-project.js.map +1 -0
- package/dist/hosted-promise-safety.d.ts +12 -0
- package/dist/hosted-promise-safety.d.ts.map +1 -0
- package/dist/hosted-promise-safety.js +63 -0
- package/dist/hosted-promise-safety.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/intrinsic-array.d.ts +3 -0
- package/dist/intrinsic-array.d.ts.map +1 -0
- package/dist/intrinsic-array.js +14 -0
- package/dist/intrinsic-array.js.map +1 -0
- package/dist/journal-client.d.ts +3 -1
- package/dist/journal-client.d.ts.map +1 -1
- package/dist/journal-client.js +17 -2
- package/dist/journal-client.js.map +1 -1
- package/dist/journal-projection.d.ts +16 -0
- package/dist/journal-projection.d.ts.map +1 -0
- package/dist/journal-projection.js +108 -0
- package/dist/journal-projection.js.map +1 -0
- package/dist/json-value.d.ts +6 -1
- package/dist/json-value.d.ts.map +1 -1
- package/dist/json-value.js +155 -36
- package/dist/json-value.js.map +1 -1
- package/dist/llm-worker.d.ts +9 -0
- package/dist/llm-worker.d.ts.map +1 -1
- package/dist/llm-worker.js +16 -2
- package/dist/llm-worker.js.map +1 -1
- package/dist/local-agent.d.ts +1 -1
- package/dist/local-agent.d.ts.map +1 -1
- package/dist/local-agent.js +4 -3
- package/dist/local-agent.js.map +1 -1
- package/dist/named-gate-lowering.js +35 -5
- package/dist/named-gate-lowering.js.map +1 -1
- package/dist/named-gate-preflight.d.ts +27 -0
- package/dist/named-gate-preflight.d.ts.map +1 -0
- package/dist/named-gate-preflight.js +47 -0
- package/dist/named-gate-preflight.js.map +1 -0
- package/dist/named-gates.d.ts +1 -1
- package/dist/named-gates.d.ts.map +1 -1
- package/dist/named-gates.js +3 -0
- package/dist/named-gates.js.map +1 -1
- package/dist/observer-link.d.ts +7 -0
- package/dist/observer-link.d.ts.map +1 -1
- package/dist/observer-link.js +3 -1
- package/dist/observer-link.js.map +1 -1
- package/dist/plugin-store.d.ts +8 -2
- package/dist/plugin-store.d.ts.map +1 -1
- package/dist/plugin-store.js +233 -56
- package/dist/plugin-store.js.map +1 -1
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +7 -0
- package/dist/preflight.js.map +1 -1
- package/dist/protocol.d.ts +2 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/redact.d.ts +42 -0
- package/dist/redact.d.ts.map +1 -1
- package/dist/redact.js +128 -0
- package/dist/redact.js.map +1 -1
- package/dist/run-projection.d.ts +83 -0
- package/dist/run-projection.d.ts.map +1 -0
- package/dist/run-projection.js +199 -0
- package/dist/run-projection.js.map +1 -0
- package/dist/semver-range.d.ts.map +1 -1
- package/dist/semver-range.js +27 -15
- package/dist/semver-range.js.map +1 -1
- package/dist/shell-word.d.ts +19 -0
- package/dist/shell-word.d.ts.map +1 -0
- package/dist/shell-word.js +23 -0
- package/dist/shell-word.js.map +1 -0
- package/dist/spec.d.ts +52 -3
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/step-fields.d.ts +1 -1
- package/dist/step-fields.js +1 -1
- package/dist/step-fields.js.map +1 -1
- package/dist/steps-green.d.ts +27 -0
- package/dist/steps-green.d.ts.map +1 -0
- package/dist/steps-green.js +164 -0
- package/dist/steps-green.js.map +1 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +39 -8
- package/dist/validate.js.map +1 -1
- package/dist/worker-lease.d.ts +8 -1
- package/dist/worker-lease.d.ts.map +1 -1
- package/dist/worker-lease.js +34 -4
- package/dist/worker-lease.js.map +1 -1
- package/dist/worker.d.ts +8 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +17 -9
- package/dist/worker.js.map +1 -1
- package/package.json +3 -7
- package/src/agent-artifacts.ts +2 -10
- package/src/agent-cwd.ts +139 -0
- package/src/artifact-scan-policy.ts +51 -0
- package/src/authored-completion-record.ts +119 -0
- package/src/authored-completion.ts +203 -0
- package/src/authored-flow-error.ts +13 -1
- package/src/authored-flow-executor.ts +101 -59
- package/src/authored-human.ts +1 -4
- package/src/authored-node-entry.ts +4 -1
- package/src/authored-node-runner.ts +96 -22
- package/src/authored-root.ts +20 -2
- package/src/authored-step-output.ts +139 -42
- package/src/authored-verdict.ts +87 -0
- package/src/authored-worker-step.ts +60 -12
- package/src/bundle-extensions.ts +6 -5
- package/src/bundle.ts +52 -7
- package/src/canonical.ts +20 -6
- package/src/cli/check-worker-surface.ts +66 -0
- package/src/cli/check.ts +35 -4
- package/src/cli/cloud-format.ts +82 -0
- package/src/cli/cloud-live.ts +366 -0
- package/src/cli/cloud-read.ts +13 -282
- package/src/cli/cloud-refusal.ts +113 -0
- package/src/cli/cloud-status-view.ts +213 -0
- package/src/cli/direct-run.ts +19 -2
- package/src/cli/hn-monitor.ts +2 -1
- package/src/cli/local-agent-remedy.ts +160 -0
- package/src/cli/observer-session.ts +107 -0
- package/src/cli/run.ts +233 -28
- package/src/cli/status.ts +59 -3
- package/src/cli/step-evidence.ts +310 -0
- package/src/cli/step-failure.ts +57 -81
- package/src/cli-commands.ts +2 -0
- package/src/cli.ts +64 -44
- package/src/cloud-read.ts +25 -1
- package/src/cloud-run-record.ts +64 -0
- package/src/cloud-run.ts +5 -24
- package/src/communication/local.ts +2 -1
- package/src/communication/worker.ts +13 -5
- package/src/compile.ts +28 -6
- package/src/direct-input.ts +22 -1
- package/src/failure-kinds.ts +104 -2
- package/src/flow-extension-compat.ts +32 -7
- package/src/flow-extension-loader.ts +56 -21
- package/src/flow-requirements.ts +24 -9
- package/src/fs-descriptor.ts +127 -0
- package/src/gate-contract.ts +13 -1
- package/src/helper-preflight.ts +10 -3
- package/src/helper-reference.ts +130 -0
- package/src/hosted-base-snapshot.ts +351 -0
- package/src/hosted-extension-declarations.ts +387 -0
- package/src/hosted-extension-isolation.ts +457 -0
- package/src/hosted-extension-manifest.ts +249 -0
- package/src/hosted-extension-protocol.ts +262 -0
- package/src/hosted-extension-runtime.ts +266 -0
- package/src/hosted-extension-sandbox-source.ts +94 -0
- package/src/hosted-extension-sandbox.ts +489 -0
- package/src/hosted-project.ts +18 -0
- package/src/hosted-promise-safety.ts +68 -0
- package/src/index.ts +20 -0
- package/src/intrinsic-array.ts +14 -0
- package/src/journal-client.ts +19 -2
- package/src/journal-projection.ts +106 -0
- package/src/json-value.ts +184 -32
- package/src/llm-worker.ts +16 -2
- package/src/local-agent.ts +4 -2
- package/src/named-gate-lowering.ts +35 -5
- package/src/named-gate-preflight.ts +47 -0
- package/src/named-gates.ts +3 -0
- package/src/observer-link.ts +10 -1
- package/src/plugin-store.ts +323 -56
- package/src/preflight.ts +7 -0
- package/src/protocol.ts +2 -0
- package/src/redact.ts +111 -0
- package/src/run-projection.ts +247 -0
- package/src/semver-range.ts +39 -14
- package/src/shell-word.ts +24 -0
- package/src/spec.ts +54 -3
- package/src/step-fields.ts +1 -1
- package/src/steps-green.ts +168 -0
- package/src/validate.ts +35 -8
- package/src/worker-lease.ts +35 -5
- package/src/worker.ts +27 -11
package/src/cli.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { describeFlowRequirements } from './flow-requirements.js';
|
|
|
7
7
|
import type { CliModelSource } from './cli-adapter.js';
|
|
8
8
|
|
|
9
9
|
import { renderProgress, type ProgressEvent } from './progress.js';
|
|
10
|
+
import type { JournalEvent } from './journal-reader.js';
|
|
11
|
+
import { createObserverSession } from './cli/observer-session.js';
|
|
10
12
|
import { realpathSync } from 'node:fs';
|
|
11
13
|
import { pathToFileURL } from 'node:url';
|
|
12
14
|
import {
|
|
@@ -31,6 +33,7 @@ import {
|
|
|
31
33
|
parseLogsArgs, parseRunsArgs, runCloudLogsCli, runCloudRunsCli, runCloudStatusCli,
|
|
32
34
|
type LogsArgs, type RunsArgs,
|
|
33
35
|
} from './cli/cloud-read.js';
|
|
36
|
+
import { runCloudLogsFollow, runCloudStatusWatch } from './cli/cloud-live.js';
|
|
34
37
|
import { transcriptTailSource } from './transcript-tail.js';
|
|
35
38
|
import { checkTypeScriptFlow } from './cli/check-typescript.js';
|
|
36
39
|
import { runCloudCli } from './cli/cloud-run.js';
|
|
@@ -57,6 +60,16 @@ export type { CheckInputDiagnostic, CheckReport } from './cli/check.js';
|
|
|
57
60
|
export interface CliIo {
|
|
58
61
|
stdout(line: string): void;
|
|
59
62
|
stderr(line: string): void;
|
|
63
|
+
/**
|
|
64
|
+
* True when stdout is an interactive terminal.
|
|
65
|
+
*
|
|
66
|
+
* Only the live views read it, and only to decide whether a redraw may use
|
|
67
|
+
* ANSI control sequences: `flows status --cloud --watch` clears the screen
|
|
68
|
+
* for a terminal and appends whole pages when its output is redirected or
|
|
69
|
+
* mounted in a host that renders text. Absent means "not a terminal", so an
|
|
70
|
+
* embedder that says nothing gets the safe form.
|
|
71
|
+
*/
|
|
72
|
+
tty?: boolean;
|
|
60
73
|
}
|
|
61
74
|
|
|
62
75
|
type CliExitCode = 0 | 1 | 2 | 3;
|
|
@@ -123,9 +136,9 @@ const USAGE = [
|
|
|
123
136
|
'flows answer [--json] [--no-spawn] [--data-dir <dir>] [--note <text>] [--by <identity>] <run-id> <wait-id> <yes|no>',
|
|
124
137
|
'flows replay [--allow-human-influenced] [--json] [--data-dir <dir>] <run-id> [--at <step-id>]',
|
|
125
138
|
'flows status [--json] [--data-dir <dir>] [--tail <n>] [<run-id>]',
|
|
126
|
-
'flows status --cloud [--json] <run-id>',
|
|
139
|
+
'flows status --cloud [--json] [--watch] <run-id>',
|
|
127
140
|
'flows runs [--limit <n>] [--json]',
|
|
128
|
-
'flows logs [--step <name>] [--raw] [--json] <run-id>',
|
|
141
|
+
'flows logs [--step <name>] [--raw] [--json] [--follow] <run-id>',
|
|
129
142
|
'flows observer [--data-dir <dir>]',
|
|
130
143
|
'flows hn-monitor start [--data-dir <dir>] [--poll-interval-ms <n>] <spec.json>',
|
|
131
144
|
].join('\n');
|
|
@@ -143,6 +156,7 @@ function spawnAllowedByEnv(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
|
143
156
|
const PROCESS_IO: CliIo = {
|
|
144
157
|
stdout: (line) => process.stdout.write(`${line}\n`),
|
|
145
158
|
stderr: (line) => process.stderr.write(`${line}\n`),
|
|
159
|
+
tty: process.stdout.isTTY === true,
|
|
146
160
|
};
|
|
147
161
|
|
|
148
162
|
/** Optional knobs for an embedded caller. `bin/flows.js` passes none. */
|
|
@@ -222,13 +236,21 @@ export async function runCli(
|
|
|
222
236
|
// works inside a step of a run whose daemon is gone (kernel/DAEMON-LIFECYCLE.md §4).
|
|
223
237
|
if (parsed.command === 'status') {
|
|
224
238
|
// One verb, two sources. `--cloud` never reaches `runStatus`, so the
|
|
225
|
-
// offline reader stays offline (cli/status.ts).
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
239
|
+
// offline reader stays offline (cli/status.ts). Only `--watch` blocks, so
|
|
240
|
+
// only `--watch` takes a signal: a one-shot read keeps installing none.
|
|
241
|
+
if (parsed.cloud !== true) return runStatus(parsed, io, { tails: transcriptTailSource() });
|
|
242
|
+
const cloudStatus = parsed;
|
|
243
|
+
return cloudStatus.watch === true
|
|
244
|
+
? withInterrupt(options.signal, (signal) => runCloudStatusWatch(cloudStatus, io, { signal }))
|
|
245
|
+
: runCloudStatusCli(cloudStatus, io);
|
|
229
246
|
}
|
|
230
247
|
if (parsed.command === 'runs') return runCloudRunsCli(parsed, io);
|
|
231
|
-
if (parsed.command === 'logs')
|
|
248
|
+
if (parsed.command === 'logs') {
|
|
249
|
+
const logs = parsed;
|
|
250
|
+
return logs.follow
|
|
251
|
+
? withInterrupt(options.signal, (signal) => runCloudLogsFollow(logs, io, { signal }))
|
|
252
|
+
: runCloudLogsCli(logs, io);
|
|
253
|
+
}
|
|
232
254
|
if (parsed.command === 'answer') {
|
|
233
255
|
const execution = await answerFlow(parsed.runId, parsed.waitId, parsed.answer, parsed.dataDir, {
|
|
234
256
|
...(parsed.note === undefined ? {} : { note: parsed.note }),
|
|
@@ -250,8 +272,13 @@ export async function runCli(
|
|
|
250
272
|
// refuses `--data-dir` on `check`, so there is no data dir to attach to.
|
|
251
273
|
// `flows check` keeps working with no daemon, no relayflowd binary and no
|
|
252
274
|
// data directory at all -- a property worth keeping, not an omission.
|
|
275
|
+
// Only this invocation opts into `agent_worker_unresolved`: `flows check`
|
|
276
|
+
// attaches no worker and, being daemon-free, cannot see one attached
|
|
277
|
+
// elsewhere. The authored `.flow.ts` path checks header declarations
|
|
278
|
+
// without compiling steps, so it has no agent steps to count.
|
|
253
279
|
const checked = /\.(?:[cm]?[jt]s)$/.test(parsed.value)
|
|
254
|
-
? await checkAuthoredFlowComposed(parsed.value)
|
|
280
|
+
? await checkAuthoredFlowComposed(parsed.value)
|
|
281
|
+
: checkFlow(parsed.value, { warnUnresolvedAgentWorker: true });
|
|
255
282
|
emitCheckReport(checked.report, parsed.json, io);
|
|
256
283
|
return checked.report.ok ? 0 : 2;
|
|
257
284
|
}
|
|
@@ -287,9 +314,11 @@ export async function runCli(
|
|
|
287
314
|
// not here. Hoisting it above the dispatch would start a daemon as a side
|
|
288
315
|
// effect of an invocation that is about to be refused for bad input.
|
|
289
316
|
const startedSteps = new Map<string, number>();
|
|
317
|
+
const observer = parsed.noObserverLink ? undefined : createObserverSession(parsed.command, io);
|
|
290
318
|
const showProgress = (event: ProgressEvent): void => {
|
|
291
319
|
if (event.type === 'step.started') startedSteps.set(event.stepId, performance.now());
|
|
292
320
|
if (!parsed.json) for (const line of renderProgress([event])) io.stderr(line);
|
|
321
|
+
observer?.onProgress(event);
|
|
293
322
|
};
|
|
294
323
|
const lifecycle = {
|
|
295
324
|
...(parsed.command === 'run' ? { bucket: parsed.bucket } : {}),
|
|
@@ -299,6 +328,10 @@ export async function runCli(
|
|
|
299
328
|
localAgent: parsed.localAgent,
|
|
300
329
|
...(parsed.agentCapacity === undefined ? {} : { agentCapacity: parsed.agentCapacity }),
|
|
301
330
|
onProgress: showProgress,
|
|
331
|
+
...(observer === undefined ? {} : {
|
|
332
|
+
onJournalEntry: (entry: JournalEvent) => observer.onJournalEntry(entry),
|
|
333
|
+
onRunStarted: (run: { runId: string; flow: string; resumed?: boolean }) => observer.onRunStarted(run),
|
|
334
|
+
}),
|
|
302
335
|
onWait: (progress: RunProgress) => {
|
|
303
336
|
emitWait(progress, io);
|
|
304
337
|
const now = performance.now();
|
|
@@ -308,16 +341,15 @@ export async function runCli(
|
|
|
308
341
|
},
|
|
309
342
|
daemon: { spawn: parsed.spawn && spawnAllowedByEnv() },
|
|
310
343
|
};
|
|
311
|
-
// Mint the observer token in parallel with the run so the mint round-trip
|
|
312
|
-
// never adds to the RUN summary latency. The outcome is only consulted at
|
|
313
|
-
// emit time; a rejected promise here can never fail the run (see
|
|
314
|
-
// `observerUrlFrom`, which swallows every failure into `warning`).
|
|
315
|
-
const observerMint = startObserverMint(parsed);
|
|
316
344
|
const execution = parsed.command === 'run'
|
|
317
345
|
? isAuthoredFlowPath(parsed.value)
|
|
318
346
|
? await runDirectFlow(parsed.value, parsed.input, parsed.dataDir, lifecycle)
|
|
319
347
|
: await runFlow(parsed.value, parsed.dataDir, lifecycle)
|
|
320
348
|
: await resumeFlow(parsed.value, parsed.dataDir, lifecycle);
|
|
349
|
+
// The link is scoped to the run's channel, so it exists only once the run
|
|
350
|
+
// does. `finish` drains the projection and settles the mint, both bounded;
|
|
351
|
+
// it never rejects (see `createObserverSession`).
|
|
352
|
+
const observerMint = observer?.finish(execution.report);
|
|
321
353
|
// In `--json` mode the report is a single machine-readable object that
|
|
322
354
|
// MUST carry `observerUrl` when one is available, so a consumer sees one
|
|
323
355
|
// authoritative signal. That justifies blocking up to `MINT_TIMEOUT_MS`
|
|
@@ -369,35 +401,6 @@ async function checkAuthoredFlowComposed(path: string): Promise<{ report: CheckR
|
|
|
369
401
|
};
|
|
370
402
|
}
|
|
371
403
|
|
|
372
|
-
/**
|
|
373
|
-
* Start the observer-token mint if the environment says one should happen.
|
|
374
|
-
* Returns `undefined` when no attempt should be made — no workspace key
|
|
375
|
-
* configured, or `FLOWS_NO_OBSERVER=1` / `--no-observer-link` — which is the
|
|
376
|
-
* silent-skip branch. The returned promise always resolves; a rejection here
|
|
377
|
-
* would slip past `observerUrlFrom` and could fail the run, which the feature
|
|
378
|
-
* expressly forbids.
|
|
379
|
-
*/
|
|
380
|
-
function startObserverMint(
|
|
381
|
-
parsed: { command: 'run' | 'resume'; noObserverLink: boolean },
|
|
382
|
-
env: NodeJS.ProcessEnv = process.env,
|
|
383
|
-
mint: (options: MintObserverOptions) => Promise<{ observerUrl?: string; warning?: string }> = mintObserverUrl,
|
|
384
|
-
): Promise<{ observerUrl?: string; warning?: string }> | undefined {
|
|
385
|
-
if (parsed.noObserverLink) return undefined;
|
|
386
|
-
// `resolveObserverLinkEnv` (not `readObserverLinkEnv`) falls back to the
|
|
387
|
-
// `agent-relay cloud login` workspace store (~/.agentworkforce/relay/
|
|
388
|
-
// workspaces.json) when RELAYCAST_WORKSPACE_KEY is unset. Env wins if set;
|
|
389
|
-
// FLOWS_NO_OBSERVER=1 still suppresses regardless of source.
|
|
390
|
-
const link = resolveObserverLinkEnv(env);
|
|
391
|
-
if (link.suppressed || link.workspaceKey === undefined) return undefined;
|
|
392
|
-
return mint({
|
|
393
|
-
workspaceKey: link.workspaceKey,
|
|
394
|
-
...(link.baseUrl !== undefined ? { baseUrl: link.baseUrl } : {}),
|
|
395
|
-
...(link.dashboardUrl !== undefined ? { dashboardUrl: link.dashboardUrl } : {}),
|
|
396
|
-
}).catch((error) => ({
|
|
397
|
-
warning: error instanceof Error ? error.message : 'unknown mint error',
|
|
398
|
-
}));
|
|
399
|
-
}
|
|
400
|
-
|
|
401
404
|
/**
|
|
402
405
|
* Resolve the pending observer-mint into a URL (or nothing), and route any
|
|
403
406
|
* mint warning to stderr under a `[observer]` label. Silent-skip (`undefined`
|
|
@@ -941,6 +944,18 @@ function parseTickArgs(rest: readonly string[]): ParsedArgs | undefined {
|
|
|
941
944
|
};
|
|
942
945
|
}
|
|
943
946
|
|
|
947
|
+
/**
|
|
948
|
+
* `agent_worker_unresolved` reads as a footnote to `REQUIRES codex (step
|
|
949
|
+
* "implement"), …`: that line already names the steps that need an agent
|
|
950
|
+
* worker, and this says what has to be true for one to be attached. So the
|
|
951
|
+
* plain-text pass holds it back and emits it in that position, exactly once —
|
|
952
|
+
* the leading batch below skips it rather than printing it twice. JSON mode
|
|
953
|
+
* returns before any of this and keeps the single ordered diagnostics array.
|
|
954
|
+
*/
|
|
955
|
+
function isWorkerSurfaceWarning(diagnostic: CheckReport['diagnostics'][number]): boolean {
|
|
956
|
+
return diagnostic.kind === 'agent_worker_unresolved';
|
|
957
|
+
}
|
|
958
|
+
|
|
944
959
|
const MODEL_PROVENANCE: Readonly<Record<CliModelSource, string>> = {
|
|
945
960
|
step: 'step',
|
|
946
961
|
named: 'named agent',
|
|
@@ -953,16 +968,20 @@ function modelProvenance(source: CliModelSource | undefined): string {
|
|
|
953
968
|
}
|
|
954
969
|
|
|
955
970
|
function emitCheckReport(report: CheckReport, json: boolean, io: CliIo): void {
|
|
956
|
-
emitDiagnostics(report.diagnostics, io);
|
|
957
971
|
if (json) {
|
|
972
|
+
emitDiagnostics(report.diagnostics, io);
|
|
958
973
|
io.stdout(JSON.stringify(report));
|
|
959
974
|
return;
|
|
960
975
|
}
|
|
976
|
+
const deferred = report.diagnostics.filter(isWorkerSurfaceWarning);
|
|
977
|
+
emitDiagnostics(report.diagnostics.filter((diagnostic) => !isWorkerSurfaceWarning(diagnostic)), io);
|
|
961
978
|
for (const gate of report.gates) {
|
|
962
979
|
// A gate that accepts every output is legal, but it must not read like a
|
|
963
980
|
// gate that judges something.
|
|
964
981
|
const vacuous = gate.acceptsAnyOutput === true ? ' [json_schema accepts any output]' : '';
|
|
965
|
-
|
|
982
|
+
// Red-but-complete is a declared shape, so it is a visible one.
|
|
983
|
+
const recorded = gate.recordsNonZeroExit === true ? ' [onNonZero: record]' : '';
|
|
984
|
+
io.stdout(`GATE step "${gate.stepId}" ${gate.checks.join('+')} from data (kernel, journal-replayable)${vacuous}${recorded}`);
|
|
966
985
|
}
|
|
967
986
|
for (const schedule of report.schedules ?? []) {
|
|
968
987
|
const declared = schedule.cron !== undefined
|
|
@@ -1001,6 +1020,7 @@ function emitCheckReport(report: CheckReport, json: boolean, io: CliIo): void {
|
|
|
1001
1020
|
// the hosted verbs check the same list against Cloud before submitting.
|
|
1002
1021
|
const requires = report.requirements === undefined ? '' : describeFlowRequirements(report.requirements);
|
|
1003
1022
|
if (requires) io.stdout(`REQUIRES ${requires}`);
|
|
1023
|
+
emitDiagnostics(deferred, io);
|
|
1004
1024
|
if (report.ok) io.stdout(`CHECK PASSED ${report.path ?? ''}`.trimEnd());
|
|
1005
1025
|
}
|
|
1006
1026
|
|
package/src/cloud-read.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// key is ignored, a route that drops one yields `null`, never a throw.
|
|
16
16
|
|
|
17
17
|
import { CloudFlowError, cloudFetch, cloudRunId, isCloudRecord, type CloudConnectionOptions } from './cloud-http.js';
|
|
18
|
+
import { cloudRunState, type CloudRunState } from './cloud-run-record.js';
|
|
18
19
|
|
|
19
20
|
/** One row of `GET /api/v1/workflows/runs`. */
|
|
20
21
|
export interface CloudRunSummary {
|
|
@@ -334,8 +335,31 @@ export async function getCloudRunDetail(
|
|
|
334
335
|
options: CloudConnectionOptions = {},
|
|
335
336
|
): Promise<CloudRunDetail> {
|
|
336
337
|
const asked = callerRunId(runId);
|
|
337
|
-
|
|
338
|
+
return runDetail(asked, await cloudFetch(`/api/v1/workflows/runs/${asked}`, options,
|
|
338
339
|
{ method: 'GET', detail: true }));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* The run record twice over: what to render, and what it attests.
|
|
344
|
+
*
|
|
345
|
+
* `--watch` and `--follow` need both, from the same bytes. The rendered view
|
|
346
|
+
* is the permissive mapping below, which shows whatever Cloud said; the exit
|
|
347
|
+
* code comes from `cloudRunState`, the same validator `flows run --wait`
|
|
348
|
+
* blocks on, so a live read cannot exit 0 on a record the waiter refuses.
|
|
349
|
+
* One request, because a second GET per poll could observe a different run
|
|
350
|
+
* than the one that was drawn.
|
|
351
|
+
*/
|
|
352
|
+
export async function getCloudRunDetailLive(
|
|
353
|
+
runId: string,
|
|
354
|
+
options: CloudConnectionOptions = {},
|
|
355
|
+
): Promise<{ detail: CloudRunDetail; state: CloudRunState }> {
|
|
356
|
+
const asked = callerRunId(runId);
|
|
357
|
+
const answered = await cloudFetch(`/api/v1/workflows/runs/${asked}`, options, { method: 'GET', detail: true });
|
|
358
|
+
return { detail: runDetail(asked, answered), state: cloudRunState(answered, asked) };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function runDetail(asked: string, answeredBody: unknown): CloudRunDetail {
|
|
362
|
+
const body = record(answeredBody);
|
|
339
363
|
if (body === null) throw new CloudFlowError('invalid_response', 'Cloud returned no run record.');
|
|
340
364
|
// The record has to be the record that was asked for, and it has to say what
|
|
341
365
|
// the run is doing. `getCloudFlowRun` (cloud-run.ts) already refuses a
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// What a Cloud run record has to say before this client will act on it.
|
|
2
|
+
//
|
|
3
|
+
// One validator, two callers. `waitForCloudFlowRun` (cloud-run.ts) blocks on
|
|
4
|
+
// it to decide when a hosted `flows run --wait` is over, and the live read
|
|
5
|
+
// verbs (cli/cloud-live.ts, through `getCloudRunDetailLive`) block on it to
|
|
6
|
+
// decide when `--watch` and `--follow` stop and with which exit code. Both
|
|
7
|
+
// have to mean the same thing by "this run is over, and it ended well", or a
|
|
8
|
+
// watch would exit 0 on a record the waiter refuses.
|
|
9
|
+
//
|
|
10
|
+
// Deliberately strict and deliberately separate from the *permissive*
|
|
11
|
+
// `getCloudRunDetail` mapping, which exists to show a reader whatever Cloud
|
|
12
|
+
// said. Attesting an outcome is a different job to rendering one: a
|
|
13
|
+
// `completed` header with no valid `completionReason` attests nothing, and
|
|
14
|
+
// must not become exit 0.
|
|
15
|
+
|
|
16
|
+
import { RUN_COMPLETION_REASONS } from '@relayflows/surface';
|
|
17
|
+
import { CloudFlowError, isCloudRecord } from './cloud-http.js';
|
|
18
|
+
import type { RunCompletionReason } from './protocol.js';
|
|
19
|
+
|
|
20
|
+
export type CloudRunState =
|
|
21
|
+
| { runId: string; status: 'pending' | 'launching' | 'running' }
|
|
22
|
+
| { runId: string; status: 'completed' | 'failed' | 'cancelled'; completionReason: RunCompletionReason };
|
|
23
|
+
|
|
24
|
+
/** The statuses that mean the hosted run has not finished. */
|
|
25
|
+
const ACTIVE_RUN_STATUSES: readonly string[] = ['pending', 'launching', 'running'];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Is this run still going?
|
|
29
|
+
*
|
|
30
|
+
* The one vocabulary for the question, so `run --cloud --wait`,
|
|
31
|
+
* `status --cloud --watch` and `logs --follow` cannot disagree about what
|
|
32
|
+
* `launching` means. Note this is about the *run*: a `needs_human` step is
|
|
33
|
+
* not a run status and never reaches here.
|
|
34
|
+
*/
|
|
35
|
+
export function isCloudRunActive(status: string): boolean {
|
|
36
|
+
return ACTIVE_RUN_STATUSES.includes(status);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validate a raw run record into an execution outcome, or refuse.
|
|
41
|
+
*
|
|
42
|
+
* Takes the body as Cloud answered it so a caller that also wants the
|
|
43
|
+
* renderable projection can make one request and validate the same bytes it
|
|
44
|
+
* rendered, rather than issuing a second GET per poll.
|
|
45
|
+
*/
|
|
46
|
+
export function cloudRunState(body: unknown, runId: string): CloudRunState {
|
|
47
|
+
if (!isCloudRecord(body) || body.runId !== runId || body.relayflowVersion !== 'v2'
|
|
48
|
+
|| typeof body.status !== 'string'
|
|
49
|
+
|| !['pending', 'launching', 'running', 'completed', 'failed', 'cancelled'].includes(body.status)) {
|
|
50
|
+
throw new CloudFlowError('invalid_response', 'Cloud returned an invalid v2 run record.');
|
|
51
|
+
}
|
|
52
|
+
if (isCloudRunActive(body.status)) {
|
|
53
|
+
return { runId, status: body.status as 'pending' | 'launching' | 'running' };
|
|
54
|
+
}
|
|
55
|
+
const report = body.result;
|
|
56
|
+
const reason = isCloudRecord(report) ? report.completionReason : undefined;
|
|
57
|
+
if (typeof reason !== 'string' || !(RUN_COMPLETION_REASONS as readonly string[]).includes(reason)
|
|
58
|
+
|| (body.status === 'completed' && (reason !== 'success' || !isCloudRecord(report) || report.ok !== true || report.status !== 'completed'))
|
|
59
|
+
|| (body.status === 'failed' && !['step_failed', 'budget_exceeded'].includes(reason))
|
|
60
|
+
|| (body.status === 'cancelled' && reason !== 'canceled')) {
|
|
61
|
+
throw new CloudFlowError('invalid_response', 'Cloud terminal record lacks a valid, consistent run completionReason; no execution outcome is attested.');
|
|
62
|
+
}
|
|
63
|
+
return { runId, status: body.status as 'completed' | 'failed' | 'cancelled', completionReason: reason as RunCompletionReason };
|
|
64
|
+
}
|
package/src/cloud-run.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { RunCompletionReason } from './protocol.js';
|
|
1
|
+
import { type ScheduleTriggerSource } from '@relayflows/surface';
|
|
3
2
|
import { readFile } from 'node:fs/promises';
|
|
4
3
|
import { createHash } from 'node:crypto';
|
|
5
4
|
import { setTimeout } from 'node:timers/promises';
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
CloudFlowError, cloudConnection, cloudFetch, cloudRequest, cloudRunId, isCloudRecord,
|
|
15
14
|
type CloudConnectionOptions,
|
|
16
15
|
} from './cloud-http.js';
|
|
16
|
+
import { cloudRunState, isCloudRunActive, type CloudRunState } from './cloud-run-record.js';
|
|
17
17
|
import { packWorkingTree, prepareCloudSync, uploadCloudCode } from './cloud-sync.js';
|
|
18
18
|
|
|
19
19
|
export type CloudFlowSource = FlowSpec | { path: string };
|
|
@@ -66,9 +66,7 @@ export function cloudSubmissionBody(submission: CloudSubmission): Record<string,
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
export type CloudRunState
|
|
70
|
-
| { runId: string; status: 'pending' | 'launching' | 'running' }
|
|
71
|
-
| { runId: string; status: 'completed' | 'failed' | 'cancelled'; completionReason: RunCompletionReason };
|
|
69
|
+
export type { CloudRunState };
|
|
72
70
|
|
|
73
71
|
/**
|
|
74
72
|
* Submit a declarative flow to Cloud's pinned v2 runtime. Compilation only
|
|
@@ -269,24 +267,7 @@ export async function getCloudFlowRun(
|
|
|
269
267
|
options: CloudConnectionOptions = {},
|
|
270
268
|
): Promise<CloudRunState> {
|
|
271
269
|
cloudRunId(runId);
|
|
272
|
-
|
|
273
|
-
if (!isCloudRecord(result) || result.runId !== runId || result.relayflowVersion !== 'v2'
|
|
274
|
-
|| typeof result.status !== 'string'
|
|
275
|
-
|| !['pending', 'launching', 'running', 'completed', 'failed', 'cancelled'].includes(result.status)) {
|
|
276
|
-
throw new CloudFlowError('invalid_response', 'Cloud returned an invalid v2 run record.');
|
|
277
|
-
}
|
|
278
|
-
if (result.status === 'pending' || result.status === 'launching' || result.status === 'running') {
|
|
279
|
-
return { runId, status: result.status };
|
|
280
|
-
}
|
|
281
|
-
const report = result.result;
|
|
282
|
-
const reason = isCloudRecord(report) ? report.completionReason : undefined;
|
|
283
|
-
if (typeof reason !== 'string' || !(RUN_COMPLETION_REASONS as readonly string[]).includes(reason)
|
|
284
|
-
|| (result.status === 'completed' && (reason !== 'success' || !isCloudRecord(report) || report.ok !== true || report.status !== 'completed'))
|
|
285
|
-
|| (result.status === 'failed' && !['step_failed', 'budget_exceeded'].includes(reason))
|
|
286
|
-
|| (result.status === 'cancelled' && reason !== 'canceled')) {
|
|
287
|
-
throw new CloudFlowError('invalid_response', 'Cloud terminal record lacks a valid, consistent run completionReason; no execution outcome is attested.');
|
|
288
|
-
}
|
|
289
|
-
return { runId, status: result.status as 'completed' | 'failed' | 'cancelled', completionReason: reason as RunCompletionReason };
|
|
270
|
+
return cloudRunState(await cloudRequest(`/api/v1/workflows/runs/${runId}`, options), runId);
|
|
290
271
|
}
|
|
291
272
|
|
|
292
273
|
/** Wait without a fixed execution deadline. Abort stops observation, not the hosted run. */
|
|
@@ -304,7 +285,7 @@ export async function waitForCloudFlowRun(
|
|
|
304
285
|
try {
|
|
305
286
|
const run = await getCloudFlowRun(runId, options);
|
|
306
287
|
failures = 0;
|
|
307
|
-
if (run.status
|
|
288
|
+
if (!isCloudRunActive(run.status)) return run;
|
|
308
289
|
} catch (error) {
|
|
309
290
|
options.signal?.throwIfAborted();
|
|
310
291
|
const transient = error instanceof CloudFlowError && (error.code === 'transient_error'
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { onWorkerFailure } from '../worker-lease.js';
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { JournalClient } from '../journal-client.js';
|
|
3
4
|
import { AgentWorker } from '../worker.js';
|
|
@@ -31,7 +32,7 @@ export async function attachCommunicationWorkers(spec: KernelRunSpec, socketPath
|
|
|
31
32
|
requiredStreams: [channelName(step.id, '$receipts')],
|
|
32
33
|
pins: { workspace: [], streams: step.surfaces?.streams?.map(({ stream }) => ({ stream, read_offset: 0 })) ?? [] } });
|
|
33
34
|
workers.push({ client, worker });
|
|
34
|
-
worker.on('error', error => { failure = error; client.close(); });
|
|
35
|
+
worker.on('error', onWorkerFailure('communication', error => { failure = error; client.close(); }));
|
|
35
36
|
await client.connect();
|
|
36
37
|
await client.hello('flows-communication');
|
|
37
38
|
await worker.attach();
|
|
@@ -7,6 +7,7 @@ import type { KernelAgentStep } from '../spec.js';
|
|
|
7
7
|
import { withWorkerLease } from '../worker-lease.js';
|
|
8
8
|
import { workerInstruction } from '../worker-input.js';
|
|
9
9
|
import { resolveCliModel } from '../cli-adapter.js';
|
|
10
|
+
import { resolveAgentCwd } from '../agent-cwd.js';
|
|
10
11
|
import { channelName, type CommunicationInstruction } from './spec.js';
|
|
11
12
|
import { acquireRelayRuntime, type RelayHandle } from './relay.js';
|
|
12
13
|
import { CommunicationSession } from './session.js';
|
|
@@ -17,13 +18,13 @@ export function requireCommunicationCli(cli: string | undefined): void {
|
|
|
17
18
|
if (!cli?.trim()) throw new Error('Agent communication requires a declared CLI executable');
|
|
18
19
|
}
|
|
19
20
|
export async function completeCommunicationDispatch(client: JournalClient, dispatch: StepDispatchEvent,
|
|
20
|
-
instruction: CommunicationInstruction, dataDir: string): Promise<void> {
|
|
21
|
+
instruction: CommunicationInstruction, dataDir: string, runRoot?: string): Promise<void> {
|
|
21
22
|
const spec = dispatch.spec as KernelAgentStep;
|
|
22
23
|
requireCommunicationCli(spec.cli);
|
|
23
24
|
let output: unknown;
|
|
24
25
|
let completionReason: 'success' | 'worker_error' = 'success';
|
|
25
26
|
try {
|
|
26
|
-
output = await withWorkerLease(client, dispatch, signal => run(client, dispatch, instruction, spec, dataDir, signal));
|
|
27
|
+
output = await withWorkerLease(client, dispatch, signal => run(client, dispatch, instruction, spec, dataDir, signal, runRoot));
|
|
27
28
|
} catch (error) {
|
|
28
29
|
completionReason = 'worker_error';
|
|
29
30
|
output = { error: error instanceof Error ? error.message : String(error) };
|
|
@@ -33,7 +34,14 @@ export async function completeCommunicationDispatch(client: JournalClient, dispa
|
|
|
33
34
|
started_pins: dispatch.pins, end_pins: dispatch.pins });
|
|
34
35
|
}
|
|
35
36
|
async function run(client: JournalClient, dispatch: StepDispatchEvent, instruction: CommunicationInstruction,
|
|
36
|
-
spec: KernelAgentStep, dataDir: string, lease: AbortSignal): Promise<unknown> {
|
|
37
|
+
spec: KernelAgentStep, dataDir: string, lease: AbortSignal, runRoot?: string): Promise<unknown> {
|
|
38
|
+
// Same contract as the CLI worker: a declared directory is resolved and held
|
|
39
|
+
// inside the same run root the CLI worker measures against, before anything
|
|
40
|
+
// is spawned. Thrown, not reported, because `completeCommunicationDispatch`
|
|
41
|
+
// already turns a throw here into a `worker_error` completion carrying the
|
|
42
|
+
// message.
|
|
43
|
+
const root = runRoot ?? process.cwd();
|
|
44
|
+
const directory = resolveAgentCwd(root, spec.cwd) ?? root;
|
|
37
45
|
const controller = new AbortController();
|
|
38
46
|
const signal = AbortSignal.any([lease, controller.signal, AbortSignal.timeout(instruction.timeoutMs)]);
|
|
39
47
|
const relay = await acquireRelayRuntime(dataDir, dispatch.run_id);
|
|
@@ -70,9 +78,9 @@ async function run(client: JournalClient, dispatch: StepDispatchEvent, instructi
|
|
|
70
78
|
});
|
|
71
79
|
// Relay supplies each CLI's launch flags and injection behavior.
|
|
72
80
|
handle = await relay.broker.spawnPty({ name, cli: basename(spec.cli!).replace(/\.exe$/i, ''), task: prompt, channels: [], skipRelayPrompt: true,
|
|
73
|
-
model: resolveCliModel(spec.cli!, spec.model), cwd:
|
|
81
|
+
model: resolveCliModel(spec.cli!, spec.model), cwd: directory,
|
|
74
82
|
harnessConfig: { runtime: 'pty', command: quote(spec.cli!), args: [],
|
|
75
|
-
cwd:
|
|
83
|
+
cwd: directory, env: { ...agentEnvironment(spec.cli!),
|
|
76
84
|
RELAYFLOW_COMMUNICATION_SOCKET: tools.path, RELAYFLOW_COMMUNICATION_TOKEN: tools.token },
|
|
77
85
|
delivery: { mode: 'pty-injection', format: 'relay-block' } } });
|
|
78
86
|
const ready = await handle.waitForReady(Math.min(instruction.timeoutMs, 90_000));
|
package/src/compile.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { parseBudget, toKernelBudget } from './budget.js';
|
|
|
19
19
|
import { bindingDependencies } from './input-binding.js';
|
|
20
20
|
import { namedGateFailure } from './named-gates.js';
|
|
21
21
|
import { lowerNamedGates } from './named-gate-lowering.js';
|
|
22
|
+
import { lowerStepsGreenGates } from './steps-green.js';
|
|
22
23
|
import type {
|
|
23
24
|
AgentStepSpec,
|
|
24
25
|
DeterministicStepSpec,
|
|
@@ -208,6 +209,10 @@ function compileStep(step: StepSpec): StepSpec {
|
|
|
208
209
|
// their dispatch timeout. It must be spread HERE and nowhere in `base`.
|
|
209
210
|
...(s.timeoutMs !== undefined ? { timeoutMs: s.timeoutMs } : {}),
|
|
210
211
|
...(s.lease_ms !== undefined ? { lease_ms: parseStepTimeout(s.lease_ms) } : {}),
|
|
212
|
+
// Normalized away when it names the default, so an author who spells
|
|
213
|
+
// out `fail` gets the same canonical spec — and the same hash — as one
|
|
214
|
+
// who omits the field entirely.
|
|
215
|
+
...(s.onNonZero !== undefined && s.onNonZero !== 'fail' ? { onNonZero: s.onNonZero } : {}),
|
|
211
216
|
};
|
|
212
217
|
}
|
|
213
218
|
case 'llm': {
|
|
@@ -259,6 +264,11 @@ function outputGate(gate: VerificationSpec, stepId: string): OutputVerificationS
|
|
|
259
264
|
`step "${stepId}": exit_code is supported only on deterministic steps`,
|
|
260
265
|
]);
|
|
261
266
|
}
|
|
267
|
+
if (gate.type === 'steps_green') {
|
|
268
|
+
throw new CompileError([
|
|
269
|
+
`step "${stepId}": gate_host_unsupported: steps_green is supported only on deterministic steps`,
|
|
270
|
+
], 'gate_host_unsupported');
|
|
271
|
+
}
|
|
262
272
|
return gate;
|
|
263
273
|
}
|
|
264
274
|
|
|
@@ -331,7 +341,13 @@ export function toKernelSpec(flow: FlowSpec): KernelRunSpec {
|
|
|
331
341
|
// reverts the other, and `validateSpec` and `flows check` would both still
|
|
332
342
|
// look correct. See ops/reviews/20260903-pr139-repair-0903.md section 10.
|
|
333
343
|
...(compiled.triggers?.length ? { triggers: compiled.triggers.map(toKernelTrigger) } : {}),
|
|
334
|
-
|
|
344
|
+
// `steps_green` lowers FIRST: it rewrites the steps it reads to the
|
|
345
|
+
// permissive envelope schema its bindings need, and the named-gate pass
|
|
346
|
+
// then adds each source's own gate barrier to the generated step. The
|
|
347
|
+
// reverse order would hand the green gate a source whose barrier it does
|
|
348
|
+
// not wait for.
|
|
349
|
+
steps: lowerNamedGates(lowerStepsGreenGates(compiled.steps))
|
|
350
|
+
.map((step) => toKernelStep(resolveNamedAgent(step, compiled.agents))),
|
|
335
351
|
...(compiled.budget !== undefined ? { budget: toKernelBudget(compiled.budget) } : {}),
|
|
336
352
|
};
|
|
337
353
|
}
|
|
@@ -426,18 +442,18 @@ function kernelTriggerToAuthoring(value: unknown, at: string): unknown {
|
|
|
426
442
|
function kernelStepToAuthoring(value: unknown, at: string): unknown {
|
|
427
443
|
const unionKeys = [
|
|
428
444
|
'id', 'type', 'depends_on', 'max_iterations', 'retry', 'verification', 'memory', 'requirements', 'input',
|
|
429
|
-
'command', 'timeout_ms', 'lease_ms', 'prompt', 'model', 'cli', 'instruction',
|
|
430
|
-
'recovery_mode', 'surfaces', 'permissions',
|
|
445
|
+
'command', 'timeout_ms', 'lease_ms', 'on_non_zero', 'prompt', 'model', 'cli', 'instruction',
|
|
446
|
+
'cwd', 'recovery_mode', 'surfaces', 'permissions',
|
|
431
447
|
] as const;
|
|
432
448
|
const step = requireKernelObject(value, unionKeys, at);
|
|
433
449
|
const type = step['type'];
|
|
434
450
|
const commonKeys = ['id', 'type', 'depends_on', 'max_iterations', 'retry', 'verification', 'memory', 'requirements', 'input'] as const;
|
|
435
451
|
const typeKeys = type === 'deterministic'
|
|
436
|
-
? ['command', 'timeout_ms', 'lease_ms'] as const
|
|
452
|
+
? ['command', 'timeout_ms', 'lease_ms', 'on_non_zero'] as const
|
|
437
453
|
: type === 'llm'
|
|
438
454
|
? ['prompt', 'model', 'cli'] as const
|
|
439
455
|
: type === 'agent'
|
|
440
|
-
? ['instruction', 'cli', 'model', 'recovery_mode', 'surfaces', 'permissions'] as const
|
|
456
|
+
? ['instruction', 'cli', 'model', 'cwd', 'recovery_mode', 'surfaces', 'permissions'] as const
|
|
441
457
|
: [];
|
|
442
458
|
assertKernelKeys(step, [...commonKeys, ...typeKeys], at);
|
|
443
459
|
if (step['retry'] !== undefined) validateAuthoringRetryDefaults(step['retry'], `${at}.retry`);
|
|
@@ -460,6 +476,7 @@ function kernelStepToAuthoring(value: unknown, at: string): unknown {
|
|
|
460
476
|
command: step['command'],
|
|
461
477
|
...(step['timeout_ms'] !== undefined ? { timeoutMs: step['timeout_ms'] } : {}),
|
|
462
478
|
...(step['lease_ms'] !== undefined ? { lease_ms: step['lease_ms'] } : {}),
|
|
479
|
+
...(step['on_non_zero'] !== undefined ? { onNonZero: step['on_non_zero'] } : {}),
|
|
463
480
|
};
|
|
464
481
|
}
|
|
465
482
|
if (type === 'llm') {
|
|
@@ -470,7 +487,7 @@ function kernelStepToAuthoring(value: unknown, at: string): unknown {
|
|
|
470
487
|
...common,
|
|
471
488
|
instruction: step['instruction'],
|
|
472
489
|
...(step['recovery_mode'] !== undefined ? { recoveryMode: step['recovery_mode'] } : {}),
|
|
473
|
-
...copyDefined(step, ['cli', 'model', 'surfaces']),
|
|
490
|
+
...copyDefined(step, ['cli', 'model', 'cwd', 'surfaces']),
|
|
474
491
|
...(step['permissions'] !== undefined
|
|
475
492
|
? { permissions: kernelPermissionsToAuthoring(step['permissions'], `${at}.permissions`) }
|
|
476
493
|
: {}),
|
|
@@ -610,6 +627,11 @@ function toKernelStep(step: StepSpec): KernelStepSpec {
|
|
|
610
627
|
command: step.command,
|
|
611
628
|
...(step.timeoutMs !== undefined ? { timeout_ms: step.timeoutMs } : {}),
|
|
612
629
|
...(step.lease_ms !== undefined ? { lease_ms: parseStepTimeout(step.lease_ms) } : {}),
|
|
630
|
+
// Omitted for the default, exactly as the kernel skips serializing it:
|
|
631
|
+
// a spec written before this field existed keeps its canonical bytes
|
|
632
|
+
// and therefore its spec hash.
|
|
633
|
+
...(step.onNonZero !== undefined && step.onNonZero !== 'fail'
|
|
634
|
+
? { on_non_zero: step.onNonZero } : {}),
|
|
613
635
|
};
|
|
614
636
|
case 'llm': {
|
|
615
637
|
return {
|
package/src/direct-input.ts
CHANGED
|
@@ -39,7 +39,7 @@ export function parseDirectInput(argument: string | undefined): unknown {
|
|
|
39
39
|
}
|
|
40
40
|
} catch (error) {
|
|
41
41
|
if (error instanceof DirectInputError) throw error;
|
|
42
|
-
if ((error
|
|
42
|
+
if (!namesNoFile(error)) {
|
|
43
43
|
throw new DirectInputError('input_unreadable', `Input file "${argument}" could not be inspected.`);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -56,6 +56,27 @@ export function parseDirectInput(argument: string | undefined): unknown {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Whether the `stat` failed because the argument names no file at all, rather
|
|
61
|
+
* than because a file exists and could not be inspected.
|
|
62
|
+
*
|
|
63
|
+
* `ENOENT` is the ordinary "nothing there". `ENAMETOOLONG` is the same answer
|
|
64
|
+
* for a longer argument: no path component may exceed the filesystem's limit
|
|
65
|
+
* (255 bytes on ext4 and on APFS), so an inline JSON object of a few hundred
|
|
66
|
+
* bytes cannot be a filename on any filesystem this runs on. Calling that
|
|
67
|
+
* `input_unreadable` refused every inline input longer than a filename — the
|
|
68
|
+
* recorded-input recovery commands `cli/local-agent-remedy.ts` prints for a
|
|
69
|
+
* parked run among them, which is how it was found.
|
|
70
|
+
*
|
|
71
|
+
* Anything else — a permission error, an I/O error — still refuses, because it
|
|
72
|
+
* means a path is there and this process could not look at it. Guessing that
|
|
73
|
+
* such an argument was inline JSON would parse a filename as a document.
|
|
74
|
+
*/
|
|
75
|
+
function namesNoFile(error: unknown): boolean {
|
|
76
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
77
|
+
return code === 'ENOENT' || code === 'ENAMETOOLONG';
|
|
78
|
+
}
|
|
79
|
+
|
|
59
80
|
function tooLarge(argument: string, fromFile: boolean): DirectInputError {
|
|
60
81
|
const sourceKind = fromFile ? `Input file "${argument}"` : 'Inline input';
|
|
61
82
|
return new DirectInputError(
|