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