@intx/workflow-host 0.2.2 → 0.4.0
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/README.md +77 -14
- package/dist/adapters/mail-part-store.d.ts +46 -0
- package/dist/adapters/mail-part-store.js +251 -0
- package/dist/adapters/repo-store.d.ts +22 -1
- package/dist/adapters/repo-store.js +56 -65
- package/dist/adapters/spawn-child.d.ts +109 -44
- package/dist/adapters/spawn-child.js +77 -81
- package/dist/adapters/step-invoker.d.ts +52 -2
- package/dist/adapters/step-invoker.js +284 -37
- package/dist/adapters/substrate-mailbox-store.d.ts +80 -0
- package/dist/adapters/substrate-mailbox-store.js +404 -0
- package/dist/child/child-mailbox-reader.d.ts +10 -0
- package/dist/child/child-mailbox-reader.js +23 -0
- package/dist/child/credential-cell.d.ts +8 -0
- package/dist/child/credential-cell.js +66 -0
- package/dist/child/env-bootstrap.d.ts +20 -6
- package/dist/child/env-bootstrap.js +9 -1
- package/dist/child/from-process-env.d.ts +12 -0
- package/dist/child/from-process-env.js +6 -0
- package/dist/child/index.d.ts +6 -2
- package/dist/child/index.js +4 -1
- package/dist/child/mailbox-mutation-bridge.d.ts +61 -0
- package/dist/child/mailbox-mutation-bridge.js +101 -0
- package/dist/child/mailbox-watch-registry.d.ts +17 -0
- package/dist/child/mailbox-watch-registry.js +61 -0
- package/dist/child/outbound-mail-bridge.d.ts +3 -2
- package/dist/child/outbound-mail-bridge.js +20 -32
- package/dist/child/parked-correlations.d.ts +42 -0
- package/dist/child/parked-correlations.js +80 -0
- package/dist/child/pending-request.d.ts +89 -0
- package/dist/child/pending-request.js +80 -0
- package/dist/child/proxy-repo-store.d.ts +3 -2
- package/dist/child/proxy-repo-store.js +2 -0
- package/dist/child/run-child.d.ts +170 -14
- package/dist/child/run-child.js +569 -155
- package/dist/child/self-discovery.d.ts +10 -0
- package/dist/child/self-discovery.js +25 -1
- package/dist/child/substrate-write-bridge.d.ts +3 -2
- package/dist/child/substrate-write-bridge.js +21 -38
- package/dist/child/supervisor-backed-transport.d.ts +52 -6
- package/dist/child/supervisor-backed-transport.js +205 -62
- package/dist/child/verified-definition-loader.d.ts +33 -0
- package/dist/child/verified-definition-loader.js +43 -0
- package/dist/child/warm-agent-cache.d.ts +44 -4
- package/dist/child/warm-agent-cache.js +41 -10
- package/dist/index.d.ts +6 -4
- package/dist/index.js +6 -4
- package/dist/ipc/control-channel.d.ts +151 -2
- package/dist/ipc/control-channel.js +222 -29
- package/dist/ipc/event-channel.d.ts +32 -1
- package/dist/ipc/index.d.ts +1 -1
- package/dist/ipc/index.js +1 -1
- package/dist/mail-bus/hub-transport-adapter.d.ts +12 -7
- package/dist/mail-bus/hub-transport-adapter.js +9 -5
- package/dist/run-body-then-cleanup.d.ts +17 -0
- package/dist/run-body-then-cleanup.js +38 -0
- package/dist/seams/scheduler.d.ts +16 -6
- package/dist/seams/scheduler.js +87 -97
- package/dist/supervisor/cancel-signing.d.ts +2 -2
- package/dist/supervisor/cancel-signing.js +4 -8
- package/dist/supervisor/credentials.d.ts +28 -15
- package/dist/supervisor/credentials.js +7 -7
- package/dist/supervisor/dispatch-attribution.js +1 -1
- package/dist/supervisor/drain-timeout.d.ts +2 -2
- package/dist/supervisor/drain-timeout.js +1 -1
- package/dist/supervisor/index.d.ts +3 -3
- package/dist/supervisor/index.js +2 -2
- package/dist/supervisor/recycle.d.ts +10 -3
- package/dist/supervisor/recycle.js +18 -7
- package/dist/supervisor/run-event-compaction.d.ts +5 -5
- package/dist/supervisor/run-event-compaction.js +14 -19
- package/dist/supervisor/run-event-recovery.d.ts +34 -0
- package/dist/supervisor/run-event-recovery.js +45 -0
- package/dist/supervisor/spawn-env.d.ts +2 -2
- package/dist/supervisor/spawn-env.js +1 -1
- package/dist/supervisor/supervisor.d.ts +106 -26
- package/dist/supervisor/supervisor.js +1903 -414
- package/dist/supervisor/terminal-commit.d.ts +36 -0
- package/dist/supervisor/terminal-commit.js +130 -0
- package/dist/supervisor/types.d.ts +180 -23
- package/dist/testing/change-notifier.d.ts +12 -0
- package/dist/testing/change-notifier.js +63 -0
- package/dist/testing/index.d.ts +8 -0
- package/dist/testing/index.js +16 -0
- package/dist/testing/log-capture.d.ts +52 -0
- package/dist/testing/log-capture.js +124 -0
- package/dist/testing/mail-bus.d.ts +22 -0
- package/dist/testing/mail-bus.js +78 -0
- package/dist/testing/memory-streams.d.ts +43 -0
- package/dist/testing/memory-streams.js +211 -0
- package/dist/testing/spawn-observer.d.ts +12 -0
- package/dist/testing/spawn-observer.js +36 -0
- package/dist/testing/stub-repo-store.d.ts +10 -0
- package/dist/testing/stub-repo-store.js +39 -0
- package/dist/testing/supervisor-reaper.d.ts +24 -0
- package/dist/testing/supervisor-reaper.js +49 -0
- package/dist/testing/upstream-frames.d.ts +47 -0
- package/dist/testing/upstream-frames.js +94 -0
- package/dist/workflow-definition-loader.d.ts +187 -0
- package/dist/workflow-definition-loader.js +422 -0
- package/package.json +18 -11
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { SpawnChildWorkflow, WorkflowDefinition } from "@intx/workflow";
|
|
1
|
+
import type { InferenceEvent } from "@intx/types/runtime";
|
|
2
|
+
import type { SpawnChildWorkflow, SpawnSuspendableChild, SuspendableChildHandle, WorkflowDefinition, WorkflowEvent } from "@intx/workflow";
|
|
3
|
+
import type { CredentialMaterialRef } from "../child/run-child.js";
|
|
3
4
|
/**
|
|
4
5
|
* The terminal-status shape the runtime body expects back from a
|
|
5
6
|
* spawn. Mirrored from `SpawnChildWorkflow`'s return type so the
|
|
@@ -16,7 +17,18 @@ export type ChildTerminalStatus = "completed" | "failed" | "cancelled";
|
|
|
16
17
|
*
|
|
17
18
|
* The callback receives the same `AbortSignal` the parent runtime
|
|
18
19
|
* passed into the adapter so a parent-initiated cancellation
|
|
19
|
-
* propagates to the child without an intermediate wrapper.
|
|
20
|
+
* propagates to the child without an intermediate wrapper. It also
|
|
21
|
+
* receives the parent run's live `onEvent` sink so the child's agent
|
|
22
|
+
* steps emit inference events up the same channel (mirroring
|
|
23
|
+
* {@link RunSuspendableChild}), and the spawn `depth` / ceiling so the
|
|
24
|
+
* child run's own spawns keep counting against the tree-wide bound.
|
|
25
|
+
*
|
|
26
|
+
* The callback also receives the parent run's live credential-material cell
|
|
27
|
+
* (the same reference the top-level step invoker reads live), so the child's
|
|
28
|
+
* inference resolves its source secret by `credentialId` against the run's
|
|
29
|
+
* current delivery -- a rotation the parent applies reaches the child through
|
|
30
|
+
* the shared reference. A non-sidecar executor that carries no credential
|
|
31
|
+
* material omits it.
|
|
20
32
|
*/
|
|
21
33
|
export type RunChildWorkflow = (input: {
|
|
22
34
|
definition: WorkflowDefinition;
|
|
@@ -26,49 +38,102 @@ export type RunChildWorkflow = (input: {
|
|
|
26
38
|
parentRunId: string;
|
|
27
39
|
parentStepId: string;
|
|
28
40
|
signal: AbortSignal;
|
|
29
|
-
|
|
41
|
+
depth: number;
|
|
42
|
+
maxChildSpawnDepth: number;
|
|
43
|
+
}, onEvent: (event: InferenceEvent) => void, credentialMaterial?: CredentialMaterialRef) => Promise<{
|
|
30
44
|
terminalStatus: ChildTerminalStatus;
|
|
31
45
|
}>;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
deployRef: string;
|
|
60
|
-
/**
|
|
61
|
-
* Runtime-supplied child execution callback. The adapter delegates
|
|
62
|
-
* here once the `WorkflowDefinition` is resolved; the supervisor
|
|
63
|
-
* owns the child `WorkflowRuntimeEnv` and the `runtimeRun`
|
|
64
|
-
* invocation.
|
|
65
|
-
*/
|
|
46
|
+
/**
|
|
47
|
+
* Construct the terminal `WorkflowRuntimeEnv.SpawnChildWorkflow` adapter for an
|
|
48
|
+
* owned childWorkflow import. The child re-evaluated the whole pinned closure
|
|
49
|
+
* and lifted every inline child to an internal `{ ref }`, so the child
|
|
50
|
+
* definitions are in hand and already covered by the parent's re-verify.
|
|
51
|
+
* Resolve each `definitionRef`
|
|
52
|
+
* from the in-memory `bodies` map and delegate to the runtime-supplied
|
|
53
|
+
* `runChild`, with NO on-disk round-trip and NO separate per-child re-verify:
|
|
54
|
+
* materializing the child back out and re-fingerprinting it would round-trip
|
|
55
|
+
* trusted-in-hand data for no gain, and the closure re-eval on restart
|
|
56
|
+
* re-derives the same bodies durably. Mirrors
|
|
57
|
+
* {@link createInMemorySpawnSuspendableChild} but drives the child terminal-only
|
|
58
|
+
* (await its terminal status) rather than across approval parks.
|
|
59
|
+
*/
|
|
60
|
+
/**
|
|
61
|
+
* Host-side widening of the runtime {@link SpawnChildWorkflow} contract: the
|
|
62
|
+
* same input plus the per-run `onEvent` sink the host injects. The runtime env
|
|
63
|
+
* carries the narrow `SpawnChildWorkflow` (no event slot); the caller wraps this
|
|
64
|
+
* with its run's `onEvent`, mirroring {@link HostSpawnSuspendableChild}. The
|
|
65
|
+
* sink is a call argument (not closed over at construction) because the resolver
|
|
66
|
+
* is selected once per deployment while `onEvent` is built per run. The run's
|
|
67
|
+
* live credential-material cell rides the same seam, so the child's inference
|
|
68
|
+
* reads the parent's current credential delivery.
|
|
69
|
+
*/
|
|
70
|
+
export type HostSpawnChild = (input: Parameters<SpawnChildWorkflow>[0], onEvent: (event: InferenceEvent) => void, credentialMaterial?: CredentialMaterialRef) => ReturnType<SpawnChildWorkflow>;
|
|
71
|
+
export declare function createInMemorySpawnChild(opts: {
|
|
72
|
+
bodies: ReadonlyMap<string, WorkflowDefinition>;
|
|
66
73
|
runChild: RunChildWorkflow;
|
|
67
|
-
}
|
|
74
|
+
}): HostSpawnChild;
|
|
75
|
+
/**
|
|
76
|
+
* Runtime-supplied suspendable child execution callback. The park-aware
|
|
77
|
+
* analog of {@link RunChildWorkflow}: the supervisor owns the child
|
|
78
|
+
* `WorkflowRuntimeEnv` construction and the `runtimeRun` invocation and
|
|
79
|
+
* returns a live `SuspendableChildHandle` the caller drives across the
|
|
80
|
+
* body's approval parks, rather than awaiting a terminal. The adapter is
|
|
81
|
+
* the single resolution point that hands the supervisor a concrete
|
|
82
|
+
* `WorkflowDefinition` alongside the parent attribution the runtime body
|
|
83
|
+
* produced.
|
|
84
|
+
*/
|
|
85
|
+
export type RunSuspendableChild = (input: {
|
|
86
|
+
definition: WorkflowDefinition;
|
|
87
|
+
definitionRef: string;
|
|
88
|
+
childRunId: string;
|
|
89
|
+
input: unknown;
|
|
90
|
+
parentRunId: string;
|
|
91
|
+
parentStepId: string;
|
|
92
|
+
signal: AbortSignal;
|
|
93
|
+
depth: number;
|
|
94
|
+
maxChildSpawnDepth: number;
|
|
95
|
+
resumeFromEvents?: readonly WorkflowEvent[];
|
|
96
|
+
},
|
|
97
|
+
/**
|
|
98
|
+
* Live inference-event sink for the child's agent steps. Threaded from the
|
|
99
|
+
* host's per-run funnel (the parent run's event-channel closure) so the
|
|
100
|
+
* body's inference events reach the hub's live stream instead of being
|
|
101
|
+
* silently dropped. Per-run durable attribution is unaffected -- the child
|
|
102
|
+
* runtime commits its events under `runs/<childRunId>/events/` regardless.
|
|
103
|
+
*/
|
|
104
|
+
onEvent: (event: InferenceEvent) => void,
|
|
105
|
+
/**
|
|
106
|
+
* The parent run's live credential-material cell. Threaded so the body's
|
|
107
|
+
* inference resolves its source secret by `credentialId` against the run's
|
|
108
|
+
* current delivery, reached live through the shared reference on a rotation.
|
|
109
|
+
* A non-sidecar executor that carries no credential material omits it.
|
|
110
|
+
*/
|
|
111
|
+
credentialMaterial?: CredentialMaterialRef) => Promise<SuspendableChildHandle>;
|
|
112
|
+
/**
|
|
113
|
+
* Host-side widening of the runtime {@link SpawnSuspendableChild} contract: the
|
|
114
|
+
* same input plus the per-run `onEvent` sink the host injects. The runtime
|
|
115
|
+
* calls the narrow `SpawnSuspendableChild` (no event slot); the host binding
|
|
116
|
+
* wired into the runtime env closes over the run's funnel and forwards it here,
|
|
117
|
+
* mirroring how `ChildStepInvoker` widens the runtime `StepInvoker` with
|
|
118
|
+
* `onEvent`. The runtime contract in `@intx/workflow` stays untouched. The
|
|
119
|
+
* run's live credential-material cell rides the same seam, so the body's
|
|
120
|
+
* inference reads the parent's current credential delivery.
|
|
121
|
+
*/
|
|
122
|
+
export type HostSpawnSuspendableChild = (input: Parameters<SpawnSuspendableChild>[0], onEvent: (event: InferenceEvent) => void, credentialMaterial?: CredentialMaterialRef) => ReturnType<SpawnSuspendableChild>;
|
|
68
123
|
/**
|
|
69
|
-
* Construct the
|
|
70
|
-
*
|
|
71
|
-
* the
|
|
72
|
-
*
|
|
124
|
+
* Construct the `WorkflowRuntimeEnv.SpawnSuspendableChild` adapter for the
|
|
125
|
+
* source-ref (code-sourced) path -- the only deploy lineage. The parent child
|
|
126
|
+
* re-evaluated the whole pinned closure in one sandbox and re-verified it
|
|
127
|
+
* against the approved hash -- which already covers every inline onTrigger body
|
|
128
|
+
* -- so the body definitions are in hand and already proven. Resolve each
|
|
129
|
+
* `definitionRef` from that in-memory `bodies` map and run it in-process, with
|
|
130
|
+
* NO disk round-trip and NO separate per-body re-verify: materializing the body
|
|
131
|
+
* back out and re-fingerprinting it would round-trip trusted-in-hand data for no
|
|
132
|
+
* gain, and the closure re-eval on restart re-derives the same bodies durably.
|
|
133
|
+
* The body still runs in the parent's sandbox (in-process today; a stricter
|
|
134
|
+
* per-body boundary is the deferred, opt-in SandboxBoundary case).
|
|
73
135
|
*/
|
|
74
|
-
export declare function
|
|
136
|
+
export declare function createInMemorySpawnSuspendableChild(opts: {
|
|
137
|
+
bodies: ReadonlyMap<string, WorkflowDefinition>;
|
|
138
|
+
runSuspendableChild: RunSuspendableChild;
|
|
139
|
+
}): HostSpawnSuspendableChild;
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
// Production `WorkflowRuntimeEnv.SpawnChildWorkflow` adapter.
|
|
2
2
|
//
|
|
3
|
-
// The runtime body sees the spawn callback shape: given a
|
|
4
|
-
//
|
|
5
|
-
// `childRunId`, the materialized child
|
|
6
|
-
// settle once the child run reaches a terminal
|
|
7
|
-
// itself does not execute the child workflow -- it resolves
|
|
8
|
-
// `definitionRef` into a concrete `WorkflowDefinition`
|
|
9
|
-
//
|
|
10
|
-
// runtime-supplied `runChild` callback. The supervisor wires the
|
|
3
|
+
// The runtime body sees the spawn callback shape: given a `definitionRef`
|
|
4
|
+
// (the internal ref the deploy step assigned when it lifted the authored
|
|
5
|
+
// inline child), a parent-allocated `childRunId`, the materialized child
|
|
6
|
+
// input, and parent attribution, settle once the child run reaches a terminal
|
|
7
|
+
// phase. The adapter itself does not execute the child workflow -- it resolves
|
|
8
|
+
// the `definitionRef` into a concrete `WorkflowDefinition` and delegates the
|
|
9
|
+
// spawn to a runtime-supplied `runChild` callback. The supervisor wires the
|
|
11
10
|
// callback against a child `WorkflowRuntimeEnv` and `runtimeRun`.
|
|
12
11
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
12
|
+
// Two spawn types with DIFFERENT trust structures resolve here, so they
|
|
13
|
+
// take different resolution paths -- not one shared resolver that pretends
|
|
14
|
+
// they are the same:
|
|
15
|
+
//
|
|
16
|
+
// - onTrigger BODY (the suspendable adapter): a body is a section
|
|
17
|
+
// extracted from the PARENT's own approved definition. Source-ref is the
|
|
18
|
+
// only deploy lineage, so the body is resolved in-memory from the parent's
|
|
19
|
+
// re-evaluated closure (`createInMemorySpawnSuspendableChild`), already
|
|
20
|
+
// covered by the parent's re-verify -- no separate on-disk read and no
|
|
21
|
+
// separate per-body re-verify.
|
|
22
|
+
//
|
|
23
|
+
// - childWorkflow (the terminal adapter): an owned import embedded inline in
|
|
24
|
+
// the parent's definition. It is lifted to an internal `{ ref }` at child
|
|
25
|
+
// boot and resolved in-memory from the parent's closure map
|
|
26
|
+
// (`createInMemorySpawnChild`) -- exactly like a source-ref onTrigger
|
|
27
|
+
// body, with NO on-disk asset and NO separate per-child re-verify (the
|
|
28
|
+
// parent's re-verify already covers it, since the inline child rides the
|
|
29
|
+
// parent's hashed projection). The terminal-only drive (await the child's
|
|
30
|
+
// terminal, no park) is the only thing that distinguishes it from the
|
|
31
|
+
// suspendable body adapter.
|
|
32
32
|
//
|
|
33
33
|
// Drain coordination is handled by the supervisor's drain primitive
|
|
34
34
|
// (`packages/workflow-host/src/supervisor`), not by this adapter. The
|
|
@@ -59,21 +59,17 @@
|
|
|
59
59
|
// `runChild` does -- but the callback's input shape (`{ definition,
|
|
60
60
|
// childRunId, ... }`) is the seam that makes the scoping unambiguous
|
|
61
61
|
// at the boundary.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const WORKFLOW_JSON_PATH = "workflow.json";
|
|
65
|
-
/**
|
|
66
|
-
* Construct the production `WorkflowRuntimeEnv.SpawnChildWorkflow`
|
|
67
|
-
* adapter. The substrate handle, the principal, the deploy ref, and
|
|
68
|
-
* the runtime-supplied child callback live in closure; the returned
|
|
69
|
-
* callable satisfies the runtime-env interface.
|
|
70
|
-
*/
|
|
71
|
-
export function createWorkflowSpawnChild(opts) {
|
|
72
|
-
return async ({ definitionRef, childRunId, input, parentRunId, parentStepId, signal, }) => {
|
|
62
|
+
export function createInMemorySpawnChild(opts) {
|
|
63
|
+
return async ({ definitionRef, childRunId, input, parentRunId, parentStepId, signal, depth, maxChildSpawnDepth, }, onEvent, credentialMaterial) => {
|
|
73
64
|
if (signal.aborted) {
|
|
74
65
|
throw abortError(signal);
|
|
75
66
|
}
|
|
76
|
-
const definition =
|
|
67
|
+
const definition = opts.bodies.get(definitionRef);
|
|
68
|
+
if (definition === undefined) {
|
|
69
|
+
throw new Error(`workflow-runtime: spawn-child has no in-memory childWorkflow ` +
|
|
70
|
+
`definition for ${JSON.stringify(definitionRef)}; the parent's ` +
|
|
71
|
+
`closure should have lifted every inline child`);
|
|
72
|
+
}
|
|
77
73
|
// Re-check the abort signal after the resolution await. The
|
|
78
74
|
// caller can fire `signal.abort()` between the entry-time check
|
|
79
75
|
// and here; without this re-check the child callback would be
|
|
@@ -91,46 +87,52 @@ export function createWorkflowSpawnChild(opts) {
|
|
|
91
87
|
parentRunId,
|
|
92
88
|
parentStepId,
|
|
93
89
|
signal,
|
|
94
|
-
|
|
90
|
+
depth,
|
|
91
|
+
maxChildSpawnDepth,
|
|
92
|
+
}, onEvent, credentialMaterial);
|
|
95
93
|
return { terminalStatus: result.terminalStatus };
|
|
96
94
|
};
|
|
97
95
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Construct the `WorkflowRuntimeEnv.SpawnSuspendableChild` adapter for the
|
|
98
|
+
* source-ref (code-sourced) path -- the only deploy lineage. The parent child
|
|
99
|
+
* re-evaluated the whole pinned closure in one sandbox and re-verified it
|
|
100
|
+
* against the approved hash -- which already covers every inline onTrigger body
|
|
101
|
+
* -- so the body definitions are in hand and already proven. Resolve each
|
|
102
|
+
* `definitionRef` from that in-memory `bodies` map and run it in-process, with
|
|
103
|
+
* NO disk round-trip and NO separate per-body re-verify: materializing the body
|
|
104
|
+
* back out and re-fingerprinting it would round-trip trusted-in-hand data for no
|
|
105
|
+
* gain, and the closure re-eval on restart re-derives the same bodies durably.
|
|
106
|
+
* The body still runs in the parent's sandbox (in-process today; a stricter
|
|
107
|
+
* per-body boundary is the deferred, opt-in SandboxBoundary case).
|
|
108
|
+
*/
|
|
109
|
+
export function createInMemorySpawnSuspendableChild(opts) {
|
|
110
|
+
return async ({ definitionRef, childRunId, input, parentRunId, parentStepId, signal, depth, maxChildSpawnDepth, resumeFromEvents, }, onEvent, credentialMaterial) => {
|
|
111
|
+
if (signal.aborted) {
|
|
112
|
+
throw abortError(signal);
|
|
113
|
+
}
|
|
114
|
+
const definition = opts.bodies.get(definitionRef);
|
|
115
|
+
if (definition === undefined) {
|
|
116
|
+
throw new Error(`workflow-runtime: source-ref spawn-child has no in-memory onTrigger ` +
|
|
117
|
+
`body for ${JSON.stringify(definitionRef)}; the parent's closure ` +
|
|
118
|
+
`re-eval should have extracted every inline body`);
|
|
119
|
+
}
|
|
120
|
+
if (signal.aborted) {
|
|
121
|
+
throw abortError(signal);
|
|
111
122
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
// The envelope schema enforces the structural shape the workflow
|
|
126
|
-
// body and state machine consume; the discriminated narrow over
|
|
127
|
-
// every `Primitive` variant lives downstream (the runtime body
|
|
128
|
-
// walks the steps and dispatches per-kind). Re-deriving the
|
|
129
|
-
// primitive narrow here would duplicate `defineWorkflow`'s
|
|
130
|
-
// validation, and the workflow-kind handler already enforced the
|
|
131
|
-
// same envelope at push time.
|
|
132
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- WorkflowDefinition's primitive union is narrowed downstream by the runtime body; the envelope schema enforces the structural shape this adapter cares about
|
|
133
|
-
return validated;
|
|
123
|
+
return opts.runSuspendableChild({
|
|
124
|
+
definition,
|
|
125
|
+
definitionRef,
|
|
126
|
+
childRunId,
|
|
127
|
+
input,
|
|
128
|
+
parentRunId,
|
|
129
|
+
parentStepId,
|
|
130
|
+
signal,
|
|
131
|
+
depth,
|
|
132
|
+
maxChildSpawnDepth,
|
|
133
|
+
...(resumeFromEvents !== undefined ? { resumeFromEvents } : {}),
|
|
134
|
+
}, onEvent, credentialMaterial);
|
|
135
|
+
};
|
|
134
136
|
}
|
|
135
137
|
/**
|
|
136
138
|
* Construct the rejection used when `signal.aborted` short-circuits.
|
|
@@ -144,9 +146,3 @@ function abortError(signal) {
|
|
|
144
146
|
return reason;
|
|
145
147
|
return new DOMException("aborted", "AbortError");
|
|
146
148
|
}
|
|
147
|
-
function isErrnoNotFound(cause) {
|
|
148
|
-
if (cause === null || typeof cause !== "object")
|
|
149
|
-
return false;
|
|
150
|
-
const code = cause.code;
|
|
151
|
-
return code === "ENOENT";
|
|
152
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Agent, type AgentDefinition, type BaseEnv } from "@intx/agent";
|
|
2
|
-
import type { InferenceEvent, InferenceSource } from "@intx/types/runtime";
|
|
2
|
+
import type { InboundMessage, InferenceEvent, InferenceSource, MailPartReader } from "@intx/types/runtime";
|
|
3
3
|
import type { StepInvokeRequest, StepInvoker, WorkflowAuthorizeFn } from "@intx/workflow";
|
|
4
|
-
import type { WarmAgentCache } from "../child/warm-agent-cache.js";
|
|
4
|
+
import type { WarmAgentCache, WarmReplyDrive } from "../child/warm-agent-cache.js";
|
|
5
5
|
/**
|
|
6
6
|
* Per-step env contributions the caller of the adapter owns.
|
|
7
7
|
*
|
|
@@ -104,6 +104,56 @@ export interface WorkflowStepInvokerOpts {
|
|
|
104
104
|
* has no cross-run conversation to mirror.
|
|
105
105
|
*/
|
|
106
106
|
onRunBoundary?: (key: string) => Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Seed hook for the warm path (design §3c threading). When supplied, the
|
|
109
|
+
* adapter calls it before `agent.send` on every message whose delivered
|
|
110
|
+
* input is a mail-derived `InboundMessage`, passing the step identity
|
|
111
|
+
* (`authzContext.stepId`, the same key `onRunBoundary` and the warm cache
|
|
112
|
+
* use) and that message. The sidecar wires this to the warm agent's
|
|
113
|
+
* durable conversation store, which routes the message onto the connector
|
|
114
|
+
* thread (seeding threadRoot / lastMessageId / replyTo) so the reply path
|
|
115
|
+
* can compose a threaded reply. Awaited before the send so the thread
|
|
116
|
+
* state is committed and durably flushed before the reply is produced; a
|
|
117
|
+
* seed failure surfaces by rejecting the step.
|
|
118
|
+
*
|
|
119
|
+
* Only mail-derived inbound messages seed: an approval-resume inbound
|
|
120
|
+
* carries a synthetic sender and a correlation id, and a synthesized
|
|
121
|
+
* string input is not a message, so neither advances the connector
|
|
122
|
+
* thread. Omitted on the cold path, which has no durable connector state
|
|
123
|
+
* to seed.
|
|
124
|
+
*/
|
|
125
|
+
seedInbound?: (key: string, message: InboundMessage) => Promise<void>;
|
|
126
|
+
/**
|
|
127
|
+
* Connector reply-drain hook for the warm path (design §3c). When supplied,
|
|
128
|
+
* the adapter invokes it ONCE -- at the warm agent's first-message build --
|
|
129
|
+
* with the step identity (`authzContext.stepId`, the same key the warm
|
|
130
|
+
* cache, seed, and run-boundary hooks use) and the agent's lifetime event
|
|
131
|
+
* stream. The sidecar wires this to the shared connector reply drain: on
|
|
132
|
+
* every `connector.reply` the agent emits, the drain composes a threaded
|
|
133
|
+
* reply from the durable store's connector thread and sends it through the
|
|
134
|
+
* supervisor-backed outbound bridge, then advances the thread from the send
|
|
135
|
+
* receipt.
|
|
136
|
+
*
|
|
137
|
+
* The returned drive handle exposes the drain's lifetime `done` promise --
|
|
138
|
+
* which settles when the agent's stream ends at eviction, folded into the
|
|
139
|
+
* warm entry's event-forward promise so the cache drains the reply loop
|
|
140
|
+
* alongside the observability forwarder -- plus the per-turn settle barrier
|
|
141
|
+
* (`replySeq` / `waitForReplyAfter`) the warm step gates each reply turn on,
|
|
142
|
+
* so the run parks only after the reply is durably sent.
|
|
143
|
+
*
|
|
144
|
+
* Omitted on the cold path (a torn-down per-step agent has no cross-message
|
|
145
|
+
* connector thread) and whenever the deployment is not warm-kept.
|
|
146
|
+
*/
|
|
147
|
+
driveReplies?: (key: string, stream: ReturnType<Agent["stream"]>) => WarmReplyDrive;
|
|
148
|
+
/**
|
|
149
|
+
* Reader for the run's inbound-mail parts. When the step input is a decoded
|
|
150
|
+
* `Mail`, the adapter resolves each part's `ref` to its committed bytes
|
|
151
|
+
* through this reader and delivers a real `InboundMessage` (text and/or
|
|
152
|
+
* attachments) to `agent.send`. Supplied by the run child for the top-level
|
|
153
|
+
* run's steps; absent for body steps, where a part whose bytes must be read
|
|
154
|
+
* is refused loudly rather than silently flattened to text.
|
|
155
|
+
*/
|
|
156
|
+
mailPartReader?: MailPartReader;
|
|
107
157
|
}
|
|
108
158
|
/**
|
|
109
159
|
* Construct the production `WorkflowRuntimeEnv.StepInvoker` adapter.
|