@relayflows/sdk 2.0.30 → 2.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +54 -47
- 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-preflight.d.ts +4 -0
- package/dist/authored-preflight.d.ts.map +1 -0
- package/dist/authored-preflight.js +50 -0
- package/dist/authored-preflight.js.map +1 -0
- 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 +51 -13
- package/dist/authored-worker-step.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 +28 -3
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +27 -140
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/cli-probe.d.ts +20 -0
- package/dist/cli/cli-probe.d.ts.map +1 -0
- package/dist/cli/cli-probe.js +166 -0
- package/dist/cli/cli-probe.js.map +1 -0
- 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-mirror-session.d.ts +97 -0
- package/dist/cli/cloud-mirror-session.d.ts.map +1 -0
- package/dist/cli/cloud-mirror-session.js +264 -0
- package/dist/cli/cloud-mirror-session.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 +12 -0
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +3 -0
- package/dist/cli-commands.js.map +1 -1
- package/dist/cli.d.ts +17 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +134 -57
- package/dist/cli.js.map +1 -1
- package/dist/cloud-deploy.d.ts +1 -1
- package/dist/cloud-deploy.d.ts.map +1 -1
- package/dist/cloud-deploy.js +24 -8
- package/dist/cloud-deploy.js.map +1 -1
- package/dist/cloud-mirror-ledger.d.ts +40 -0
- package/dist/cloud-mirror-ledger.d.ts.map +1 -0
- package/dist/cloud-mirror-ledger.js +142 -0
- package/dist/cloud-mirror-ledger.js.map +1 -0
- package/dist/cloud-mirror-step.d.ts +168 -0
- package/dist/cloud-mirror-step.d.ts.map +1 -0
- package/dist/cloud-mirror-step.js +504 -0
- package/dist/cloud-mirror-step.js.map +1 -0
- package/dist/cloud-mirror-transport.d.ts +94 -0
- package/dist/cloud-mirror-transport.d.ts.map +1 -0
- package/dist/cloud-mirror-transport.js +163 -0
- package/dist/cloud-mirror-transport.js.map +1 -0
- package/dist/cloud-mirror.d.ts +132 -0
- package/dist/cloud-mirror.d.ts.map +1 -0
- package/dist/cloud-mirror.js +556 -0
- package/dist/cloud-mirror.js.map +1 -0
- 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-requirements.d.ts.map +1 -1
- package/dist/flow-requirements.js +24 -9
- package/dist/flow-requirements.js.map +1 -1
- 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-extension-runtime.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- 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/llm-worker.js +1 -1
- 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/package-version.d.ts +10 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +22 -0
- package/dist/package-version.js.map +1 -0
- package/dist/preflight.d.ts +14 -0
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +25 -3
- 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/relay-cli.d.ts.map +1 -1
- package/dist/relay-cli.js +1 -21
- package/dist/relay-cli.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/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 +91 -58
- 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-preflight.ts +46 -0
- 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 +63 -14
- package/src/cli/check-worker-surface.ts +66 -0
- package/src/cli/check.ts +59 -166
- package/src/cli/cli-probe.ts +194 -0
- package/src/cli/cloud-format.ts +82 -0
- package/src/cli/cloud-live.ts +366 -0
- package/src/cli/cloud-mirror-session.ts +329 -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 +3 -0
- package/src/cli.ts +155 -60
- package/src/cloud-deploy.ts +26 -8
- package/src/cloud-mirror-ledger.ts +161 -0
- package/src/cloud-mirror-step.ts +618 -0
- package/src/cloud-mirror-transport.ts +261 -0
- package/src/cloud-mirror.ts +633 -0
- 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-requirements.ts +24 -9
- package/src/gate-contract.ts +13 -1
- package/src/helper-preflight.ts +10 -3
- package/src/helper-reference.ts +130 -0
- package/src/hosted-extension-runtime.ts +1 -1
- package/src/index.ts +2 -0
- package/src/journal-client.ts +19 -2
- package/src/journal-projection.ts +106 -0
- package/src/llm-worker.ts +1 -1
- 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/package-version.ts +23 -0
- package/src/preflight.ts +38 -7
- package/src/protocol.ts +2 -0
- package/src/redact.ts +111 -0
- package/src/relay-cli.ts +1 -24
- package/src/run-projection.ts +247 -0
- 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
|
@@ -13,6 +13,13 @@ import { AuthoredBudget } from './authored-budget.js';
|
|
|
13
13
|
import { assertMemoryReachable, authoredMemory, scriptMemoryScope } from './authored-memory.js';
|
|
14
14
|
import { authoredDeterministicRunner, authoredWorkerRunner } from './authored-worker-step.js';
|
|
15
15
|
import { isSurfaceFlowCompletionReason, isSurfaceRunCompletionReason } from './authored-step-output.js';
|
|
16
|
+
import {
|
|
17
|
+
completionMarker,
|
|
18
|
+
isLoweredCompletion,
|
|
19
|
+
normalizeCompletionDetail,
|
|
20
|
+
type LoweredCompletionReason,
|
|
21
|
+
} from './authored-completion.js';
|
|
22
|
+
import { commitAuthoredVerdict } from './authored-completion-record.js';
|
|
16
23
|
import {
|
|
17
24
|
type AgentResult,
|
|
18
25
|
type LlmOptions,
|
|
@@ -21,6 +28,8 @@ import {
|
|
|
21
28
|
type Ctx,
|
|
22
29
|
type FlowCompletionReason,
|
|
23
30
|
type RunCompletionReason as SurfaceRunCompletionReason,
|
|
31
|
+
type RunOptions,
|
|
32
|
+
type RunResult,
|
|
24
33
|
type Step,
|
|
25
34
|
} from '@relayflows/surface';
|
|
26
35
|
import { createHelpers, helperProviders, type HelperCall, type FlowHandle } from '@relayflows/surface/runtime';
|
|
@@ -96,6 +105,12 @@ export interface AuthoredFlowExecutionResult {
|
|
|
96
105
|
readonly rootRunId?: string;
|
|
97
106
|
readonly name: string;
|
|
98
107
|
readonly completionReason: LoweredCompletionReason;
|
|
108
|
+
/**
|
|
109
|
+
* Why, when the body said. Normalized at `done()` — redacted, trimmed and
|
|
110
|
+
* bounded — and absent for every one-argument call, so the durable output,
|
|
111
|
+
* the IPC frame and the report keep the shapes they had.
|
|
112
|
+
*/
|
|
113
|
+
readonly completionDetail?: string;
|
|
99
114
|
readonly journalSteps: readonly AuthoredFlowJournalStep[];
|
|
100
115
|
}
|
|
101
116
|
|
|
@@ -243,6 +258,7 @@ export async function executeAuthoredFlow<Input = undefined>(
|
|
|
243
258
|
const stepEdges = (step: string): AuthoredStepEdges | undefined => lifecycle.stepEdges(step);
|
|
244
259
|
let nextStep = 1;
|
|
245
260
|
let requestedCompletion: LoweredCompletionReason | undefined;
|
|
261
|
+
let requestedDetail: string | undefined;
|
|
246
262
|
|
|
247
263
|
const lowerDeterministic = authoredDeterministicRunner(
|
|
248
264
|
definition.name, journal, journalSteps, budget, {
|
|
@@ -382,6 +398,56 @@ export async function executeAuthoredFlow<Input = undefined>(
|
|
|
382
398
|
return trackStep(authoredSteps, llmOp);
|
|
383
399
|
}
|
|
384
400
|
|
|
401
|
+
/**
|
|
402
|
+
* `f.run` (docs/SURFACE.md §1). Declared as overloads like `llmOperation`,
|
|
403
|
+
* because the exit-code policy decides the RESULT TYPE: the default keeps
|
|
404
|
+
* the stdout string every body is written against, and `'record'` widens it
|
|
405
|
+
* to the `RunResult` whose `ok` is the journaled exit code.
|
|
406
|
+
*/
|
|
407
|
+
function runOperation(command: string, options?: RunOptions & { onNonZero?: 'fail' }): Step<string>;
|
|
408
|
+
function runOperation(command: string, options: RunOptions & { onNonZero: 'record' }): Step<RunResult>;
|
|
409
|
+
function runOperation(command: string, options: RunOptions): Step<string | RunResult>;
|
|
410
|
+
function runOperation(command: string, runOptions?: RunOptions): Step<string> | Step<RunResult> {
|
|
411
|
+
assertOperationAllowed('run', definition.name, requestedCompletion);
|
|
412
|
+
const leaseMs = runOptions?.timeout === undefined ? undefined : parseStepTimeout(runOptions.timeout);
|
|
413
|
+
// Refused before an ordinal is consumed or anything is journaled. A
|
|
414
|
+
// misspelled policy must not quietly fall back to the fatal default and
|
|
415
|
+
// take away the very branch the author wrote below this line.
|
|
416
|
+
const policy = runOptions?.onNonZero;
|
|
417
|
+
if (policy !== undefined && policy !== 'fail' && policy !== 'record') {
|
|
418
|
+
throw new AuthoredFlowExecutionError(
|
|
419
|
+
'unsupported_verb',
|
|
420
|
+
`f.run options.onNonZero must be 'fail' or 'record' (got ${JSON.stringify(policy)}).`,
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
const id = `run-${nextStep++}`;
|
|
424
|
+
if (policy === 'record') {
|
|
425
|
+
let recordOp!: AuthoredFlowOperation<RunResult>;
|
|
426
|
+
recordOp = new AuthoredFlowOperation<RunResult>(
|
|
427
|
+
id, 'run',
|
|
428
|
+
() => assertOperationAllowed('run', definition.name, requestedCompletion),
|
|
429
|
+
() => observeStep(id, 'deterministic',
|
|
430
|
+
() => lowerDeterministic.recording(id, command, leaseMs, recordOp.namedGate), options.onProgress),
|
|
431
|
+
lifecycle,
|
|
432
|
+
// No label: a command is not a display name. It carries literal
|
|
433
|
+
// tokens and URLs, and no prefix of it is safe to show (displayLabel).
|
|
434
|
+
{},
|
|
435
|
+
);
|
|
436
|
+
return trackStep(authoredSteps, recordOp);
|
|
437
|
+
}
|
|
438
|
+
let runOp!: AuthoredFlowOperation<string>;
|
|
439
|
+
runOp = new AuthoredFlowOperation<string>(
|
|
440
|
+
id, 'run',
|
|
441
|
+
() => assertOperationAllowed('run', definition.name, requestedCompletion),
|
|
442
|
+
() => observeStep(id, 'deterministic', () => lowerDeterministic(id, command, false, leaseMs, runOp.namedGate), options.onProgress),
|
|
443
|
+
lifecycle,
|
|
444
|
+
// No label: a command is not a display name. It carries literal
|
|
445
|
+
// tokens and URLs, and no prefix of it is safe to show (displayLabel).
|
|
446
|
+
{},
|
|
447
|
+
);
|
|
448
|
+
return trackStep(authoredSteps, runOp);
|
|
449
|
+
}
|
|
450
|
+
|
|
385
451
|
const slackRun = options.rootRunId ?? randomUUID();
|
|
386
452
|
function slackOperation<T>(call: SlackCall): Step<T> {
|
|
387
453
|
assertOperationAllowed(`slack.${call.verb}`, definition.name, requestedCompletion);
|
|
@@ -444,23 +510,7 @@ export async function executeAuthoredFlow<Input = undefined>(
|
|
|
444
510
|
() => assertOperationAllowed('memory', definition.name, requestedCompletion),
|
|
445
511
|
definition.header.memory?.script !== false,
|
|
446
512
|
),
|
|
447
|
-
run
|
|
448
|
-
assertOperationAllowed('run', definition.name, requestedCompletion);
|
|
449
|
-
const leaseMs = runOptions?.timeout === undefined ? undefined : parseStepTimeout(runOptions.timeout);
|
|
450
|
-
const id = `run-${nextStep++}`;
|
|
451
|
-
let runOp!: AuthoredFlowOperation<string>;
|
|
452
|
-
runOp = new AuthoredFlowOperation<string>(
|
|
453
|
-
id,
|
|
454
|
-
'run',
|
|
455
|
-
() => assertOperationAllowed('run', definition.name, requestedCompletion),
|
|
456
|
-
() => observeStep(id, 'deterministic', () => lowerDeterministic(id, command, false, leaseMs, runOp.namedGate), options.onProgress),
|
|
457
|
-
lifecycle,
|
|
458
|
-
// No label: a command is not a display name. It carries literal
|
|
459
|
-
// tokens and URLs, and no prefix of it is safe to show (displayLabel).
|
|
460
|
-
{},
|
|
461
|
-
);
|
|
462
|
-
return trackStep(authoredSteps, runOp);
|
|
463
|
-
},
|
|
513
|
+
run: runOperation,
|
|
464
514
|
llm: llmOperation,
|
|
465
515
|
agent(name, options) {
|
|
466
516
|
assertOperationAllowed('agent', definition.name, requestedCompletion);
|
|
@@ -562,7 +612,7 @@ export async function executeAuthoredFlow<Input = undefined>(
|
|
|
562
612
|
displayLabel(typeof name === 'string' ? name : undefined),
|
|
563
613
|
));
|
|
564
614
|
},
|
|
565
|
-
done(reason) {
|
|
615
|
+
done(reason, doneOptions) {
|
|
566
616
|
if (!isSurfaceFlowCompletionReason(reason)) {
|
|
567
617
|
throw new AuthoredFlowExecutionError(
|
|
568
618
|
'unsupported_completion',
|
|
@@ -595,8 +645,13 @@ export async function executeAuthoredFlow<Input = undefined>(
|
|
|
595
645
|
reason,
|
|
596
646
|
);
|
|
597
647
|
}
|
|
648
|
+
// Validated before the completion is marked, so a malformed `detail`
|
|
649
|
+
// leaves the flow exactly as any other refused `done()` does: not
|
|
650
|
+
// completed, and still able to report the real refusal.
|
|
651
|
+
const detail = normalizeCompletionDetail(doneOptions);
|
|
598
652
|
lifecycle.markCompletion();
|
|
599
653
|
requestedCompletion = reason;
|
|
654
|
+
requestedDetail = detail;
|
|
600
655
|
},
|
|
601
656
|
cloud: unsupportedCloud(
|
|
602
657
|
() => assertOperationAllowed('cloud', definition.name, requestedCompletion),
|
|
@@ -670,12 +725,27 @@ export async function executeAuthoredFlow<Input = undefined>(
|
|
|
670
725
|
// not as a fabricated kernel run.completed reason. The marker step reports
|
|
671
726
|
// what the body decided; it is not itself a step that failed. The CLI turns
|
|
672
727
|
// the verdict into the exit code (success/declined 0, needs_human 3, step_failed 1).
|
|
673
|
-
|
|
674
|
-
|
|
728
|
+
//
|
|
729
|
+
// The verdict is committed to the root BEFORE the marker run is opened, and
|
|
730
|
+
// a resumed body reuses what was committed rather than recomputing it
|
|
731
|
+
// (authored-completion-record.ts). A detail is redacted against
|
|
732
|
+
// `process.env`, so a credential rotated while this process was down would
|
|
733
|
+
// otherwise re-normalize the same authored sentence differently, retry the
|
|
734
|
+
// marker's stable admission key with a drifted spec, and lose the
|
|
735
|
+
// explanation the root had already journaled to `run_admission_conflict`.
|
|
736
|
+
// With no detail there is no environment in the command and nothing to
|
|
737
|
+
// commit, so that path is untouched.
|
|
738
|
+
const terminalId = `complete-${nextStep}`;
|
|
739
|
+
const verdict = await commitAuthoredVerdict(journal, options.rootRunId, terminalId, {
|
|
740
|
+
reason: requestedCompletion,
|
|
741
|
+
...(requestedDetail === undefined ? {} : { detail: requestedDetail }),
|
|
742
|
+
});
|
|
743
|
+
await lowerDeterministic(terminalId, completionMarker(verdict.reason, verdict.detail), true);
|
|
675
744
|
return Object.freeze({
|
|
676
745
|
...(options.rootRunId === undefined ? {} : { rootRunId: options.rootRunId }),
|
|
677
746
|
name: definition.name,
|
|
678
|
-
completionReason:
|
|
747
|
+
completionReason: verdict.reason,
|
|
748
|
+
...(verdict.detail === undefined ? {} : { completionDetail: verdict.detail }),
|
|
679
749
|
journalSteps: Object.freeze([...journalSteps]),
|
|
680
750
|
});
|
|
681
751
|
}
|
|
@@ -695,43 +765,6 @@ function unsupportedVerb(verb: string): AuthoredFlowExecutionError {
|
|
|
695
765
|
);
|
|
696
766
|
}
|
|
697
767
|
|
|
698
|
-
/**
|
|
699
|
-
* The completion reasons an authored body may declare and this executor lowers.
|
|
700
|
-
*
|
|
701
|
-
* `FlowCompletionReason` is wider than this on purpose — it is the journal's
|
|
702
|
-
* run vocabulary plus authored verdicts — but the two sets drifting silently is
|
|
703
|
-
* exactly what made a type-valid `done("step_failed")` die at runtime as
|
|
704
|
-
* `unsupported_completion`. Every gate that asks "is this a completion this
|
|
705
|
-
* runtime can lower?" now asks this one function, so a reason cannot be
|
|
706
|
-
* accepted in one place and rejected in another.
|
|
707
|
-
*
|
|
708
|
-
* These three are internal cross-module helpers for the authored seam (the
|
|
709
|
-
* executor, the durable root, the IPC verifier and the CLI report), NOT public
|
|
710
|
-
* SDK surface. `src/index.ts` deliberately re-exports nothing from this module
|
|
711
|
-
* — keep it that way, or the whole authored seam leaks with them.
|
|
712
|
-
*/
|
|
713
|
-
export const LOWERED_COMPLETIONS = ['success', 'needs_human', 'step_failed', 'declined'] as const;
|
|
714
|
-
export type LoweredCompletionReason = (typeof LOWERED_COMPLETIONS)[number];
|
|
715
|
-
|
|
716
|
-
export function isLoweredCompletion(value: unknown): value is LoweredCompletionReason {
|
|
717
|
-
return typeof value === 'string' && (LOWERED_COMPLETIONS as readonly string[]).includes(value);
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
/**
|
|
721
|
-
* The deterministic command that carries an authored verdict into the journal.
|
|
722
|
-
*
|
|
723
|
-
* `success` lowers to `:` because success needs no marker: the marker run's own
|
|
724
|
-
* kernel `success` already IS that record. Every other lowered verdict is
|
|
725
|
-
* something the kernel's completion vocabulary cannot express on a step that
|
|
726
|
-
* *succeeded*, so it travels as data on stdout and is read back from
|
|
727
|
-
* `step.completed`. It is deliberately not lowered as a failing command: no
|
|
728
|
-
* step failed here, and a fabricated failure would put bogus evidence in the
|
|
729
|
-
* journal for a flow whose steps all ran correctly.
|
|
730
|
-
*/
|
|
731
|
-
export function completionMarker(reason: LoweredCompletionReason): string {
|
|
732
|
-
return reason === 'success' ? ':' : `printf '%s' '{"completionReason":"${reason}"}'`;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
768
|
function assertOperationAllowed(
|
|
736
769
|
verb: string,
|
|
737
770
|
flowName: string,
|
package/src/authored-human.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AuthoredFlowExecutionError, type AuthoredHumanWait } from './authored-flow-error.js';
|
|
2
2
|
import type { JournalClient } from './journal-client.js';
|
|
3
|
+
import { shellWord } from './shell-word.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* The answer contract for a parked `f.human`.
|
|
@@ -144,7 +145,3 @@ export function resumeCommand(runId: string, dataDir?: string, localAgent = fals
|
|
|
144
145
|
const dir = dataDir === undefined ? '' : ` --data-dir ${shellWord(dataDir)}`;
|
|
145
146
|
return `flows resume${dir}${localAgent ? ' --local-agent' : ''} ${runId}`;
|
|
146
147
|
}
|
|
147
|
-
|
|
148
|
-
function shellWord(value: string): string {
|
|
149
|
-
return /^[A-Za-z0-9_./=:@%+,-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
150
|
-
}
|
|
@@ -88,9 +88,12 @@ try {
|
|
|
88
88
|
// `details` is the failing child step's journal evidence. Without it the
|
|
89
89
|
// parent can only re-render the message, and the machine-readable
|
|
90
90
|
// diagnostic loses the command's exit code and output tails.
|
|
91
|
+
// `parkCause` travels for the same reason: it is the difference between
|
|
92
|
+
// "attach a worker" and "a human has to recover this", and this process is
|
|
93
|
+
// the only one that saw the child's classification.
|
|
91
94
|
...(error instanceof AuthoredFlowExecutionError ? { code: error.code,
|
|
92
95
|
completionReason: error.completionReason, runId: error.runId,
|
|
93
|
-
details: error.details } : {}),
|
|
96
|
+
details: error.details, parkCause: error.parkCause } : {}),
|
|
94
97
|
...(error instanceof AuthoredHumanParked ? { wait: error.wait } : {}) });
|
|
95
98
|
process.exitCode = 1;
|
|
96
99
|
} finally {
|
|
@@ -8,12 +8,14 @@ import { isAbsolute, join } from 'node:path';
|
|
|
8
8
|
import type { Readable } from 'node:stream';
|
|
9
9
|
import type { AuthoredRootMetadata } from './authored-root.js';
|
|
10
10
|
import type { AuthoredExecutionRuntime, AuthoredFlowExecutionResult, ExecuteAuthoredFlowOptions } from './authored-flow-executor.js';
|
|
11
|
-
import { completionMarker, isLoweredCompletion } from './authored-
|
|
11
|
+
import { completionMarker, isDurableCompletionDetail, isLoweredCompletion } from './authored-completion.js';
|
|
12
12
|
import {
|
|
13
13
|
AuthoredFlowExecutionError, AuthoredHumanParked,
|
|
14
14
|
type AuthoredFlowExecutionErrorCode, type AuthoredHumanWait,
|
|
15
15
|
} from './authored-flow-error.js';
|
|
16
|
-
import type {
|
|
16
|
+
import type {
|
|
17
|
+
AttemptEvidenceComparison, ParkCause, StepAttemptFailure, StepFailedDetails,
|
|
18
|
+
} from './failure-kinds.js';
|
|
17
19
|
import { HUMAN_WAIT_ID } from './authored-human.js';
|
|
18
20
|
import { assertAuthoredPromiseHooks } from './authored-runtime-capability.js';
|
|
19
21
|
|
|
@@ -137,13 +139,18 @@ export async function runAuthoredInNode(
|
|
|
137
139
|
else if (message.type === 'wait') options.onWait?.(message.event);
|
|
138
140
|
else if (message.type === 'result') result = { ...message.result, executionRuntime: runtime };
|
|
139
141
|
else if (message.type === 'error') {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
if (message.code === 'human_parked' && isHumanWaitFrame(message.wait) && message.runId === rootRunId) {
|
|
143
|
+
failure = new AuthoredHumanParked(message.wait, rootRunId);
|
|
144
|
+
} else if (typeof message.code === 'string') {
|
|
145
|
+
const authored = new AuthoredFlowExecutionError(message.code as AuthoredFlowExecutionErrorCode,
|
|
146
|
+
message.message, message.completionReason, message.runId,
|
|
147
|
+
stepFailedFrame(message.details));
|
|
148
|
+
// Validated, not trusted, like `details`: an unrecognised cause
|
|
149
|
+
// is dropped so the boundary says nothing about workers rather
|
|
150
|
+
// than acting on a value this frame could have invented.
|
|
151
|
+
authored.parkCause = parkCauseFrame(message.parkCause);
|
|
152
|
+
failure = authored;
|
|
153
|
+
} else failure = new Error(message.message);
|
|
147
154
|
} else throw new Error('unknown authored runtime message');
|
|
148
155
|
} catch (error) { stop(error instanceof Error ? error : new Error('invalid authored runtime message')); }
|
|
149
156
|
}
|
|
@@ -174,6 +181,17 @@ function isHumanWaitFrame(value: unknown): value is AuthoredHumanWait {
|
|
|
174
181
|
&& typeof wait.to === 'string' && wait.to !== '';
|
|
175
182
|
}
|
|
176
183
|
|
|
184
|
+
/**
|
|
185
|
+
* A park cause arriving over IPC, accepted only as one of the two values the
|
|
186
|
+
* type admits. Anything else — absent, misspelled, a different type — becomes
|
|
187
|
+
* `undefined`, which the remedy formatter reads as "unestablished" and answers
|
|
188
|
+
* with silence. Guessing `worker_unavailable` here would let a malformed frame
|
|
189
|
+
* put "attach a worker" on a park a worker cannot clear.
|
|
190
|
+
*/
|
|
191
|
+
export function parkCauseFrame(value: unknown): ParkCause | undefined {
|
|
192
|
+
return value === 'worker_unavailable' || value === 'needs_human' ? value : undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
177
195
|
/**
|
|
178
196
|
* Step evidence arriving over IPC, reduced to the fields `StepFailedDetails`
|
|
179
197
|
* declares and the types it declares them as. The child is the same pinned
|
|
@@ -184,26 +202,76 @@ function isHumanWaitFrame(value: unknown): value is AuthoredHumanWait {
|
|
|
184
202
|
* evidence frame would replace the answer with a worse one.
|
|
185
203
|
*/
|
|
186
204
|
export function stepFailedFrame(value: unknown): StepFailedDetails | undefined {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const text = (key: string): string | undefined =>
|
|
190
|
-
typeof frame[key] === 'string' ? (frame[key] as string).slice(0, 8192) : undefined;
|
|
191
|
-
const count = (key: string): number | undefined =>
|
|
192
|
-
typeof frame[key] === 'number' && Number.isSafeInteger(frame[key]) ? frame[key] as number : undefined;
|
|
205
|
+
const frame = frameRecord(value);
|
|
206
|
+
if (frame === undefined) return undefined;
|
|
193
207
|
const details: StepFailedDetails = {};
|
|
194
208
|
for (const [key, parsed] of [
|
|
195
|
-
['stepId',
|
|
196
|
-
['completionReason',
|
|
197
|
-
['maxIterations',
|
|
198
|
-
['stdoutTail',
|
|
199
|
-
['detail',
|
|
200
|
-
['
|
|
209
|
+
['stepId', frameText(frame, 'stepId')], ['stepType', frameText(frame, 'stepType')],
|
|
210
|
+
['completionReason', frameText(frame, 'completionReason')], ['attempt', frameCount(frame, 'attempt')],
|
|
211
|
+
['maxIterations', frameCount(frame, 'maxIterations')], ['exitCode', frameCount(frame, 'exitCode')],
|
|
212
|
+
['stdoutTail', frameText(frame, 'stdoutTail')], ['stderrTail', frameText(frame, 'stderrTail')],
|
|
213
|
+
['detail', frameText(frame, 'detail')], ['transcriptPath', frameText(frame, 'transcriptPath')],
|
|
214
|
+
['attempts', attemptFrames(frame['attempts'])],
|
|
215
|
+
['attemptEvidence', comparisonFrame(frame['attemptEvidence'])],
|
|
216
|
+
['hint', frameText(frame, 'hint')], ['journalPath', frameText(frame, 'journalPath')],
|
|
201
217
|
] as const) {
|
|
202
218
|
if (parsed !== undefined) (details as Record<string, unknown>)[key] = parsed;
|
|
203
219
|
}
|
|
204
220
|
return Object.keys(details).length === 0 ? undefined : details;
|
|
205
221
|
}
|
|
206
222
|
|
|
223
|
+
/**
|
|
224
|
+
* The attempt history, reduced element by element on the same terms as the
|
|
225
|
+
* frame that carries it. An element that is not an object contributes nothing
|
|
226
|
+
* rather than voiding the whole history: a report naming three of four
|
|
227
|
+
* attempts is still the first attempt's error, which is the fact the terminal
|
|
228
|
+
* scalars cannot supply.
|
|
229
|
+
*/
|
|
230
|
+
function attemptFrames(value: unknown): StepAttemptFailure[] | undefined {
|
|
231
|
+
if (!Array.isArray(value)) return undefined;
|
|
232
|
+
const attempts: StepAttemptFailure[] = [];
|
|
233
|
+
for (const element of value) {
|
|
234
|
+
const source = frameRecord(element);
|
|
235
|
+
if (source === undefined) continue;
|
|
236
|
+
const attempt: StepAttemptFailure = {};
|
|
237
|
+
for (const [key, parsed] of [
|
|
238
|
+
['attempt', frameCount(source, 'attempt')],
|
|
239
|
+
['completionReason', frameText(source, 'completionReason')],
|
|
240
|
+
['disposition', frameText(source, 'disposition')],
|
|
241
|
+
['exitCode', frameCount(source, 'exitCode')],
|
|
242
|
+
['stdoutTail', frameText(source, 'stdoutTail')],
|
|
243
|
+
['stderrTail', frameText(source, 'stderrTail')],
|
|
244
|
+
['detail', frameText(source, 'detail')],
|
|
245
|
+
// Only a literal `true` claims truncation; anything else leaves the
|
|
246
|
+
// excerpt unlabelled rather than labelling a complete one as cut.
|
|
247
|
+
['truncated', source['truncated'] === true ? true : undefined],
|
|
248
|
+
] as const) {
|
|
249
|
+
if (parsed !== undefined) (attempt as Record<string, unknown>)[key] = parsed;
|
|
250
|
+
}
|
|
251
|
+
if (Object.keys(attempt).length > 0) attempts.push(attempt);
|
|
252
|
+
}
|
|
253
|
+
return attempts.length === 0 ? undefined : attempts;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** An unrecognised verdict is dropped, and rendering then says `unknown`. */
|
|
257
|
+
function comparisonFrame(value: unknown): AttemptEvidenceComparison | undefined {
|
|
258
|
+
return value === 'differs' || value === 'unchanged' || value === 'unknown' ? value : undefined;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function frameRecord(value: unknown): Record<string, unknown> | undefined {
|
|
262
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
263
|
+
? value as Record<string, unknown> : undefined;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function frameText(frame: Record<string, unknown>, key: string): string | undefined {
|
|
267
|
+
return typeof frame[key] === 'string' ? (frame[key] as string).slice(0, 8192) : undefined;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function frameCount(frame: Record<string, unknown>, key: string): number | undefined {
|
|
271
|
+
return typeof frame[key] === 'number' && Number.isSafeInteger(frame[key])
|
|
272
|
+
? frame[key] as number : undefined;
|
|
273
|
+
}
|
|
274
|
+
|
|
207
275
|
/** The IPC frame is a claim, not a durable terminal fact or a sandbox boundary. */
|
|
208
276
|
export async function verifyAuthoredNodeResult(
|
|
209
277
|
result: AuthoredFlowExecutionResult, metadata: AuthoredRootMetadata,
|
|
@@ -212,6 +280,9 @@ export async function verifyAuthoredNodeResult(
|
|
|
212
280
|
const invalid = (why = ''): never => { throw new Error(`authored runtime result has no matching durable completion${process.env['FLOWS_VERIFIER_DEBUG'] && why ? ` (${why})` : ''}`); };
|
|
213
281
|
if (result.rootRunId !== rootRunId || result.name !== metadata.flowName
|
|
214
282
|
|| !isLoweredCompletion(result.completionReason)
|
|
283
|
+
// Type- and bound-check the claimed detail here, so the marker comparison
|
|
284
|
+
// below compares two values this process would itself have produced.
|
|
285
|
+
|| (result.completionDetail !== undefined && !isDurableCompletionDetail(result.completionDetail))
|
|
215
286
|
|| !Array.isArray(result.journalSteps) || result.journalSteps.length === 0) invalid('frame');
|
|
216
287
|
const terminal = result.journalSteps.at(-1)!;
|
|
217
288
|
if (!terminal || !/^complete-[1-9][0-9]*$/.test(terminal.id)) invalid('terminal');
|
|
@@ -288,8 +359,11 @@ export async function verifyAuthoredNodeResult(
|
|
|
288
359
|
// rejected a frame whose reason is not lowerable at all — that one is
|
|
289
360
|
// the runtime validation of untrusted IPC, and it is why nothing has
|
|
290
361
|
// to be re-asserted here just to satisfy the type.
|
|
362
|
+
// The detail is inside the marker, so this one comparison also
|
|
363
|
+
// attests it: a frame cannot claim a detail the journal does not
|
|
364
|
+
// hold, drop one it does, or alter a character of it.
|
|
291
365
|
if (step?.type !== 'deterministic'
|
|
292
|
-
|| step.command !== completionMarker(result.completionReason)) invalid('marker');
|
|
366
|
+
|| step.command !== completionMarker(result.completionReason, result.completionDetail)) invalid('marker');
|
|
293
367
|
}
|
|
294
368
|
}
|
|
295
369
|
} finally { journal.close(); }
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
2
|
+
import { checkAuthoredFlow, readProjectConfig, type ProjectConfig } from './cli/check.js';
|
|
3
|
+
import { probeCliAsync } from './cli/cli-probe.js';
|
|
4
|
+
import { communicationInstruction } from './communication/spec.js';
|
|
5
|
+
import { cliProbeKey, CliProbeError, resolvePreflight, type CliProbeOutcome } from './preflight.js';
|
|
6
|
+
import type { FlowSpec } from './spec.js';
|
|
7
|
+
|
|
8
|
+
/** Auth/model facts belong to one execution, including concurrent cold callers. */
|
|
9
|
+
export function authoredPreflight(path: string) {
|
|
10
|
+
const probeCache = new Map<string, CliProbeOutcome>();
|
|
11
|
+
const pending = new Map<string, Promise<void>>();
|
|
12
|
+
const directory = dirname(resolve(path));
|
|
13
|
+
return async (flow: FlowSpec) => {
|
|
14
|
+
let config: ProjectConfig;
|
|
15
|
+
try { config = readProjectConfig(directory); }
|
|
16
|
+
catch { return checkAuthoredFlow(flow, path); }
|
|
17
|
+
const resolved = resolvePreflight(flow, {
|
|
18
|
+
projectCli: config.cli, projectConfigPath: config.path, projectSearchStart: directory,
|
|
19
|
+
models: config.models, modelRegistryPath: config.modelRegistryPath,
|
|
20
|
+
});
|
|
21
|
+
if (!resolved.ok) return checkAuthoredFlow(flow, path, config);
|
|
22
|
+
await Promise.all(resolved.resolutions.map(async resolution => {
|
|
23
|
+
const step = resolved.compiled!.steps.find(step => step.id === resolution.stepId)!;
|
|
24
|
+
const managed = step.type === 'agent' && communicationInstruction(step.instruction) !== undefined;
|
|
25
|
+
const key = cliProbeKey(resolution, managed);
|
|
26
|
+
if (probeCache.has(key)) return;
|
|
27
|
+
let probing = pending.get(key);
|
|
28
|
+
if (probing === undefined) {
|
|
29
|
+
probing = (async () => {
|
|
30
|
+
try {
|
|
31
|
+
probeCache.set(key, { result: await probeCliAsync(resolution.cli,
|
|
32
|
+
resolution.source === 'project' ? config.directory : directory,
|
|
33
|
+
resolution.model, managed ? 'managed' : undefined) });
|
|
34
|
+
} catch (error) {
|
|
35
|
+
probeCache.set(key, { failure: error instanceof CliProbeError ? error.detail : null });
|
|
36
|
+
}
|
|
37
|
+
})();
|
|
38
|
+
pending.set(key, probing);
|
|
39
|
+
}
|
|
40
|
+
await probing;
|
|
41
|
+
pending.delete(key);
|
|
42
|
+
}));
|
|
43
|
+
const result = checkAuthoredFlow(flow, path, config, {}, probeCache);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
}
|
package/src/authored-root.ts
CHANGED
|
@@ -4,7 +4,9 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
4
4
|
import { readFile } from 'node:fs/promises';
|
|
5
5
|
import { canonicalize } from './canonical.js';
|
|
6
6
|
import { compileSpec, toKernelSpec } from './compile.js';
|
|
7
|
-
import { executeAuthoredFlow,
|
|
7
|
+
import { executeAuthoredFlow, type AuthoredFlowExecutionResult } from './authored-flow-executor.js';
|
|
8
|
+
import { isDurableCompletionDetail, isLoweredCompletion } from './authored-completion.js';
|
|
9
|
+
import { AUTHORED_ROOT_KIND } from './authored-verdict.js';
|
|
8
10
|
import {
|
|
9
11
|
loadAuthoredFlow,
|
|
10
12
|
type LoadedAuthoredFlow,
|
|
@@ -19,7 +21,12 @@ import { AuthoredFlowExecutionError, AuthoredHumanParked } from './authored-flow
|
|
|
19
21
|
import { readOpenHumanWaits } from './authored-human.js';
|
|
20
22
|
import { isSurfaceCompletionReason } from './authored-step-output.js';
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Taken from the projection module rather than spelled twice: `flows status`
|
|
26
|
+
* has to recognise the same root this file writes, and it must not import the
|
|
27
|
+
* executor to do it.
|
|
28
|
+
*/
|
|
29
|
+
const ROOT_KIND = AUTHORED_ROOT_KIND;
|
|
23
30
|
|
|
24
31
|
export interface AuthoredRootExtension {
|
|
25
32
|
readonly name: string;
|
|
@@ -104,6 +111,7 @@ export async function executeDurableAuthoredFlow(
|
|
|
104
111
|
return await completedRootResult(journal, outcome.run_id);
|
|
105
112
|
}
|
|
106
113
|
assertRootCanDispatch(outcome);
|
|
114
|
+
options.lifecycle?.onRunStarted?.({ runId: outcome.run_id, flow: definition.name });
|
|
107
115
|
// `run.start` is an idempotent receipt. If the first caller died after
|
|
108
116
|
// the daemon dispatched this root, a same-daemon retry sees the existing
|
|
109
117
|
// active run but receives no second dispatch from start itself. Resume is
|
|
@@ -150,6 +158,7 @@ export async function resumeDurableAuthoredFlow(
|
|
|
150
158
|
}
|
|
151
159
|
assertRootCanDispatch(outcome);
|
|
152
160
|
await assertNoOpenHumanWait(journal, outcome);
|
|
161
|
+
options.lifecycle?.onRunStarted?.({ runId: rootRunId, flow: metadata.flowName, resumed: true });
|
|
153
162
|
const dispatch = await dispatchWait.promise;
|
|
154
163
|
return await driveRoot(loaded, metadata, journal, peer, dispatch, options);
|
|
155
164
|
} finally {
|
|
@@ -230,6 +239,7 @@ async function driveRoot(
|
|
|
230
239
|
dispatch.run_id, dispatch.step_id, dispatch.attempt,
|
|
231
240
|
dispatch.idempotency_key, 'success', {
|
|
232
241
|
output: { name: result.name, completionReason: result.completionReason,
|
|
242
|
+
...(result.completionDetail === undefined ? {} : { completionDetail: result.completionDetail }),
|
|
233
243
|
journalSteps: result.journalSteps,
|
|
234
244
|
...(result.executionRuntime === undefined ? {} : { executionRuntime: result.executionRuntime }) },
|
|
235
245
|
started_pins: dispatch.pins, end_pins: dispatch.pins,
|
|
@@ -372,9 +382,13 @@ async function completedRootResult(
|
|
|
372
382
|
if (!isCompletedRootOutput(output)) {
|
|
373
383
|
throw new Error('completed authored root has no durable result');
|
|
374
384
|
}
|
|
385
|
+
// The stored detail, never a freshly computed one: redaction reads the
|
|
386
|
+
// CURRENT environment, so recomputing here would let a completed run report
|
|
387
|
+
// something its journal does not hold.
|
|
375
388
|
return Object.freeze({
|
|
376
389
|
name: output.name,
|
|
377
390
|
completionReason: output.completionReason,
|
|
391
|
+
...(output.completionDetail === undefined ? {} : { completionDetail: output.completionDetail }),
|
|
378
392
|
journalSteps: Object.freeze(output.journalSteps.map(step => Object.freeze({ ...step }))),
|
|
379
393
|
rootRunId,
|
|
380
394
|
});
|
|
@@ -385,6 +399,10 @@ function isCompletedRootOutput(value: unknown): value is Omit<AuthoredFlowExecut
|
|
|
385
399
|
const output = value as Partial<AuthoredFlowExecutionResult>;
|
|
386
400
|
return typeof output.name === 'string'
|
|
387
401
|
&& isLoweredCompletion(output.completionReason)
|
|
402
|
+
// A malformed detail fails the whole readback rather than being dropped:
|
|
403
|
+
// silently discarding it would report the verdict without the evidence
|
|
404
|
+
// the journal says it was recorded with.
|
|
405
|
+
&& (output.completionDetail === undefined || isDurableCompletionDetail(output.completionDetail))
|
|
388
406
|
&& Array.isArray(output.journalSteps)
|
|
389
407
|
&& output.journalSteps.every(step => typeof step === 'object' && step !== null
|
|
390
408
|
&& typeof step.id === 'string' && typeof step.runId === 'string'
|