@deepseek-ai/dsh-workflow-worker-thread 0.0.1-rc.3
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/LICENSE +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +124 -0
- package/README.zh.md +124 -0
- package/lib/index.js +896 -0
- package/lib/invariant.js +23 -0
- package/lib/types/host.d.ts +151 -0
- package/lib/types/index.d.ts +58 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/meta.d.ts +19 -0
- package/lib/types/protocol.d.ts +132 -0
- package/lib/types/realm.d.ts +39 -0
- package/lib/types/runtime.d.ts +109 -0
- package/lib/types/session.d.ts +33 -0
- package/lib/types/types.d.ts +88 -0
- package/lib/types/worker.d.ts +8 -0
- package/lib/worker.cjs +783 -0
- package/package.json +69 -0
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-workflow-worker-thread`.
|
|
4
|
+
* @module @deepseek-ai/dsh-workflow-worker-thread/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-workflow-worker-thread";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "workflow-worker-thread-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: this process-boundary implementation exposes no same-process event relation;
|
|
13
|
+
* worker protocol and built-worker tests cover it.
|
|
14
|
+
*/
|
|
15
|
+
const install = () => {};
|
|
16
|
+
/**
|
|
17
|
+
* Register this package's invariant companion.
|
|
18
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
19
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
20
|
+
*/
|
|
21
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
22
|
+
//#endregion
|
|
23
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host side of one workflow run. The first worker result, unexpected death, or
|
|
3
|
+
* cancellation-grace expiry owns settlement and closes message admission.
|
|
4
|
+
* Pending starts share one abort signal; published children share idempotent
|
|
5
|
+
* cleanup, and quiescence waits for both while synthesizing any missing end events.
|
|
6
|
+
* @module @deepseek-ai/dsh-workflow-worker-thread/host
|
|
7
|
+
*/
|
|
8
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
9
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
10
|
+
import type SubagentRuntime from '@deepseek-ai/dsh-subagent';
|
|
11
|
+
import type { WorkflowMeta, WorkflowResult, WorkflowRun, WorkflowRunId } from '@deepseek-ai/dsh-workflow';
|
|
12
|
+
import type { ExecutionObserver } from './runtime.ts';
|
|
13
|
+
import type { WorkerInit } from './types.ts';
|
|
14
|
+
/**
|
|
15
|
+
* One live worker-engine run — the seam's {@link WorkflowRun}, returned by
|
|
16
|
+
* `start()` directly. Owns the Worker, the child registry, and the result
|
|
17
|
+
* settlement; `result` never rejects. `meta` is trusted same-process data
|
|
18
|
+
* borrowed as immutable by the handle and lifecycle events. The holder-bound
|
|
19
|
+
* SubagentRuntime handle is captured before the
|
|
20
|
+
* engine returns this run, so unloading the engine removes only the ability to
|
|
21
|
+
* start another workflow; this run can still start and clean up its children.
|
|
22
|
+
*/
|
|
23
|
+
export declare class WorkerRun implements WorkflowRun {
|
|
24
|
+
private readonly ctx;
|
|
25
|
+
private readonly subagents;
|
|
26
|
+
readonly id: WorkflowRunId;
|
|
27
|
+
readonly meta: WorkflowMeta;
|
|
28
|
+
private readonly parent;
|
|
29
|
+
private readonly provider;
|
|
30
|
+
private readonly disposeGraceMs;
|
|
31
|
+
private readonly observer;
|
|
32
|
+
/** Settles exactly once with the run's outcome; never rejects. */
|
|
33
|
+
readonly result: Promise<WorkflowResult>;
|
|
34
|
+
private settleResolve;
|
|
35
|
+
private settled;
|
|
36
|
+
/** A Result/death/grace outcome atomically won before teardown callbacks. */
|
|
37
|
+
private terminalClaimed;
|
|
38
|
+
/** The first death signal closes worker-message admission and owns failure-time cleanup. */
|
|
39
|
+
private workerDeathObserved;
|
|
40
|
+
private cancelReason;
|
|
41
|
+
private graceTimer;
|
|
42
|
+
private readonly worker;
|
|
43
|
+
/** Set on `exit`: the thread is gone, so posting has nowhere to go. */
|
|
44
|
+
private workerGone;
|
|
45
|
+
/** Accepted `child-start` messages — the terminate-path `agentsStarted` (see module doc). */
|
|
46
|
+
private hostStarted;
|
|
47
|
+
/** Published children by callId; an entry leaves only after disposal settles. */
|
|
48
|
+
private readonly children;
|
|
49
|
+
/** Provider starts that have not yet fulfilled or rejected. */
|
|
50
|
+
private readonly pendingStarts;
|
|
51
|
+
/** Started-but-not-ended agents by seq — the pairing ledger the HOST guarantees (see {@link endAgent}). */
|
|
52
|
+
private readonly liveAgents;
|
|
53
|
+
private readonly quiescenceWaiters;
|
|
54
|
+
/** The per-run abort fanout every child start request carries. */
|
|
55
|
+
private readonly controller;
|
|
56
|
+
/** External start signal and the exact callback installed on it, retained only until first settle/teardown. */
|
|
57
|
+
private inputSignal;
|
|
58
|
+
private inputSignalAbort;
|
|
59
|
+
private disposed;
|
|
60
|
+
constructor(ctx: Context, subagents: SubagentRuntime, id: WorkflowRunId, meta: WorkflowMeta, parent: Agent, init: WorkerInit, provider: string, disposeGraceMs: number, observer: ExecutionObserver, signal: AbortSignal | undefined);
|
|
61
|
+
/**
|
|
62
|
+
* Cancel the run: the worker is told (its hooks start throwing and the
|
|
63
|
+
* script dies at its next await), the required signal shared by every child
|
|
64
|
+
* start is aborted, and the grace timer
|
|
65
|
+
* arms: a run still unsettled `disposeGraceMs` later force-settles
|
|
66
|
+
* `cancelled` and its worker is TERMINATED. Idempotent; the first reason
|
|
67
|
+
* wins.
|
|
68
|
+
* @param reason - human-readable cause (default `'workflow cancelled'`).
|
|
69
|
+
*/
|
|
70
|
+
cancel(reason?: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Cancel + bounded settle + termination. Host-drives every registered
|
|
73
|
+
* child's disposal IMMEDIATELY — a wedged worker can relay no dispose RPC,
|
|
74
|
+
* and deferring child teardown to the post-terminate reap would spend the
|
|
75
|
+
* whole grace waiting for a quiescence that cannot start, then return with
|
|
76
|
+
* the disposals still in flight — so child disposal overlaps the same
|
|
77
|
+
* grace the worker gets to settle (the worker's own dispose RPCs join the
|
|
78
|
+
* shared per-child disposal). Waits (at most the grace) for the result and
|
|
79
|
+
* child quiescence, then terminates the worker unconditionally — the
|
|
80
|
+
* thread never outlives its run — and reaps whatever children remain
|
|
81
|
+
* (their disposal is contained, not awaited past the grace, the same
|
|
82
|
+
* abandonment the seam documents for a slow-disposing child). Idempotent;
|
|
83
|
+
* safe on every path.
|
|
84
|
+
* @returns resolves when the run's resources are released or abandoned.
|
|
85
|
+
*/
|
|
86
|
+
dispose(): Promise<void>;
|
|
87
|
+
/** Post one message to the worker (payload looked up from the tag's map entry), tolerating a thread that is already gone. */
|
|
88
|
+
private post;
|
|
89
|
+
private onMessage;
|
|
90
|
+
/** Why a ready provider result may no longer be admitted to the worker. */
|
|
91
|
+
private childAdmissionFailure;
|
|
92
|
+
private onChildStart;
|
|
93
|
+
/** Await one provider-owned startup transaction and publish only while admitted. */
|
|
94
|
+
private startChild;
|
|
95
|
+
private onChildDispose;
|
|
96
|
+
/**
|
|
97
|
+
* Start (or join) one registered child's disposal; the registry entry
|
|
98
|
+
* leaves when it settles. Memoized per callId: the worker's dispose RPC,
|
|
99
|
+
* the dispose() host drive, and the reap can all land on the same child —
|
|
100
|
+
* the child's `dispose()` runs once and every caller awaits that one
|
|
101
|
+
* settlement. A rejection is contained (the subagent seam's dispose() is
|
|
102
|
+
* not supposed to reject, but a backend that does anyway must not break
|
|
103
|
+
* quiescence): logged, and the child still leaves the registry.
|
|
104
|
+
* @param callId - the child's registry key.
|
|
105
|
+
* @param record - the registered child (the caller looked it up).
|
|
106
|
+
* @returns resolves when the disposal settled either way; never rejects.
|
|
107
|
+
*/
|
|
108
|
+
private disposeChild;
|
|
109
|
+
/** Drop a child record and release quiescence waiters when all work ends. */
|
|
110
|
+
private finishChild;
|
|
111
|
+
/** Retire one provider startup transaction. */
|
|
112
|
+
private finishPendingStart;
|
|
113
|
+
/** Release waiters only after both pending starts and published children end. */
|
|
114
|
+
private notifyChildQuiescence;
|
|
115
|
+
/** Resolves once every pending start and published child has reached quiescence. */
|
|
116
|
+
private childQuiescence;
|
|
117
|
+
/** Abort + dispose every registered child (worker death / final teardown); disposal is contained, not awaited. */
|
|
118
|
+
private reapChildren;
|
|
119
|
+
/** Abort the one canonical signal shared by pending and published children. */
|
|
120
|
+
private abortChildren;
|
|
121
|
+
private onResult;
|
|
122
|
+
/** Process an error/messageerror/exit signal; `exit` also performs the final disposal sweep. */
|
|
123
|
+
private onWorkerDeath;
|
|
124
|
+
/**
|
|
125
|
+
* The single agent-end emission gate: forwards `end` iff its start is still
|
|
126
|
+
* unpaired in the ledger, so every forwarded `workflow/agent-start` gets
|
|
127
|
+
* EXACTLY one `workflow/agent-end` — the worker's own report where it can
|
|
128
|
+
* speak, a host-synthesized one where it cannot ({@link endStrandedAgents}).
|
|
129
|
+
* @param end - the settlement to emit (worker-reported or synthesized).
|
|
130
|
+
*/
|
|
131
|
+
private endAgent;
|
|
132
|
+
/**
|
|
133
|
+
* Synthesize the missing `agent-end` for every started-but-unpaired agent,
|
|
134
|
+
* outcome `'cancelled'`: the reap cancels every child, and a real
|
|
135
|
+
* settlement racing the force-settle loses to that already-started external
|
|
136
|
+
* cancellation. The atomic terminal boundaries in {@link onResult} and
|
|
137
|
+
* {@link onWorkerDeath} deliberately exclude teardown callbacks as contenders.
|
|
138
|
+
* Called where the worker can no longer speak (the grace force-settle,
|
|
139
|
+
* worker death, physical exit). When grace/death is the terminal source it
|
|
140
|
+
* runs before settleResult, so already-known pairs precede `workflow/end`;
|
|
141
|
+
* after an earlier Result, exit cleanup may close a survivor afterward.
|
|
142
|
+
* The ledger preserves exactly-once pairing in both orders.
|
|
143
|
+
*/
|
|
144
|
+
private endStrandedAgents;
|
|
145
|
+
private cancelledResult;
|
|
146
|
+
/** Remove the exact abort callback installed on the caller's start signal. */
|
|
147
|
+
private detachInputSignal;
|
|
148
|
+
/** First settle wins; disarms the grace timer and releases the caller signal. */
|
|
149
|
+
private settleResult;
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=host.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker-thread workflow engine. Each run executes its model-written script in
|
|
3
|
+
* an escapable vm context on a fresh worker and bridges `agent()` calls to host
|
|
4
|
+
* subagents. The thread prevents synchronous script work from blocking the host
|
|
5
|
+
* and permits forced termination, but it is containment rather than a security boundary.
|
|
6
|
+
* @module @deepseek-ai/dsh-workflow-worker-thread
|
|
7
|
+
*/
|
|
8
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
9
|
+
import z from '@deepseek-ai/schemastery';
|
|
10
|
+
import WorkflowEngine from '@deepseek-ai/dsh-workflow';
|
|
11
|
+
import type { WorkflowRun, WorkflowStartRequest } from '@deepseek-ai/dsh-workflow';
|
|
12
|
+
export { validateMeta } from './meta.ts';
|
|
13
|
+
export { materializeFromRealm, MaterializeError } from './realm.ts';
|
|
14
|
+
export type { ChildHandle, ChildPort, ChildResult, ChildStartRequest, WorkerInit, WorkerLimits, } from './types.ts';
|
|
15
|
+
/** Plugin config (all optional — `static Config` supplies the defaults). */
|
|
16
|
+
export interface Config {
|
|
17
|
+
/** The `ctx.subagents` provider children run on (default `spawn`). */
|
|
18
|
+
provider?: string;
|
|
19
|
+
/** Concurrent `agent()` ceiling; `0` (the default) auto-resolves to `min(16, max(1, cores - 2))`. */
|
|
20
|
+
maxConcurrentAgents?: number;
|
|
21
|
+
/** Total `agent()` calls one run may start — the runaway-loop backstop (default 1000). */
|
|
22
|
+
maxTotalAgents?: number;
|
|
23
|
+
/** Items accepted by a single `parallel()`/`pipeline()` call (default 4096). */
|
|
24
|
+
maxItemsPerCall?: number;
|
|
25
|
+
/** vm timeout for the script's initial synchronous slice, inside the worker (default 5000 ms). */
|
|
26
|
+
syncTimeoutMs?: number;
|
|
27
|
+
/**
|
|
28
|
+
* How long after a cancellation an unsettled script may keep running before
|
|
29
|
+
* the run force-settles `cancelled` and its worker is TERMINATED (default
|
|
30
|
+
* 5000 ms); also bounds `dispose()`.
|
|
31
|
+
*/
|
|
32
|
+
disposeGraceMs?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The worker-thread engine service. `start()` validates the script up front
|
|
36
|
+
* (meta + a host-side body parse) and returns a {@link WorkflowRun} whose
|
|
37
|
+
* `result` never rejects; the `workflow/*` events fire around the run per
|
|
38
|
+
* the seam contract.
|
|
39
|
+
*/
|
|
40
|
+
declare class WorkerThreadWorkflowEngine extends WorkflowEngine {
|
|
41
|
+
static inject: string[];
|
|
42
|
+
static Config: z<Config>;
|
|
43
|
+
private readonly config;
|
|
44
|
+
constructor(ctx: Context, config: Config);
|
|
45
|
+
/**
|
|
46
|
+
* Validate and execute a workflow script in a fresh worker thread. Throws
|
|
47
|
+
* {@link WorkflowError} synchronously (`META_INVALID` for a malformed meta
|
|
48
|
+
* block, `SCRIPT_PARSE` for a body that does not compile) for a request
|
|
49
|
+
* that cannot begin; once a run is returned, every failure resolves through
|
|
50
|
+
* `result.stopReason` instead.
|
|
51
|
+
* @param request - the script body, its meta data and `args`, the parent
|
|
52
|
+
* agent, and an optional cancel signal.
|
|
53
|
+
* @returns the live run (its `result` resolves when the script settles).
|
|
54
|
+
*/
|
|
55
|
+
start(request: WorkflowStartRequest): WorkflowRun;
|
|
56
|
+
}
|
|
57
|
+
export default WorkerThreadWorkflowEngine;
|
|
58
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-workflow-worker-thread`.
|
|
3
|
+
* @module @deepseek-ai/dsh-workflow-worker-thread/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "workflow-worker-thread-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta validation checks caller-provided DATA against the {@link WorkflowMeta}
|
|
3
|
+
* contract and rejects every violation by name. Meta arrives as schema-checked
|
|
4
|
+
* JSON data, never evaluated script text; evaluating it on the host could run getters outside the
|
|
5
|
+
* worker timeout that exists to isolate model-written code.
|
|
6
|
+
* @module @deepseek-ai/dsh-workflow-worker-thread/meta
|
|
7
|
+
*/
|
|
8
|
+
import type { WorkflowMeta } from '@deepseek-ai/dsh-workflow';
|
|
9
|
+
/**
|
|
10
|
+
* Validate a caller-provided meta value against the {@link WorkflowMeta}
|
|
11
|
+
* contract. Throws `META_INVALID` naming every violation (unknown fields,
|
|
12
|
+
* missing/mistyped `name`/`description`, malformed `phases`); the returned
|
|
13
|
+
* meta is a NORMALIZED copy built from the validated fields, so the engine
|
|
14
|
+
* never aliases the caller's object.
|
|
15
|
+
* @param value - the meta data from the start request (plain JSON by the seam contract).
|
|
16
|
+
* @returns the validated, normalized meta block.
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateMeta(value: unknown): WorkflowMeta;
|
|
19
|
+
//# sourceMappingURL=meta.d.ts.map
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host⇄worker wire protocol: one string-valued enum of message tags per direction, a
|
|
3
|
+
* payload map giving each tag its parameters (the single source of truth), and the message
|
|
4
|
+
* unions derived from them. Payloads are plain JSON by construction for structured clone. Both
|
|
5
|
+
* directions are closed engine protocols whose receivers use `assertNever`; generic typed senders
|
|
6
|
+
* make tag/payload mismatches compile-time errors rather than silently skipped messages.
|
|
7
|
+
* @module @deepseek-ai/dsh-workflow-worker-thread/protocol
|
|
8
|
+
*/
|
|
9
|
+
import type { WorkflowAgentEndInfo, WorkflowAgentInfo, WorkflowResult } from '@deepseek-ai/dsh-workflow';
|
|
10
|
+
import type { ChildResult, ChildStartRequest } from './types.ts';
|
|
11
|
+
/** Message tags the worker sends the host (the wire values are the tag strings). */
|
|
12
|
+
export declare enum WorkerToHostType {
|
|
13
|
+
/** The startup handshake: the session is listening and awaits {@link HostToWorkerType.Go}. */
|
|
14
|
+
Ready = "ready",
|
|
15
|
+
/** Observer narration: a `phase(title)` call. */
|
|
16
|
+
Phase = "phase",
|
|
17
|
+
/** Observer narration: a `log(message)` call. */
|
|
18
|
+
Log = "log",
|
|
19
|
+
/** Observer lifecycle: one `agent()` call started a child. */
|
|
20
|
+
AgentStart = "agent-start",
|
|
21
|
+
/** Observer lifecycle: one `agent()` call settled. */
|
|
22
|
+
AgentEnd = "agent-end",
|
|
23
|
+
/** Child RPC: start a child on the host (answered by ChildStarted or ChildStartError). */
|
|
24
|
+
ChildStart = "child-start",
|
|
25
|
+
/** Child RPC: dispose a started child (answered by ChildDisposed). */
|
|
26
|
+
ChildDispose = "child-dispose",
|
|
27
|
+
/** The run's single terminal result. */
|
|
28
|
+
Result = "result"
|
|
29
|
+
}
|
|
30
|
+
/** The payload each worker→host tag carries. */
|
|
31
|
+
export interface WorkerToHostPayloads {
|
|
32
|
+
/** Ready carries nothing. */
|
|
33
|
+
[WorkerToHostType.Ready]: Record<never, never>;
|
|
34
|
+
/** The phase title, verbatim. */
|
|
35
|
+
[WorkerToHostType.Phase]: {
|
|
36
|
+
title: string;
|
|
37
|
+
};
|
|
38
|
+
/** The logged message, verbatim. */
|
|
39
|
+
[WorkerToHostType.Log]: {
|
|
40
|
+
message: string;
|
|
41
|
+
};
|
|
42
|
+
/** The call's sequence number, label, phase, and child id. */
|
|
43
|
+
[WorkerToHostType.AgentStart]: {
|
|
44
|
+
info: WorkflowAgentInfo;
|
|
45
|
+
};
|
|
46
|
+
/** The call identity plus its outcome. */
|
|
47
|
+
[WorkerToHostType.AgentEnd]: {
|
|
48
|
+
info: WorkflowAgentEndInfo;
|
|
49
|
+
};
|
|
50
|
+
/** The RPC correlation id and the prompt plus validated options. */
|
|
51
|
+
[WorkerToHostType.ChildStart]: {
|
|
52
|
+
callId: number;
|
|
53
|
+
request: ChildStartRequest;
|
|
54
|
+
};
|
|
55
|
+
/** The RPC correlation id of the child to dispose. */
|
|
56
|
+
[WorkerToHostType.ChildDispose]: {
|
|
57
|
+
callId: number;
|
|
58
|
+
};
|
|
59
|
+
/** The run's terminal outcome. */
|
|
60
|
+
[WorkerToHostType.Result]: {
|
|
61
|
+
result: WorkflowResult;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Message tags the host sends the worker (the wire values are the tag strings). */
|
|
65
|
+
export declare enum HostToWorkerType {
|
|
66
|
+
/** Releases the startup gate: run the script body. */
|
|
67
|
+
Go = "go",
|
|
68
|
+
/** Cancel the run: hooks start throwing and the script dies at its next await. */
|
|
69
|
+
Cancel = "cancel",
|
|
70
|
+
/** Child RPC reply: the provider fulfilled with a published run (exactly one start reply per ChildStart). */
|
|
71
|
+
ChildStarted = "child-started",
|
|
72
|
+
/** Child RPC reply: the provider's asynchronous start failed. */
|
|
73
|
+
ChildStartError = "child-start-error",
|
|
74
|
+
/** Child RPC: a started child's result RESOLVED (its JSON projection). */
|
|
75
|
+
ChildSettled = "child-settled",
|
|
76
|
+
/** Child RPC: a started child's result REJECTED (an infrastructure fault, rendered). */
|
|
77
|
+
ChildFailed = "child-failed",
|
|
78
|
+
/** Child RPC reply: a requested disposal completed. */
|
|
79
|
+
ChildDisposed = "child-disposed"
|
|
80
|
+
}
|
|
81
|
+
/** The payload each host→worker tag carries. */
|
|
82
|
+
export interface HostToWorkerPayloads {
|
|
83
|
+
/** Go carries nothing. */
|
|
84
|
+
[HostToWorkerType.Go]: Record<never, never>;
|
|
85
|
+
/** The cancel reason, canonical for the whole run. */
|
|
86
|
+
[HostToWorkerType.Cancel]: {
|
|
87
|
+
reason: string;
|
|
88
|
+
};
|
|
89
|
+
/** The RPC correlation id and the child agent's id (minted by the subagent seam). */
|
|
90
|
+
[HostToWorkerType.ChildStarted]: {
|
|
91
|
+
callId: number;
|
|
92
|
+
childId: string;
|
|
93
|
+
};
|
|
94
|
+
/** The RPC correlation id and the rendered start failure. */
|
|
95
|
+
[HostToWorkerType.ChildStartError]: {
|
|
96
|
+
callId: number;
|
|
97
|
+
rendered: string;
|
|
98
|
+
};
|
|
99
|
+
/** The RPC correlation id and the child's terminal result projection. */
|
|
100
|
+
[HostToWorkerType.ChildSettled]: {
|
|
101
|
+
callId: number;
|
|
102
|
+
result: ChildResult;
|
|
103
|
+
};
|
|
104
|
+
/** The RPC correlation id and the rendered infrastructure fault. */
|
|
105
|
+
[HostToWorkerType.ChildFailed]: {
|
|
106
|
+
callId: number;
|
|
107
|
+
rendered: string;
|
|
108
|
+
};
|
|
109
|
+
/** The RPC correlation id of the completed disposal. */
|
|
110
|
+
[HostToWorkerType.ChildDisposed]: {
|
|
111
|
+
callId: number;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* One worker→host message of tag `T`; unparameterized, the closed union over
|
|
116
|
+
* every tag (a discriminated union — `switch` on `type` narrows).
|
|
117
|
+
*/
|
|
118
|
+
export type WorkerToHostMessage<T extends WorkerToHostType = WorkerToHostType> = {
|
|
119
|
+
[K in T]: {
|
|
120
|
+
type: K;
|
|
121
|
+
} & WorkerToHostPayloads[K];
|
|
122
|
+
}[T];
|
|
123
|
+
/**
|
|
124
|
+
* One host→worker message of tag `T`; unparameterized, the closed union over
|
|
125
|
+
* every tag (a discriminated union — `switch` on `type` narrows).
|
|
126
|
+
*/
|
|
127
|
+
export type HostToWorkerMessage<T extends HostToWorkerType = HostToWorkerType> = {
|
|
128
|
+
[K in T]: {
|
|
129
|
+
type: K;
|
|
130
|
+
} & HostToWorkerPayloads[K];
|
|
131
|
+
}[T];
|
|
132
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Materializes values leaving the script vm into plain JSON before they cross the worker
|
|
3
|
+
* boundary, and renders thrown script values without rejecting the run. The walk rejects
|
|
4
|
+
* values that JSON cannot preserve but trusts model-written workflow scripts: getters and proxy traps may
|
|
5
|
+
* run, and the vm is not a security boundary. The worker provides host-loop isolation and
|
|
6
|
+
* forced termination, not hostile-value containment. See
|
|
7
|
+
* .agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md for the isolation rationale.
|
|
8
|
+
* @module @deepseek-ai/dsh-workflow-worker-thread/realm
|
|
9
|
+
*/
|
|
10
|
+
/** Thrown by {@link materializeFromRealm}; the caller wraps it into the right `WorkflowError` code. */
|
|
11
|
+
export declare class MaterializeError extends Error {
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly reason: string;
|
|
14
|
+
constructor(path: string, reason: string);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Render a thrown value to failure text without ever throwing: prefer the
|
|
18
|
+
* `stack` (host or realm — a realm error's `stack` is a plain string read),
|
|
19
|
+
* fall back to `message`, then `String()`. Reading those properties MAY run
|
|
20
|
+
* script code (a getter, `toString`) — accepted under the module's trust
|
|
21
|
+
* premise; if that code itself throws, a fixed label is returned instead.
|
|
22
|
+
* @param error - any value thrown in the host or worker realm.
|
|
23
|
+
* @returns human-readable text for the failure report; prefers the stack.
|
|
24
|
+
*/
|
|
25
|
+
export declare function renderThrown(error: unknown): string;
|
|
26
|
+
/**
|
|
27
|
+
* Copy `value` (typically from the vm realm) into plain host JSON data. Root `undefined` is
|
|
28
|
+
* returned unchanged; nested `undefined` and values JSON cannot represent losslessly fail
|
|
29
|
+
* with the offending path. Property accessors run normally, and a throwing read is wrapped
|
|
30
|
+
* with its rendered failure.
|
|
31
|
+
*
|
|
32
|
+
* @param value - the realm value to materialize.
|
|
33
|
+
* @param root - the path label for the root value (error messages).
|
|
34
|
+
* @returns the host-realm copy (plain objects/arrays/scalars only).
|
|
35
|
+
* @throws {@link MaterializeError} for unsupported values, cycles, sparse arrays, exotic
|
|
36
|
+
* prototypes, or property reads that throw.
|
|
37
|
+
*/
|
|
38
|
+
export declare function materializeFromRealm(value: unknown, root?: string): unknown;
|
|
39
|
+
//# sourceMappingURL=realm.d.ts.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-run worker-side vm hooks, child RPC, concurrency/caps, cancellation, and result serialization; it
|
|
3
|
+
* never touches Cordis. Script values leaving the realm are materialized as plain JSON before
|
|
4
|
+
* messaging. Values entering the trusted model-written realm are passed directly; `args` alone is
|
|
5
|
+
* cloned so script mutation cannot alter initialization data. See `./realm.ts` for the trust model.
|
|
6
|
+
*
|
|
7
|
+
* Fatal workflow errors—bad hook arguments, unsupported schemas/options, caps, start failures, and
|
|
8
|
+
* cancellation—propagate through combinators. Only child failures and ordinary stage errors become
|
|
9
|
+
* per-item nulls. Every returned promise has a rejection consumer so dropped script promises cannot
|
|
10
|
+
* kill the worker. A cancelled script that never settles emits nothing; the host force-settles the
|
|
11
|
+
* run within grace and terminates the thread.
|
|
12
|
+
* @module @deepseek-ai/dsh-workflow-worker-thread/runtime
|
|
13
|
+
*/
|
|
14
|
+
import type { WorkflowAgentEndInfo, WorkflowAgentInfo, WorkflowMeta, WorkflowResult } from '@deepseek-ai/dsh-workflow';
|
|
15
|
+
import type { ChildPort, WorkerLimits } from './types.ts';
|
|
16
|
+
/** The observers the execution reports progress through (the session posts them to the host). */
|
|
17
|
+
export interface ExecutionObserver {
|
|
18
|
+
phase(title: string): void;
|
|
19
|
+
log(message: string): void;
|
|
20
|
+
agentStart(info: WorkflowAgentInfo): void;
|
|
21
|
+
agentEnd(info: WorkflowAgentEndInfo): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One live script execution inside the worker. Constructed per run by the
|
|
25
|
+
* session; `drive()` is called exactly once and NEVER rejects — every failure
|
|
26
|
+
* becomes a {@link WorkflowResult} with a non-`completed` stop reason. The
|
|
27
|
+
* host owns cancellation and cleanup of any dropped child work.
|
|
28
|
+
*/
|
|
29
|
+
export declare class WorkflowExecution {
|
|
30
|
+
private readonly limits;
|
|
31
|
+
private readonly observer;
|
|
32
|
+
private readonly children;
|
|
33
|
+
/** 1-based count of `agent()` calls started (the `agentsStarted` result field). */
|
|
34
|
+
private started;
|
|
35
|
+
private activeSlots;
|
|
36
|
+
private readonly slotWaiters;
|
|
37
|
+
private cancelReason;
|
|
38
|
+
private cancelError;
|
|
39
|
+
private currentPhase;
|
|
40
|
+
private readonly context;
|
|
41
|
+
private readonly compiled;
|
|
42
|
+
constructor(meta: WorkflowMeta, body: string, args: unknown, limits: WorkerLimits, observer: ExecutionObserver, children: ChildPort);
|
|
43
|
+
/**
|
|
44
|
+
* Whether the run has been cancelled. A METHOD, not an inline property
|
|
45
|
+
* read: `cancel()` mutates `cancelReason` concurrently (the session's
|
|
46
|
+
* message handler), and an inline read after an `await` gets narrowed by
|
|
47
|
+
* control flow into an always-false comparison.
|
|
48
|
+
*/
|
|
49
|
+
private isCancelled;
|
|
50
|
+
/**
|
|
51
|
+
* Shared hook entry guard: after {@link cancel}, EVERY hook throws
|
|
52
|
+
* `CANCELLED` at its next call — cancellation is the next HOOK boundary,
|
|
53
|
+
* not just the next `agent()`, so a script that caught one cancelled
|
|
54
|
+
* rejection cannot keep emitting progress through `phase`/`log` or enter a
|
|
55
|
+
* combinator.
|
|
56
|
+
*/
|
|
57
|
+
private throwIfCancelled;
|
|
58
|
+
/**
|
|
59
|
+
* Cancel the run: waiting `agent()` slots reject and every future hook call
|
|
60
|
+
* throws `CANCELLED` — the script dies at its next await. A script that
|
|
61
|
+
* never settles anyway (parked on a promise no hook owns) is the HOST's
|
|
62
|
+
* problem: its grace timer force-settles the run and terminates the
|
|
63
|
+
* worker. Idempotent; the first reason wins.
|
|
64
|
+
* @param reason - human-readable cause carried on the CANCELLED error. The
|
|
65
|
+
* host independently aborts the required signal shared by every child.
|
|
66
|
+
*/
|
|
67
|
+
cancel(reason: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Run the script to settlement. Resolves — never rejects — with the run's
|
|
70
|
+
* {@link WorkflowResult}: the materialized return value on `completed`, the
|
|
71
|
+
* failure message on `error`, and `cancelled` when the script died of
|
|
72
|
+
* cancellation. This method only chooses the result; the session publishes
|
|
73
|
+
* it and the host owns terminal child cancellation.
|
|
74
|
+
* @returns the settled outcome — this promise NEVER rejects (the seam's
|
|
75
|
+
* `result`-never-rejects contract); every failure maps to a variant.
|
|
76
|
+
*/
|
|
77
|
+
drive(): Promise<WorkflowResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Attach a no-op rejection consumer WITHOUT changing what the caller
|
|
80
|
+
* receives: if the script drops the promise (no await), cancellation cannot
|
|
81
|
+
* become an unhandled rejection (which would kill the worker thread); if
|
|
82
|
+
* the script does await it, it still observes the rejection.
|
|
83
|
+
*/
|
|
84
|
+
private contain;
|
|
85
|
+
private cancelledError;
|
|
86
|
+
/** Materialize the script's return value; violations become RESULT_UNSERIALIZABLE. */
|
|
87
|
+
private materializeResult;
|
|
88
|
+
/**
|
|
89
|
+
* Acquire one concurrency slot (FIFO). Cancellation rejects QUEUED waiters
|
|
90
|
+
* (see {@link cancel}); the callers guard their own entry and post-acquire
|
|
91
|
+
* windows, so no cancelled-precheck is duplicated here.
|
|
92
|
+
*/
|
|
93
|
+
private acquireSlot;
|
|
94
|
+
private releaseSlot;
|
|
95
|
+
/** The `agent(prompt, opts)` hook. */
|
|
96
|
+
private agent;
|
|
97
|
+
/** Materialize + validate the `agent()` options bag from the realm. */
|
|
98
|
+
private readAgentOptions;
|
|
99
|
+
/** The `parallel(thunks)` hook: each thunk caught → `null`; fatal errors propagate. */
|
|
100
|
+
private parallel;
|
|
101
|
+
/** The `pipeline(items, ...stages)` hook: per-item stage chains, NO cross-stage barrier. */
|
|
102
|
+
private pipeline;
|
|
103
|
+
private assertItemCap;
|
|
104
|
+
/** The `phase(title)` hook: sets the current label for subsequent `agent()` calls and notifies observers. */
|
|
105
|
+
private phase;
|
|
106
|
+
/** The `log(message)` hook: narration to observers. */
|
|
107
|
+
private log;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The worker-side half of the engine: {@link runWorkerSession} wires one MessagePort to one
|
|
3
|
+
* {@link WorkflowExecution} — hook progress and child starts go out as messages, run control
|
|
4
|
+
* and child lifecycle come back in — and posts the run's terminal result exactly once. Keeping it
|
|
5
|
+
* separate from `worker.ts` lets unit tests drive the session over a MessageChannel, because main
|
|
6
|
+
* process coverage cannot observe code inside a real Worker.
|
|
7
|
+
*
|
|
8
|
+
* The session announces ready and waits for `go`, so cancellation racing startup can prevent even
|
|
9
|
+
* the script's synchronous prefix. A cancel in place of `go` releases the gate into a cancelled
|
|
10
|
+
* drive without executing the body.
|
|
11
|
+
* @module @deepseek-ai/dsh-workflow-worker-thread/session
|
|
12
|
+
*/
|
|
13
|
+
import type { MessagePort } from 'node:worker_threads';
|
|
14
|
+
import type { WorkerInit } from './types.ts';
|
|
15
|
+
/**
|
|
16
|
+
* Narrow the nullable `parentPort` the bootstrap reads from
|
|
17
|
+
* `node:worker_threads`.
|
|
18
|
+
* @param port - `parentPort` as imported (null on the main thread).
|
|
19
|
+
* @returns the port, non-null.
|
|
20
|
+
*/
|
|
21
|
+
export declare function requireParentPort(port: MessagePort | null): MessagePort;
|
|
22
|
+
/**
|
|
23
|
+
* Run one workflow script to settlement against `port`, posting the terminal result message
|
|
24
|
+
* exactly once; resolves after that post (stray children may still be winding down through the
|
|
25
|
+
* port — the host owns their teardown and ultimately terminates the thread). It never rejects:
|
|
26
|
+
* constructor failure becomes an error result. Host pre-parse makes syntax failure here a likely
|
|
27
|
+
* Node-version skew, but the session still reports it instead of dying silently.
|
|
28
|
+
* @param port - the channel to the host (the real `parentPort`, or one side
|
|
29
|
+
* of an in-process `MessageChannel` in tests).
|
|
30
|
+
* @param init - the run payload the host provided as `workerData`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function runWorkerSession(port: MessagePort, init: WorkerInit): Promise<void>;
|
|
33
|
+
//# sourceMappingURL=session.d.ts.map
|