@relayflows/sdk 2.0.35 → 2.0.37
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/adapters/base.d.ts +16 -0
- package/dist/adapters/base.d.ts.map +1 -1
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +15 -0
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/codex.d.ts.map +1 -1
- package/dist/adapters/codex.js +4 -0
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/usage-limit.d.ts +2 -0
- package/dist/adapters/usage-limit.d.ts.map +1 -0
- package/dist/adapters/usage-limit.js +17 -0
- package/dist/adapters/usage-limit.js.map +1 -0
- package/dist/authored-activity.d.ts +17 -0
- package/dist/authored-activity.d.ts.map +1 -0
- package/dist/authored-activity.js +218 -0
- package/dist/authored-activity.js.map +1 -0
- package/dist/authored-child-run.d.ts +34 -0
- package/dist/authored-child-run.d.ts.map +1 -0
- package/dist/authored-child-run.js +128 -0
- package/dist/authored-child-run.js.map +1 -0
- package/dist/authored-flow-error.d.ts +32 -3
- package/dist/authored-flow-error.d.ts.map +1 -1
- package/dist/authored-flow-error.js +3 -1
- package/dist/authored-flow-error.js.map +1 -1
- package/dist/authored-flow-executor.d.ts +11 -1
- package/dist/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +32 -1
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/authored-helper-effect.d.ts.map +1 -1
- package/dist/authored-helper-effect.js +3 -2
- package/dist/authored-helper-effect.js.map +1 -1
- package/dist/authored-mcp.d.ts.map +1 -1
- package/dist/authored-mcp.js +4 -2
- package/dist/authored-mcp.js.map +1 -1
- package/dist/authored-node-entry.js +9 -1
- package/dist/authored-node-entry.js.map +1 -1
- package/dist/authored-node-runner.d.ts.map +1 -1
- package/dist/authored-node-runner.js +42 -9
- package/dist/authored-node-runner.js.map +1 -1
- package/dist/authored-plugin-effect.d.ts.map +1 -1
- package/dist/authored-plugin-effect.js +4 -2
- package/dist/authored-plugin-effect.js.map +1 -1
- package/dist/authored-preflight.d.ts +1 -1
- package/dist/authored-preflight.d.ts.map +1 -1
- package/dist/authored-preflight.js +2 -2
- package/dist/authored-preflight.js.map +1 -1
- package/dist/authored-root.d.ts +12 -7
- package/dist/authored-root.d.ts.map +1 -1
- package/dist/authored-root.js +24 -0
- package/dist/authored-root.js.map +1 -1
- package/dist/authored-step-output.d.ts +24 -8
- package/dist/authored-step-output.d.ts.map +1 -1
- package/dist/authored-step-output.js +81 -15
- package/dist/authored-step-output.js.map +1 -1
- package/dist/authored-subscription-park.d.ts +5 -0
- package/dist/authored-subscription-park.d.ts.map +1 -0
- package/dist/authored-subscription-park.js +47 -0
- package/dist/authored-subscription-park.js.map +1 -0
- package/dist/authored-verdict.d.ts +2 -0
- package/dist/authored-verdict.d.ts.map +1 -1
- package/dist/authored-verdict.js +2 -1
- package/dist/authored-verdict.js.map +1 -1
- package/dist/authored-worker-step.d.ts +1 -1
- package/dist/authored-worker-step.d.ts.map +1 -1
- package/dist/authored-worker-step.js +9 -4
- package/dist/authored-worker-step.js.map +1 -1
- package/dist/cli/check-activities.d.ts +8 -0
- package/dist/cli/check-activities.d.ts.map +1 -0
- package/dist/cli/check-activities.js +155 -0
- package/dist/cli/check-activities.js.map +1 -0
- package/dist/cli/check.d.ts +2 -0
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +3 -3
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/cli-probe.d.ts +2 -2
- package/dist/cli/cli-probe.d.ts.map +1 -1
- package/dist/cli/cli-probe.js +22 -9
- package/dist/cli/cli-probe.js.map +1 -1
- package/dist/cli/direct-run.d.ts.map +1 -1
- package/dist/cli/direct-run.js +17 -4
- package/dist/cli/direct-run.js.map +1 -1
- package/dist/cli/run.d.ts +18 -5
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +37 -12
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/subscription-report.d.ts +5 -0
- package/dist/cli/subscription-report.d.ts.map +1 -0
- package/dist/cli/subscription-report.js +48 -0
- package/dist/cli/subscription-report.js.map +1 -0
- package/dist/cli-adapter.d.ts +4 -2
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +4 -0
- package/dist/cli-adapter.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +13 -5
- package/dist/cli.js.map +1 -1
- package/dist/communication/local.d.ts +1 -1
- package/dist/communication/local.d.ts.map +1 -1
- package/dist/communication/local.js +2 -2
- 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 +4 -4
- package/dist/communication/worker.js.map +1 -1
- package/dist/decimal-dollars.d.ts +12 -0
- package/dist/decimal-dollars.d.ts.map +1 -0
- package/dist/decimal-dollars.js +39 -0
- package/dist/decimal-dollars.js.map +1 -0
- package/dist/effect-channel.d.ts.map +1 -1
- package/dist/effect-channel.js +2 -0
- package/dist/effect-channel.js.map +1 -1
- package/dist/failure-kinds.d.ts +2 -2
- package/dist/failure-kinds.d.ts.map +1 -1
- package/dist/failure-kinds.js +2 -0
- package/dist/failure-kinds.js.map +1 -1
- package/dist/helper-instruction.d.ts +3 -0
- package/dist/helper-instruction.d.ts.map +1 -0
- package/dist/helper-instruction.js +3 -0
- package/dist/helper-instruction.js.map +1 -0
- 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 +21 -3
- package/dist/journal-client.d.ts.map +1 -1
- package/dist/journal-client.js +45 -6
- package/dist/journal-client.js.map +1 -1
- package/dist/journal-reader.d.ts.map +1 -1
- package/dist/journal-reader.js +2 -0
- package/dist/journal-reader.js.map +1 -1
- package/dist/llm-worker.d.ts +2 -1
- package/dist/llm-worker.d.ts.map +1 -1
- package/dist/llm-worker.js +7 -2
- package/dist/llm-worker.js.map +1 -1
- package/dist/local-agent-environment.d.ts +13 -0
- package/dist/local-agent-environment.d.ts.map +1 -0
- package/dist/local-agent-environment.js +104 -0
- package/dist/local-agent-environment.js.map +1 -0
- package/dist/local-agent.d.ts +4 -1
- package/dist/local-agent.d.ts.map +1 -1
- package/dist/local-agent.js +15 -5
- package/dist/local-agent.js.map +1 -1
- package/dist/preflight.d.ts +6 -0
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +36 -10
- package/dist/preflight.js.map +1 -1
- package/dist/protocol.d.ts +187 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/reported-cost-total.d.ts +41 -0
- package/dist/reported-cost-total.d.ts.map +1 -0
- package/dist/reported-cost-total.js +63 -0
- package/dist/reported-cost-total.js.map +1 -0
- package/dist/reported-cost.d.ts +24 -0
- package/dist/reported-cost.d.ts.map +1 -0
- package/dist/reported-cost.js +49 -0
- package/dist/reported-cost.js.map +1 -0
- package/dist/run-state.d.ts +9 -11
- package/dist/run-state.d.ts.map +1 -1
- package/dist/run-state.js +15 -38
- package/dist/run-state.js.map +1 -1
- package/dist/step-settling.d.ts +11 -0
- package/dist/step-settling.d.ts.map +1 -0
- package/dist/step-settling.js +22 -0
- package/dist/step-settling.js.map +1 -0
- package/dist/worker-cli.d.ts +1 -1
- package/dist/worker-cli.d.ts.map +1 -1
- package/dist/worker-cli.js +3 -3
- package/dist/worker-cli.js.map +1 -1
- package/dist/worker.d.ts +2 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +5 -2
- package/dist/worker.js.map +1 -1
- package/dist/yaml-helper-effect.d.ts.map +1 -1
- package/dist/yaml-helper-effect.js +2 -1
- package/dist/yaml-helper-effect.js.map +1 -1
- package/dist/yaml-helpers.d.ts.map +1 -1
- package/dist/yaml-helpers.js +2 -1
- package/dist/yaml-helpers.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/base.ts +19 -0
- package/src/adapters/claude.ts +15 -0
- package/src/adapters/codex.ts +6 -0
- package/src/adapters/usage-limit.ts +17 -0
- package/src/authored-activity.ts +251 -0
- package/src/authored-child-run.ts +138 -0
- package/src/authored-flow-error.ts +33 -0
- package/src/authored-flow-executor.ts +44 -1
- package/src/authored-helper-effect.ts +3 -2
- package/src/authored-mcp.ts +4 -2
- package/src/authored-node-entry.ts +11 -2
- package/src/authored-node-runner.ts +43 -9
- package/src/authored-plugin-effect.ts +4 -2
- package/src/authored-preflight.ts +2 -2
- package/src/authored-root.ts +34 -4
- package/src/authored-step-output.ts +88 -15
- package/src/authored-subscription-park.ts +46 -0
- package/src/authored-verdict.ts +2 -1
- package/src/authored-worker-step.ts +9 -3
- package/src/cli/check-activities.ts +144 -0
- package/src/cli/check.ts +6 -3
- package/src/cli/cli-probe.ts +23 -9
- package/src/cli/direct-run.ts +16 -1
- package/src/cli/run.ts +59 -14
- package/src/cli/subscription-report.ts +51 -0
- package/src/cli-adapter.ts +8 -2
- package/src/cli.ts +13 -6
- package/src/communication/local.ts +3 -2
- package/src/communication/worker.ts +7 -4
- package/src/decimal-dollars.ts +38 -0
- package/src/effect-channel.ts +2 -0
- package/src/failure-kinds.ts +2 -0
- package/src/helper-instruction.ts +2 -0
- package/src/index.ts +6 -0
- package/src/journal-client.ts +60 -7
- package/src/journal-reader.ts +2 -0
- package/src/llm-worker.ts +6 -1
- package/src/local-agent-environment.ts +92 -0
- package/src/local-agent.ts +19 -4
- package/src/preflight.ts +46 -10
- package/src/protocol.ts +151 -0
- package/src/reported-cost-total.ts +82 -0
- package/src/reported-cost.ts +51 -0
- package/src/run-state.ts +25 -37
- package/src/step-settling.ts +19 -0
- package/src/worker-cli.ts +3 -2
- package/src/worker.ts +9 -2
- package/src/yaml-helper-effect.ts +2 -1
- package/src/yaml-helpers.ts +2 -1
|
@@ -43,6 +43,7 @@ import type { LoadedAuthoredFlowNode } from './authored-flow-loader.js';
|
|
|
43
43
|
import type { RunLifecycleOptions } from './cli/run.js';
|
|
44
44
|
import {
|
|
45
45
|
AuthoredFlowExecutionError,
|
|
46
|
+
type AuthoredFlowSuspension,
|
|
46
47
|
AuthoredHumanParked,
|
|
47
48
|
type AuthoredFlowExecutionErrorCode,
|
|
48
49
|
} from './authored-flow-error.js';
|
|
@@ -54,6 +55,7 @@ import {
|
|
|
54
55
|
verifyAuthoredOperations,
|
|
55
56
|
} from './authored-flow-operation.js';
|
|
56
57
|
import { AuthoredFlowLifecycle } from './authored-flow-lifecycle.js';
|
|
58
|
+
import { AuthoredActivities } from './authored-activity.js';
|
|
57
59
|
import { displayLabel, type AuthoredStepEdges } from './authored-step-index.js';
|
|
58
60
|
import { JournalClient } from './journal-client.js';
|
|
59
61
|
import { PluginError } from './plugin-manifest.js';
|
|
@@ -105,6 +107,7 @@ export interface AuthoredExecutionRuntime {
|
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
export interface AuthoredFlowExecutionResult {
|
|
110
|
+
readonly state?: undefined;
|
|
108
111
|
readonly executionRuntime?: AuthoredExecutionRuntime;
|
|
109
112
|
readonly rootRunId?: string;
|
|
110
113
|
readonly name: string;
|
|
@@ -118,6 +121,14 @@ export interface AuthoredFlowExecutionResult {
|
|
|
118
121
|
readonly journalSteps: readonly AuthoredFlowJournalStep[];
|
|
119
122
|
}
|
|
120
123
|
|
|
124
|
+
/** A body reached a durable event boundary and released its worker lease. */
|
|
125
|
+
export interface AuthoredFlowSuspendedResult {
|
|
126
|
+
readonly state: 'suspended';
|
|
127
|
+
readonly name: string;
|
|
128
|
+
readonly suspension: AuthoredFlowSuspension;
|
|
129
|
+
readonly journalSteps: readonly AuthoredFlowJournalStep[];
|
|
130
|
+
}
|
|
131
|
+
|
|
121
132
|
// The authoring SURFACE stays wide: `done()` accepts every
|
|
122
133
|
// `FlowCompletionReason` and refuses the kernel-owned ones at runtime with a
|
|
123
134
|
// diagnostic that names the alternative. The RESULT is narrow, because `done()`
|
|
@@ -174,6 +185,8 @@ export interface ExecuteAuthoredFlowOptions {
|
|
|
174
185
|
readonly onWait?: RunLifecycleOptions['onWait'];
|
|
175
186
|
readonly onProgress?: (event: ProgressEvent) => void;
|
|
176
187
|
readonly localAgentStream?: string;
|
|
188
|
+
/** Environment reserved for provider probes and local agent subprocesses. */
|
|
189
|
+
readonly agentEnvironment?: NodeJS.ProcessEnv;
|
|
177
190
|
/**
|
|
178
191
|
* How many agent (and, separately, LLM) dispatches the attached local
|
|
179
192
|
* workers hold at once. Set, it caps this body's concurrent `f.agent` /
|
|
@@ -273,6 +286,7 @@ export async function executeAuthoredFlow<Input = undefined>(
|
|
|
273
286
|
const journalSteps: AuthoredFlowJournalStep[] = [];
|
|
274
287
|
const authoredSteps: AuthoredFlowOperation<unknown>[] = [];
|
|
275
288
|
const lifecycle = new AuthoredFlowLifecycle();
|
|
289
|
+
const activities = new AuthoredActivities(journal, options.rootRunId);
|
|
276
290
|
const edgeOverrides = new Map<string, AuthoredStepEdges>();
|
|
277
291
|
const stepEdges = (step: string): AuthoredStepEdges | undefined => {
|
|
278
292
|
const overridden = edgeOverrides.get(step);
|
|
@@ -300,6 +314,7 @@ export async function executeAuthoredFlow<Input = undefined>(
|
|
|
300
314
|
definition.name, journal, journalSteps, budget, {
|
|
301
315
|
...(options.rootRunId === undefined ? {} : { rootRunId: options.rootRunId }),
|
|
302
316
|
...(options.dataDir === undefined ? {} : { dataDir: options.dataDir }),
|
|
317
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
303
318
|
stepEdges,
|
|
304
319
|
},
|
|
305
320
|
);
|
|
@@ -307,7 +322,7 @@ export async function executeAuthoredFlow<Input = undefined>(
|
|
|
307
322
|
const worker = authoredWorkerRunner(
|
|
308
323
|
definition, journal, flowPath, journalSteps, waitOptions,
|
|
309
324
|
localAgentStream, budget, definition.header.budget, options.rootRunId, options.workerCapacity, stepEdges,
|
|
310
|
-
options.workerSlots,
|
|
325
|
+
options.workerSlots, options.agentEnvironment,
|
|
311
326
|
);
|
|
312
327
|
|
|
313
328
|
/**
|
|
@@ -567,6 +582,17 @@ export async function executeAuthoredFlow<Input = undefined>(
|
|
|
567
582
|
);
|
|
568
583
|
return trackStep(authoredSteps, agentOp);
|
|
569
584
|
},
|
|
585
|
+
on(source, activityOptions) {
|
|
586
|
+
assertOperationAllowed('on', definition.name, requestedCompletion);
|
|
587
|
+
// Activity ids are root-scoped (`activity-N`); a child's own counter would collide.
|
|
588
|
+
if ((options.dispatchDepth ?? 0) > 0) {
|
|
589
|
+
throw new AuthoredFlowExecutionError(
|
|
590
|
+
'dispatch_invalid',
|
|
591
|
+
`child flow "${definition.name}" cannot call f.on in this release; activities are root-scoped`,
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
return activities.open(source, activityOptions);
|
|
595
|
+
},
|
|
570
596
|
/**
|
|
571
597
|
* `f.human` (docs/SURFACE.md §1, §7). The question is not a child run: it
|
|
572
598
|
* is the ROOT attempt parking on the kernel's `wait.human`. The body
|
|
@@ -661,6 +687,7 @@ export async function executeAuthoredFlow<Input = undefined>(
|
|
|
661
687
|
dataDir: options.dataDir,
|
|
662
688
|
flowPath: child.path,
|
|
663
689
|
...(localAgentStream === undefined ? {} : { localAgentStream }),
|
|
690
|
+
...(options.agentEnvironment === undefined ? {} : { agentEnvironment: options.agentEnvironment }),
|
|
664
691
|
...(options.workerCapacity === undefined ? {} : { workerCapacity: options.workerCapacity }),
|
|
665
692
|
...(options.rootRunId === undefined ? {} : { rootRunId: options.rootRunId }),
|
|
666
693
|
flowGraph: options.flowGraph,
|
|
@@ -794,6 +821,11 @@ export async function executeAuthoredFlow<Input = undefined>(
|
|
|
794
821
|
try {
|
|
795
822
|
worker.stop(bodyFailure);
|
|
796
823
|
await stopAuthoredOperations(authoredSteps, bodyFailure);
|
|
824
|
+
// A durable wait hands execution back to the control plane. Its
|
|
825
|
+
// subscriptions must keep receiving events while no body is running.
|
|
826
|
+
const parked = bodyFailure instanceof AuthoredFlowExecutionError
|
|
827
|
+
&& (bodyFailure.code === 'subscription_suspended' || bodyFailure.code === 'human_parked');
|
|
828
|
+
if (!parked) await activities.closeAll('canceled');
|
|
797
829
|
} finally {
|
|
798
830
|
lifecycle.close();
|
|
799
831
|
}
|
|
@@ -821,6 +853,7 @@ export async function executeAuthoredFlow<Input = undefined>(
|
|
|
821
853
|
try {
|
|
822
854
|
worker.stop(missingCompletion);
|
|
823
855
|
await stopAuthoredOperations(authoredSteps, missingCompletion);
|
|
856
|
+
await activities.closeAll('canceled');
|
|
824
857
|
} finally {
|
|
825
858
|
lifecycle.close();
|
|
826
859
|
}
|
|
@@ -828,6 +861,16 @@ export async function executeAuthoredFlow<Input = undefined>(
|
|
|
828
861
|
}
|
|
829
862
|
try {
|
|
830
863
|
await verifyAuthoredOperations(definition.name, authoredSteps, lifecycle);
|
|
864
|
+
} catch (error) {
|
|
865
|
+
try {
|
|
866
|
+
await activities.closeAll('canceled');
|
|
867
|
+
} finally {
|
|
868
|
+
lifecycle.close();
|
|
869
|
+
}
|
|
870
|
+
throw error;
|
|
871
|
+
}
|
|
872
|
+
try {
|
|
873
|
+
await activities.closeAll('run_completed');
|
|
831
874
|
} finally {
|
|
832
875
|
lifecycle.close();
|
|
833
876
|
}
|
|
@@ -14,6 +14,7 @@ import { helperWriteback, HelperDeliveryError } from './helper-writeback.js';
|
|
|
14
14
|
import { checkSlackHelpers } from './slack-preflight.js';
|
|
15
15
|
import { atomicJson, readSlackReceipt, receiptPath, slackWriteback, type SlackCall } from './slack-writeback.js';
|
|
16
16
|
import { authoredChildAdmissionKey } from './authored-admission.js';
|
|
17
|
+
import { NO_MODEL_COST } from './reported-cost.js';
|
|
17
18
|
|
|
18
19
|
type ProviderCall = HelperCall | SlackCall;
|
|
19
20
|
|
|
@@ -135,11 +136,11 @@ async function completeHelperDispatch(client: JournalClient, dispatch: StepDispa
|
|
|
135
136
|
await client.stepComplete(dispatch.run_id, dispatch.step_id, dispatch.attempt,
|
|
136
137
|
dispatch.idempotency_key, 'worker_error', {
|
|
137
138
|
output: { ...call, diagnostic: error.message },
|
|
138
|
-
started_pins: dispatch.pins, end_pins: dispatch.pins, effects: [],
|
|
139
|
+
started_pins: dispatch.pins, end_pins: dispatch.pins, effects: [], reported_cost: NO_MODEL_COST,
|
|
139
140
|
});
|
|
140
141
|
return;
|
|
141
142
|
}
|
|
142
143
|
await client.stepComplete(dispatch.run_id, dispatch.step_id, dispatch.attempt,
|
|
143
|
-
dispatch.idempotency_key, 'success', { output, started_pins: dispatch.pins, end_pins: dispatch.pins,
|
|
144
|
+
dispatch.idempotency_key, 'success', { output, started_pins: dispatch.pins, end_pins: dispatch.pins, reported_cost: NO_MODEL_COST,
|
|
144
145
|
effects: [{ surface_path: `/${call.provider}`, idempotency_key: dispatch.idempotency_key }] });
|
|
145
146
|
}
|
package/src/authored-mcp.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { AuthoredFlowJournalStep } from './authored-flow-executor.js';
|
|
|
12
12
|
import { readCompletedStepOutput } from './authored-step-output.js';
|
|
13
13
|
import { withWorkerLease } from './worker-lease.js';
|
|
14
14
|
import { authoredChildAdmissionKey } from './authored-admission.js';
|
|
15
|
+
import { NO_MODEL_COST } from './reported-cost.js';
|
|
15
16
|
|
|
16
17
|
/** Own keys expose precisely the preflight inventory, including prototype-like names. */
|
|
17
18
|
export function buildMcpProxy(
|
|
@@ -116,6 +117,7 @@ export async function runMcpEffect(
|
|
|
116
117
|
output: { ...receipt, output: output ?? null, ...(diagnostic ? { diagnostic } : {}) },
|
|
117
118
|
started_pins: event.pins, end_pins: event.pins,
|
|
118
119
|
...(diagnostic ? { trajectory_tail: { ...receipt, diagnostic } } : {}),
|
|
120
|
+
reported_cost: NO_MODEL_COST,
|
|
119
121
|
effects: confirmed ? [{ surface_path: surfacePath, idempotency_key: event.idempotency_key }] : [],
|
|
120
122
|
});
|
|
121
123
|
}
|
|
@@ -140,13 +142,13 @@ export async function runMcpEffect(
|
|
|
140
142
|
childRunId = outcome.run_id;
|
|
141
143
|
if (outcome.status !== 'completed') await completed;
|
|
142
144
|
if (diagnostic !== undefined) {
|
|
143
|
-
try { await readCompletedStepOutput(journal, outcome.run_id, id, journalSteps); }
|
|
145
|
+
try { await readCompletedStepOutput(journal, outcome.run_id, id, journalSteps, {}, outcome.status); }
|
|
144
146
|
catch (error) {
|
|
145
147
|
if (!(error instanceof AuthoredFlowExecutionError) || error.code !== 'step_failed') throw error;
|
|
146
148
|
throw new McpStepError(diagnostic, outcome.run_id);
|
|
147
149
|
}
|
|
148
150
|
}
|
|
149
|
-
const receipt = await readCompletedStepOutput(journal, outcome.run_id, id, journalSteps) as { output: unknown };
|
|
151
|
+
const receipt = await readCompletedStepOutput(journal, outcome.run_id, id, journalSteps, {}, outcome.status) as { output: unknown };
|
|
150
152
|
return receipt.output;
|
|
151
153
|
} finally {
|
|
152
154
|
clearTimeout(timer);
|
|
@@ -8,6 +8,7 @@ import { assertAuthoredNodeVersion, parseAuthoredParentPid } from './authored-ru
|
|
|
8
8
|
import { AuthoredFlowExecutionError, AuthoredHumanParked } from './authored-flow-error.js';
|
|
9
9
|
import { isAgentCapacity } from './worker-slots.js';
|
|
10
10
|
import type { AuthoredRootMetadata } from './authored-root.js';
|
|
11
|
+
import { validatedLocalAgentEnvironment } from './local-agent-environment.js';
|
|
11
12
|
|
|
12
13
|
let channelKey: string | undefined, sequence = 0;
|
|
13
14
|
// Capture writers before loading authored modules; credentials never enter env.
|
|
@@ -49,7 +50,8 @@ try {
|
|
|
49
50
|
send({ type: 'ready', runtime: { kind: 'node', version: process.versions.node,
|
|
50
51
|
executableSha256: hash(process.execPath), payloadSha256: hash(process.argv[1]!) } });
|
|
51
52
|
const request = await new Promise<{ channelKey: string; metadata: AuthoredRootMetadata; socketPath: string;
|
|
52
|
-
rootRunId: string; dataDir: string; localAgentStream?: string; workerCapacity?: number
|
|
53
|
+
rootRunId: string; dataDir: string; localAgentStream?: string; workerCapacity?: number;
|
|
54
|
+
agentEnvironment?: unknown }>((resolve, reject) => {
|
|
53
55
|
let buffer = '';
|
|
54
56
|
process.stdin.setEncoding('utf8');
|
|
55
57
|
const onData = (chunk: string): void => {
|
|
@@ -65,6 +67,10 @@ try {
|
|
|
65
67
|
if (typeof request.channelKey !== 'string' || !/^[a-f0-9]{64}$/.test(request.channelKey)) throw new Error('invalid authored channel key');
|
|
66
68
|
channelKey = request.channelKey;
|
|
67
69
|
controller.signal.throwIfAborted();
|
|
70
|
+
// Validate the parent handoff before evaluating any authored module. Only
|
|
71
|
+
// provider credentials may cross this channel; controls such as PATH and
|
|
72
|
+
// NODE_OPTIONS remain the child's inherited values.
|
|
73
|
+
const agentEnvironment = validatedLocalAgentEnvironment(request.agentEnvironment);
|
|
68
74
|
const loaded = await loadPinnedAuthoredSource(request.metadata, true);
|
|
69
75
|
if (request.localAgentStream !== request.metadata.localAgentStream) throw new Error('authored root local agent surface mismatch');
|
|
70
76
|
if (request.workerCapacity !== undefined && !isAgentCapacity(request.workerCapacity)) throw new Error('invalid authored worker capacity');
|
|
@@ -78,6 +84,7 @@ try {
|
|
|
78
84
|
flowGraph: loaded.graph,
|
|
79
85
|
localAgentStream: request.localAgentStream, signal: controller.signal,
|
|
80
86
|
...(request.workerCapacity === undefined ? {} : { workerCapacity: request.workerCapacity }),
|
|
87
|
+
...(agentEnvironment === undefined ? {} : { agentEnvironment: { ...process.env, ...agentEnvironment } }),
|
|
81
88
|
onProgress: event => send({ type: 'progress', event }),
|
|
82
89
|
onWait: event => send({ type: 'wait', event }),
|
|
83
90
|
});
|
|
@@ -94,7 +101,9 @@ try {
|
|
|
94
101
|
// the only one that saw the child's classification.
|
|
95
102
|
...(error instanceof AuthoredFlowExecutionError ? { code: error.code,
|
|
96
103
|
completionReason: error.completionReason, runId: error.runId,
|
|
97
|
-
details: error.details,
|
|
104
|
+
details: error.details,
|
|
105
|
+
parkCause: error.parkCause,
|
|
106
|
+
...(error.suspension === undefined ? {} : { suspension: error.suspension }) } : {}),
|
|
98
107
|
...(error instanceof AuthoredHumanParked ? { wait: error.wait } : {}) });
|
|
99
108
|
process.exitCode = 1;
|
|
100
109
|
} finally {
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
} from './failure-kinds.js';
|
|
19
19
|
import { HUMAN_WAIT_ID } from './authored-human.js';
|
|
20
20
|
import { assertAuthoredPromiseHooks } from './authored-runtime-capability.js';
|
|
21
|
+
import { settlingCompletion } from './step-settling.js';
|
|
21
22
|
|
|
22
23
|
let embeddedSource: string | undefined;
|
|
23
24
|
/** Installed by the standalone build; never fetched or resolved from a workspace. */
|
|
@@ -133,7 +134,8 @@ export async function runAuthoredInNode(
|
|
|
133
134
|
// Keep stdin open: EOF tells the child its lease-owning parent died.
|
|
134
135
|
child.stdin!.write(JSON.stringify({ channelKey, metadata, socketPath, rootRunId,
|
|
135
136
|
dataDir: options.dataDir, localAgentStream: options.localAgentStream,
|
|
136
|
-
workerCapacity: options.workerCapacity
|
|
137
|
+
workerCapacity: options.workerCapacity,
|
|
138
|
+
agentEnvironment: options.agentEnvironment }) + '\n');
|
|
137
139
|
} else if (!ready || result) throw new Error('unexpected authored runtime message');
|
|
138
140
|
else if (message.type === 'progress') options.onProgress?.(message.event);
|
|
139
141
|
else if (message.type === 'wait') options.onWait?.(message.event);
|
|
@@ -144,7 +146,8 @@ export async function runAuthoredInNode(
|
|
|
144
146
|
} else if (typeof message.code === 'string') {
|
|
145
147
|
const authored = new AuthoredFlowExecutionError(message.code as AuthoredFlowExecutionErrorCode,
|
|
146
148
|
message.message, message.completionReason, message.runId,
|
|
147
|
-
stepFailedFrame(message.details)
|
|
149
|
+
stepFailedFrame(message.details),
|
|
150
|
+
isSuspension(message.suspension) ? message.suspension : undefined);
|
|
148
151
|
// Validated, not trusted, like `details`: an unrecognised cause
|
|
149
152
|
// is dropped so the boundary says nothing about workers rather
|
|
150
153
|
// than acting on a value this frame could have invented.
|
|
@@ -172,6 +175,35 @@ export async function runAuthoredInNode(
|
|
|
172
175
|
} finally { await rm(directory, { recursive: true, force: true }); }
|
|
173
176
|
}
|
|
174
177
|
|
|
178
|
+
function isSuspension(value: unknown): value is import('./authored-flow-error.js').AuthoredFlowSuspension {
|
|
179
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;
|
|
180
|
+
const record = value as Record<string, unknown>;
|
|
181
|
+
const common = typeof record['subscriptionId'] === 'string' && record['subscriptionId'].length > 0
|
|
182
|
+
&& typeof record['stream'] === 'string' && record['stream'].length > 0
|
|
183
|
+
&& Number.isSafeInteger(record['deadlineAtMs']) && (record['deadlineAtMs'] as number) >= 0;
|
|
184
|
+
if (!common) return false;
|
|
185
|
+
if (record['kind'] === 'event_wait') return true;
|
|
186
|
+
return record['kind'] === 'activation'
|
|
187
|
+
&& Array.isArray(record['eventTypes']) && record['eventTypes'].length > 0
|
|
188
|
+
&& record['eventTypes'].every(type => typeof type === 'string' && type.length > 0)
|
|
189
|
+
&& (record['pattern'] === undefined || isJsonRecord(record['pattern']))
|
|
190
|
+
&& Number.isSafeInteger(record['settleMs']) && (record['settleMs'] as number) >= 0
|
|
191
|
+
&& Number.isSafeInteger(record['idleMs']) && (record['idleMs'] as number) > 0
|
|
192
|
+
&& typeof record['includeSelf'] === 'boolean';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function isJsonRecord(value: unknown): value is Record<string, unknown> {
|
|
196
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
197
|
+
&& Object.values(value).every(isJsonValue);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function isJsonValue(value: unknown): boolean {
|
|
201
|
+
return value === null || typeof value === 'boolean' || typeof value === 'string'
|
|
202
|
+
|| (typeof value === 'number' && Number.isFinite(value))
|
|
203
|
+
|| (Array.isArray(value) && value.every(isJsonValue))
|
|
204
|
+
|| isJsonRecord(value);
|
|
205
|
+
}
|
|
206
|
+
|
|
175
207
|
/** A park signal from the child names the question the parent must journal. */
|
|
176
208
|
function isHumanWaitFrame(value: unknown): value is AuthoredHumanWait {
|
|
177
209
|
const wait = value as Partial<AuthoredHumanWait> | null;
|
|
@@ -342,15 +374,17 @@ export async function verifyAuthoredNodeResult(
|
|
|
342
374
|
// step a child spec may carry, and it must have completed too.
|
|
343
375
|
const lowered = spec?.steps ?? [];
|
|
344
376
|
const specShape = lowered.length === 1 || (lowered.length === 2 && lowered[1]?.id === `${claimed.id}.gate`);
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
377
|
+
// Judged against the step's SETTLING completion, the same one the
|
|
378
|
+
// authored reader returned: a retried attempt (`disposition: retry`,
|
|
379
|
+
// e.g. crash recovery's `crashed`) journals a completion of its own
|
|
380
|
+
// ahead of it, so neither a count nor the first entry is the verdict.
|
|
381
|
+
const completed = settlingCompletion(entries, claimed.id);
|
|
382
|
+
const gateCompleted = lowered.length === 2 ? settlingCompletion(entries, `${claimed.id}.gate`) : undefined;
|
|
348
383
|
const terminalFacts = entries.filter(entry => entry.entry_type === 'run.completed');
|
|
349
384
|
if (spec?.name !== `${metadata.flowName}/${claimed.id}` || !specShape
|
|
350
|
-
|| step?.id !== claimed.id || completed
|
|
351
|
-
||
|
|
352
|
-
||
|
|
353
|
-
|| terminalFacts.length !== 1 || terminalFacts[0]?.payload?.completionReason !== 'success') invalid(`evidence ${claimed.id} spec=${spec?.name} step=${step?.id} completed=${completed.length}`);
|
|
385
|
+
|| step?.id !== claimed.id || completed?.payload?.completionReason !== 'success'
|
|
386
|
+
|| (lowered.length === 2 && gateCompleted?.payload?.completionReason !== 'success')
|
|
387
|
+
|| terminalFacts.length !== 1 || terminalFacts[0]?.payload?.completionReason !== 'success') invalid(`evidence ${claimed.id} spec=${spec?.name} step=${step?.id} completed=${completed?.payload?.completionReason}`);
|
|
354
388
|
if (claimed === terminal) {
|
|
355
389
|
// The claimed verdict must match the marker the journal actually
|
|
356
390
|
// recorded, so an IPC frame cannot claim `success` over a run whose
|
|
@@ -11,6 +11,7 @@ import type { AuthoredFlowJournalStep } from './authored-flow-executor.js';
|
|
|
11
11
|
import { readCompletedStepOutput } from './authored-step-output.js';
|
|
12
12
|
import { withWorkerLease } from './worker-lease.js';
|
|
13
13
|
import { authoredChildAdmissionKey } from './authored-admission.js';
|
|
14
|
+
import { NO_MODEL_COST } from './reported-cost.js';
|
|
14
15
|
|
|
15
16
|
export class PluginStepError extends AuthoredFlowExecutionError {
|
|
16
17
|
constructor(readonly diagnostic: string, runId: string) {
|
|
@@ -88,6 +89,7 @@ export async function runPluginEffect(
|
|
|
88
89
|
output: { ...receipt, output: output ?? null, ...(diagnostic ? { diagnostic } : {}) },
|
|
89
90
|
started_pins: event.pins, end_pins: event.pins,
|
|
90
91
|
...(diagnostic ? { trajectory_tail: { ...receipt, diagnostic } } : {}),
|
|
92
|
+
reported_cost: NO_MODEL_COST,
|
|
91
93
|
effects: confirmed ? [{ surface_path: surfacePath, idempotency_key: event.idempotency_key }] : [],
|
|
92
94
|
});
|
|
93
95
|
}
|
|
@@ -125,13 +127,13 @@ export async function runPluginEffect(
|
|
|
125
127
|
if (dispatchExpired) await cancelChildRun();
|
|
126
128
|
if (outcome.status !== 'completed') await completed;
|
|
127
129
|
if (diagnostic !== undefined) {
|
|
128
|
-
try { await readCompletedStepOutput(journal, outcome.run_id, id, journalSteps); }
|
|
130
|
+
try { await readCompletedStepOutput(journal, outcome.run_id, id, journalSteps, {}, outcome.status); }
|
|
129
131
|
catch (error) {
|
|
130
132
|
if (!(error instanceof AuthoredFlowExecutionError) || error.code !== 'step_failed') throw error;
|
|
131
133
|
throw new PluginStepError(diagnostic, outcome.run_id);
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
|
-
const receipt = await readCompletedStepOutput(journal, outcome.run_id, id, journalSteps) as { output: unknown };
|
|
136
|
+
const receipt = await readCompletedStepOutput(journal, outcome.run_id, id, journalSteps, {}, outcome.status) as { output: unknown };
|
|
135
137
|
return receipt.output;
|
|
136
138
|
}, admissionKey), deadline]);
|
|
137
139
|
} finally {
|
|
@@ -6,7 +6,7 @@ import { cliProbeKey, CliProbeError, resolvePreflight, type CliProbeOutcome } fr
|
|
|
6
6
|
import type { FlowSpec } from './spec.js';
|
|
7
7
|
|
|
8
8
|
/** Auth/model facts belong to one execution, including concurrent cold callers. */
|
|
9
|
-
export function authoredPreflight(path: string) {
|
|
9
|
+
export function authoredPreflight(path: string, environment?: NodeJS.ProcessEnv) {
|
|
10
10
|
const probeCache = new Map<string, CliProbeOutcome>();
|
|
11
11
|
const pending = new Map<string, Promise<void>>();
|
|
12
12
|
const directory = dirname(resolve(path));
|
|
@@ -30,7 +30,7 @@ export function authoredPreflight(path: string) {
|
|
|
30
30
|
try {
|
|
31
31
|
probeCache.set(key, { result: await probeCliAsync(resolution.cli,
|
|
32
32
|
resolution.source === 'project' ? config.directory : directory,
|
|
33
|
-
resolution.model, managed ? 'managed' : undefined) });
|
|
33
|
+
resolution.model, managed ? 'managed' : undefined, environment) });
|
|
34
34
|
} catch (error) {
|
|
35
35
|
probeCache.set(key, { failure: error instanceof CliProbeError ? error.detail : null });
|
|
36
36
|
}
|
package/src/authored-root.ts
CHANGED
|
@@ -4,7 +4,7 @@ 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, type AuthoredFlowExecutionResult } from './authored-flow-executor.js';
|
|
7
|
+
import { executeAuthoredFlow, type AuthoredFlowExecutionResult, type AuthoredFlowSuspendedResult } from './authored-flow-executor.js';
|
|
8
8
|
import { isDurableCompletionDetail, isLoweredCompletion } from './authored-completion.js';
|
|
9
9
|
import { AUTHORED_ROOT_KIND } from './authored-verdict.js';
|
|
10
10
|
import {
|
|
@@ -20,6 +20,12 @@ import { withWorkerLease } from './worker-lease.js';
|
|
|
20
20
|
import { AuthoredFlowExecutionError, AuthoredHumanParked } from './authored-flow-error.js';
|
|
21
21
|
import { readOpenHumanWaits } from './authored-human.js';
|
|
22
22
|
import { isSurfaceCompletionReason } from './authored-step-output.js';
|
|
23
|
+
import { readSubscriptionPark } from './authored-subscription-park.js';
|
|
24
|
+
import { localAgentCredentialEnvironment } from './local-agent-environment.js';
|
|
25
|
+
|
|
26
|
+
export type DurableAuthoredFlowResult =
|
|
27
|
+
| (AuthoredFlowExecutionResult & { readonly rootRunId: string })
|
|
28
|
+
| (AuthoredFlowSuspendedResult & { readonly rootRunId: string });
|
|
23
29
|
|
|
24
30
|
/**
|
|
25
31
|
* Taken from the projection module rather than spelled twice: `flows status`
|
|
@@ -55,11 +61,15 @@ export interface AuthoredRootSourceAuthority {
|
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
export interface DurableAuthoredOptions {
|
|
64
|
+
/** Preserve root authority even if a child later fails or parks. */
|
|
65
|
+
readonly onAdmitted?: (runId: string) => void;
|
|
58
66
|
readonly dataDir: string;
|
|
59
67
|
readonly admissionKey?: string;
|
|
60
68
|
readonly localAgentStream?: string;
|
|
61
69
|
/** Concurrency of the attached local workers; see `ExecuteAuthoredFlowOptions.workerCapacity`. */
|
|
62
70
|
readonly workerCapacity?: number;
|
|
71
|
+
/** Provider-only environment consumed by preflight and local workers. */
|
|
72
|
+
readonly agentEnvironment?: NodeJS.ProcessEnv;
|
|
63
73
|
readonly lifecycle?: RunLifecycleOptions;
|
|
64
74
|
}
|
|
65
75
|
|
|
@@ -69,7 +79,7 @@ export async function executeDurableAuthoredFlow(
|
|
|
69
79
|
journal: JournalClient,
|
|
70
80
|
input: unknown,
|
|
71
81
|
options: DurableAuthoredOptions,
|
|
72
|
-
): Promise<
|
|
82
|
+
): Promise<DurableAuthoredFlowResult> {
|
|
73
83
|
assertAuthoredRuntimeAvailable();
|
|
74
84
|
const source = await readFile(loaded.sourcePath);
|
|
75
85
|
const sources = await Promise.all(loaded.graph.map(async node => Object.freeze({
|
|
@@ -106,6 +116,7 @@ export async function executeDurableAuthoredFlow(
|
|
|
106
116
|
workspace: [], streams: [{ stream, read_offset: 0 }],
|
|
107
117
|
});
|
|
108
118
|
const outcome = await journal.runStart(spec, undefined, admissionKey);
|
|
119
|
+
options.onAdmitted?.(outcome.run_id);
|
|
109
120
|
if (outcome.status === 'completed') {
|
|
110
121
|
dispatchWait.cancel();
|
|
111
122
|
return await completedRootResult(journal, outcome.run_id);
|
|
@@ -119,6 +130,9 @@ export async function executeDurableAuthoredFlow(
|
|
|
119
130
|
// redelivers only when the former worker connection is gone.
|
|
120
131
|
const resumed = await journal.runResume(outcome.run_id);
|
|
121
132
|
await assertNoOpenHumanWait(journal, resumed);
|
|
133
|
+
const parked = await readSubscriptionPark(journal, outcome.run_id);
|
|
134
|
+
if (parked !== undefined) return { state: 'suspended', name: metadata.flowName,
|
|
135
|
+
suspension: parked, journalSteps: [], rootRunId: outcome.run_id };
|
|
122
136
|
const dispatch = await dispatchWait.promise;
|
|
123
137
|
return await driveRoot(loaded, metadata, journal, peer, dispatch, options);
|
|
124
138
|
} finally {
|
|
@@ -132,7 +146,7 @@ export async function resumeDurableAuthoredFlow(
|
|
|
132
146
|
rootRunId: string,
|
|
133
147
|
journal: JournalClient,
|
|
134
148
|
options: Omit<DurableAuthoredOptions, 'admissionKey'>,
|
|
135
|
-
): Promise<
|
|
149
|
+
): Promise<DurableAuthoredFlowResult | undefined> {
|
|
136
150
|
const metadata = await readAuthoredRootMetadata(journal, rootRunId);
|
|
137
151
|
if (metadata === undefined) return undefined;
|
|
138
152
|
assertAuthoredRuntimeAvailable();
|
|
@@ -159,6 +173,9 @@ export async function resumeDurableAuthoredFlow(
|
|
|
159
173
|
assertRootCanDispatch(outcome);
|
|
160
174
|
await assertNoOpenHumanWait(journal, outcome);
|
|
161
175
|
options.lifecycle?.onRunStarted?.({ runId: rootRunId, flow: metadata.flowName, resumed: true });
|
|
176
|
+
const parked = await readSubscriptionPark(journal, rootRunId);
|
|
177
|
+
if (parked !== undefined) return { state: 'suspended', name: metadata.flowName,
|
|
178
|
+
suspension: parked, journalSteps: [], rootRunId };
|
|
162
179
|
const dispatch = await dispatchWait.promise;
|
|
163
180
|
return await driveRoot(loaded, metadata, journal, peer, dispatch, options);
|
|
164
181
|
} finally {
|
|
@@ -204,7 +221,7 @@ async function driveRoot(
|
|
|
204
221
|
peer: JournalClient,
|
|
205
222
|
dispatch: StepDispatchEvent,
|
|
206
223
|
options: Omit<DurableAuthoredOptions, 'admissionKey'>,
|
|
207
|
-
): Promise<
|
|
224
|
+
): Promise<DurableAuthoredFlowResult> {
|
|
208
225
|
try {
|
|
209
226
|
const result = await withWorkerLease(peer, dispatch, async rootSignal => {
|
|
210
227
|
const callerSignal = options.lifecycle?.signal;
|
|
@@ -212,6 +229,9 @@ async function driveRoot(
|
|
|
212
229
|
if (process.versions['bun'] !== undefined) {
|
|
213
230
|
return runAuthoredInNode(metadata, journal.socketPath, dispatch.run_id, {
|
|
214
231
|
dataDir: options.dataDir, localAgentStream: options.localAgentStream,
|
|
232
|
+
...(options.agentEnvironment === undefined ? {} : {
|
|
233
|
+
agentEnvironment: localAgentCredentialEnvironment(options.agentEnvironment),
|
|
234
|
+
}),
|
|
215
235
|
...(options.workerCapacity === undefined ? {} : { workerCapacity: options.workerCapacity }),
|
|
216
236
|
...options.lifecycle, signal,
|
|
217
237
|
});
|
|
@@ -225,6 +245,7 @@ async function driveRoot(
|
|
|
225
245
|
dataDir: options.dataDir,
|
|
226
246
|
flowPath: metadata.flowPath,
|
|
227
247
|
localAgentStream: options.localAgentStream,
|
|
248
|
+
...(options.agentEnvironment === undefined ? {} : { agentEnvironment: options.agentEnvironment }),
|
|
228
249
|
...(options.workerCapacity === undefined ? {} : { workerCapacity: options.workerCapacity }),
|
|
229
250
|
rootRunId: dispatch.run_id,
|
|
230
251
|
extensions: loaded.extensions,
|
|
@@ -248,6 +269,15 @@ async function driveRoot(
|
|
|
248
269
|
);
|
|
249
270
|
return Object.freeze({ ...result, rootRunId: dispatch.run_id });
|
|
250
271
|
} catch (error) {
|
|
272
|
+
if (error instanceof AuthoredFlowExecutionError
|
|
273
|
+
&& error.code === 'subscription_suspended'
|
|
274
|
+
&& error.suspension !== undefined) {
|
|
275
|
+
await peer.subscriptionPark({ run_id: dispatch.run_id, step_id: dispatch.step_id,
|
|
276
|
+
attempt: dispatch.attempt, idempotency_key: dispatch.idempotency_key,
|
|
277
|
+
subscription_id: error.suspension.subscriptionId, phase: error.suspension.kind });
|
|
278
|
+
return Object.freeze({ state: 'suspended' as const, name: metadata.flowName,
|
|
279
|
+
suspension: error.suspension, journalSteps: Object.freeze([]), rootRunId: dispatch.run_id });
|
|
280
|
+
}
|
|
251
281
|
if (error instanceof AuthoredHumanParked) {
|
|
252
282
|
// Not a failure: the body reached a question nobody has answered. Park
|
|
253
283
|
// THIS attempt on the kernel's `wait.human` under the body's own wait
|