@zaalipro/dsh-workflows 0.1.0-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 +21 -0
- package/NOTICE.md +224 -0
- package/README.i18n.yaml +4 -0
- package/README.md +109 -0
- package/README.zh.md +109 -0
- package/cordis.patch.yml +10 -0
- package/docs/architecture.i18n.yaml +4 -0
- package/docs/architecture.md +177 -0
- package/docs/architecture.zh.md +177 -0
- package/docs/testing.i18n.yaml +4 -0
- package/docs/testing.md +125 -0
- package/docs/testing.zh.md +125 -0
- package/docs/user-guide.i18n.yaml +4 -0
- package/docs/user-guide.md +351 -0
- package/docs/user-guide.zh.md +351 -0
- package/lib/client-types/WorkflowMemberInspector.d.ts +28 -0
- package/lib/client-types/WorkflowMemberInspector.d.ts.map +1 -0
- package/lib/client-types/WorkflowMemberInspector.js +181 -0
- package/lib/client-types/WorkflowMemberInspector.js.map +1 -0
- package/lib/client-types/WorkflowRunPanel.d.ts +43 -0
- package/lib/client-types/WorkflowRunPanel.d.ts.map +1 -0
- package/lib/client-types/WorkflowRunPanel.js +130 -0
- package/lib/client-types/WorkflowRunPanel.js.map +1 -0
- package/lib/client-types/WorkflowsDashboard.d.ts +49 -0
- package/lib/client-types/WorkflowsDashboard.d.ts.map +1 -0
- package/lib/client-types/WorkflowsDashboard.js +942 -0
- package/lib/client-types/WorkflowsDashboard.js.map +1 -0
- package/lib/client-types/adapter.d.ts +60 -0
- package/lib/client-types/adapter.d.ts.map +1 -0
- package/lib/client-types/adapter.js +117 -0
- package/lib/client-types/adapter.js.map +1 -0
- package/lib/client-types/chat-renderer.d.ts +10 -0
- package/lib/client-types/chat-renderer.d.ts.map +1 -0
- package/lib/client-types/chat-renderer.js +11 -0
- package/lib/client-types/chat-renderer.js.map +1 -0
- package/lib/client-types/contract.d.ts +210 -0
- package/lib/client-types/contract.d.ts.map +1 -0
- package/lib/client-types/contract.js +37 -0
- package/lib/client-types/contract.js.map +1 -0
- package/lib/client-types/controller.d.ts +83 -0
- package/lib/client-types/controller.d.ts.map +1 -0
- package/lib/client-types/controller.js +705 -0
- package/lib/client-types/controller.js.map +1 -0
- package/lib/client-types/index.d.ts +19 -0
- package/lib/client-types/index.d.ts.map +1 -0
- package/lib/client-types/index.js +730 -0
- package/lib/client-types/index.js.map +1 -0
- package/lib/client-types/locales.d.ts +257 -0
- package/lib/client-types/locales.d.ts.map +1 -0
- package/lib/client-types/locales.js +270 -0
- package/lib/client-types/locales.js.map +1 -0
- package/lib/client-types/slot-components.d.ts +38 -0
- package/lib/client-types/slot-components.d.ts.map +1 -0
- package/lib/client-types/slot-components.js +22 -0
- package/lib/client-types/slot-components.js.map +1 -0
- package/lib/client-types/store.d.ts +69 -0
- package/lib/client-types/store.d.ts.map +1 -0
- package/lib/client-types/store.js +135 -0
- package/lib/client-types/store.js.map +1 -0
- package/lib/client-types/workflow-definition.d.ts +52 -0
- package/lib/client-types/workflow-definition.d.ts.map +1 -0
- package/lib/client-types/workflow-definition.js +135 -0
- package/lib/client-types/workflow-definition.js.map +1 -0
- package/lib/client.js +11064 -0
- package/lib/client.js.map +1 -0
- package/lib/compat-engine/index.js +1926 -0
- package/lib/compat-engine/index.js.map +1 -0
- package/lib/compat-engine/worker.cjs +1500 -0
- package/lib/compat-engine/worker.cjs.map +1 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +1709 -0
- package/lib/typert.remote-client.d.ts +57 -0
- package/lib/typert.remote-client.js +1662 -0
- package/lib/types/commands/aliases.d.ts +4 -0
- package/lib/types/commands/aliases.d.ts.map +1 -0
- package/lib/types/commands/aliases.js +25 -0
- package/lib/types/commands/aliases.js.map +1 -0
- package/lib/types/commands/index.d.ts +66 -0
- package/lib/types/commands/index.d.ts.map +1 -0
- package/lib/types/commands/index.js +550 -0
- package/lib/types/commands/index.js.map +1 -0
- package/lib/types/commands/parser.d.ts +17 -0
- package/lib/types/commands/parser.d.ts.map +1 -0
- package/lib/types/commands/parser.js +45 -0
- package/lib/types/commands/parser.js.map +1 -0
- package/lib/types/config.d.ts +71 -0
- package/lib/types/config.d.ts.map +1 -0
- package/lib/types/config.js +109 -0
- package/lib/types/config.js.map +1 -0
- package/lib/types/index.d.ts +41 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +483 -0
- package/lib/types/index.js.map +1 -0
- package/lib/types/invariant.d.ts +21 -0
- package/lib/types/invariant.d.ts.map +1 -0
- package/lib/types/invariant.js +85 -0
- package/lib/types/invariant.js.map +1 -0
- package/lib/types/registry/definition.d.ts +24 -0
- package/lib/types/registry/definition.d.ts.map +1 -0
- package/lib/types/registry/definition.js +189 -0
- package/lib/types/registry/definition.js.map +1 -0
- package/lib/types/registry/index.d.ts +100 -0
- package/lib/types/registry/index.d.ts.map +1 -0
- package/lib/types/registry/index.js +899 -0
- package/lib/types/registry/index.js.map +1 -0
- package/lib/types/registry/names.d.ts +19 -0
- package/lib/types/registry/names.d.ts.map +1 -0
- package/lib/types/registry/names.js +69 -0
- package/lib/types/registry/names.js.map +1 -0
- package/lib/types/registry/remote.d.ts +15 -0
- package/lib/types/registry/remote.d.ts.map +1 -0
- package/lib/types/registry/remote.js +128 -0
- package/lib/types/registry/remote.js.map +1 -0
- package/lib/types/registry/roots.d.ts +44 -0
- package/lib/types/registry/roots.d.ts.map +1 -0
- package/lib/types/registry/roots.js +118 -0
- package/lib/types/registry/roots.js.map +1 -0
- package/lib/types/registry/types.d.ts +62 -0
- package/lib/types/registry/types.d.ts.map +1 -0
- package/lib/types/registry/types.js +2 -0
- package/lib/types/registry/types.js.map +1 -0
- package/lib/types/registry/watchers.d.ts +70 -0
- package/lib/types/registry/watchers.d.ts.map +1 -0
- package/lib/types/registry/watchers.js +152 -0
- package/lib/types/registry/watchers.js.map +1 -0
- package/lib/types/remote-events.d.ts +42 -0
- package/lib/types/remote-events.d.ts.map +1 -0
- package/lib/types/remote-events.js +21 -0
- package/lib/types/remote-events.js.map +1 -0
- package/lib/types/run-recorder.d.ts +44 -0
- package/lib/types/run-recorder.d.ts.map +1 -0
- package/lib/types/run-recorder.js +356 -0
- package/lib/types/run-recorder.js.map +1 -0
- package/lib/types/supervisor/canned-validate.d.ts +25 -0
- package/lib/types/supervisor/canned-validate.d.ts.map +1 -0
- package/lib/types/supervisor/canned-validate.js +122 -0
- package/lib/types/supervisor/canned-validate.js.map +1 -0
- package/lib/types/supervisor/completion-notice.d.ts +49 -0
- package/lib/types/supervisor/completion-notice.d.ts.map +1 -0
- package/lib/types/supervisor/completion-notice.js +281 -0
- package/lib/types/supervisor/completion-notice.js.map +1 -0
- package/lib/types/supervisor/cursors.d.ts +68 -0
- package/lib/types/supervisor/cursors.d.ts.map +1 -0
- package/lib/types/supervisor/cursors.js +158 -0
- package/lib/types/supervisor/cursors.js.map +1 -0
- package/lib/types/supervisor/engine-compat.d.ts +48 -0
- package/lib/types/supervisor/engine-compat.d.ts.map +1 -0
- package/lib/types/supervisor/engine-compat.js +116 -0
- package/lib/types/supervisor/engine-compat.js.map +1 -0
- package/lib/types/supervisor/index.d.ts +183 -0
- package/lib/types/supervisor/index.d.ts.map +1 -0
- package/lib/types/supervisor/index.js +2040 -0
- package/lib/types/supervisor/index.js.map +1 -0
- package/lib/types/supervisor/parallel-compat.d.ts +36 -0
- package/lib/types/supervisor/parallel-compat.d.ts.map +1 -0
- package/lib/types/supervisor/parallel-compat.js +180 -0
- package/lib/types/supervisor/parallel-compat.js.map +1 -0
- package/lib/types/supervisor/remote.d.ts +25 -0
- package/lib/types/supervisor/remote.d.ts.map +1 -0
- package/lib/types/supervisor/remote.js +478 -0
- package/lib/types/supervisor/remote.js.map +1 -0
- package/lib/types/supervisor/storage/bounded-file.d.ts +41 -0
- package/lib/types/supervisor/storage/bounded-file.d.ts.map +1 -0
- package/lib/types/supervisor/storage/bounded-file.js +186 -0
- package/lib/types/supervisor/storage/bounded-file.js.map +1 -0
- package/lib/types/supervisor/storage/details-codec.d.ts +14 -0
- package/lib/types/supervisor/storage/details-codec.d.ts.map +1 -0
- package/lib/types/supervisor/storage/details-codec.js +359 -0
- package/lib/types/supervisor/storage/details-codec.js.map +1 -0
- package/lib/types/supervisor/storage/index.d.ts +26 -0
- package/lib/types/supervisor/storage/index.d.ts.map +1 -0
- package/lib/types/supervisor/storage/index.js +100 -0
- package/lib/types/supervisor/storage/index.js.map +1 -0
- package/lib/types/supervisor/storage/lease.d.ts +34 -0
- package/lib/types/supervisor/storage/lease.d.ts.map +1 -0
- package/lib/types/supervisor/storage/lease.js +289 -0
- package/lib/types/supervisor/storage/lease.js.map +1 -0
- package/lib/types/supervisor/storage/manifest-codec.d.ts +10 -0
- package/lib/types/supervisor/storage/manifest-codec.d.ts.map +1 -0
- package/lib/types/supervisor/storage/manifest-codec.js +335 -0
- package/lib/types/supervisor/storage/manifest-codec.js.map +1 -0
- package/lib/types/supervisor/storage/manifest-store.d.ts +99 -0
- package/lib/types/supervisor/storage/manifest-store.d.ts.map +1 -0
- package/lib/types/supervisor/storage/manifest-store.js +1606 -0
- package/lib/types/supervisor/storage/manifest-store.js.map +1 -0
- package/lib/types/supervisor/storage/manifest-types.d.ts +269 -0
- package/lib/types/supervisor/storage/manifest-types.d.ts.map +1 -0
- package/lib/types/supervisor/storage/manifest-types.js +2 -0
- package/lib/types/supervisor/storage/manifest-types.js.map +1 -0
- package/lib/types/supervisor/storage/private-root.d.ts +143 -0
- package/lib/types/supervisor/storage/private-root.d.ts.map +1 -0
- package/lib/types/supervisor/storage/private-root.js +860 -0
- package/lib/types/supervisor/storage/private-root.js.map +1 -0
- package/lib/types/supervisor/storage/recovery.d.ts +14 -0
- package/lib/types/supervisor/storage/recovery.d.ts.map +1 -0
- package/lib/types/supervisor/storage/recovery.js +126 -0
- package/lib/types/supervisor/storage/recovery.js.map +1 -0
- package/lib/types/supervisor/storage/run-files.d.ts +67 -0
- package/lib/types/supervisor/storage/run-files.d.ts.map +1 -0
- package/lib/types/supervisor/storage/run-files.js +346 -0
- package/lib/types/supervisor/storage/run-files.js.map +1 -0
- package/lib/types/supervisor/types.d.ts +295 -0
- package/lib/types/supervisor/types.d.ts.map +1 -0
- package/lib/types/supervisor/types.js +3 -0
- package/lib/types/supervisor/types.js.map +1 -0
- package/lib/types/supervisor/value-view.d.ts +19 -0
- package/lib/types/supervisor/value-view.d.ts.map +1 -0
- package/lib/types/supervisor/value-view.js +183 -0
- package/lib/types/supervisor/value-view.js.map +1 -0
- package/lib/types/tool/index.d.ts +86 -0
- package/lib/types/tool/index.d.ts.map +1 -0
- package/lib/types/tool/index.js +810 -0
- package/lib/types/tool/index.js.map +1 -0
- package/lib/types/tool/schema.d.ts +158 -0
- package/lib/types/tool/schema.d.ts.map +1 -0
- package/lib/types/tool/schema.js +137 -0
- package/lib/types/tool/schema.js.map +1 -0
- package/lib/types/types.d.ts +9 -0
- package/lib/types/types.d.ts.map +1 -0
- package/lib/types/types.js +3 -0
- package/lib/types/types.js.map +1 -0
- package/lib/types/user-questions.d.ts +9 -0
- package/lib/types/user-questions.d.ts.map +1 -0
- package/lib/types/user-questions.js +91 -0
- package/lib/types/user-questions.js.map +1 -0
- package/package.json +273 -0
- package/skills/create-workflow/SKILL.md +166 -0
- package/vendor/workflow-engine/LICENSE +21 -0
- package/vendor/workflow-engine/README.md +1 -0
|
@@ -0,0 +1,1500 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
let node_worker_threads = require("node:worker_threads");
|
|
24
|
+
let _deepseek_ai_dsh_workflow = require("@deepseek-ai/dsh-workflow");
|
|
25
|
+
let _deepseek_ai_dsh_llm = require("@deepseek-ai/dsh-llm");
|
|
26
|
+
let _deepseek_ai_dsh_session = require("@deepseek-ai/dsh-session");
|
|
27
|
+
let _deepseek_ai_dsh_tools = require("@deepseek-ai/dsh-tools");
|
|
28
|
+
let node_async_hooks = require("node:async_hooks");
|
|
29
|
+
let node_crypto = require("node:crypto");
|
|
30
|
+
let node_vm = require("node:vm");
|
|
31
|
+
node_vm = __toESM(node_vm, 1);
|
|
32
|
+
//#region vendor/workflow-engine/realm.ts
|
|
33
|
+
/**
|
|
34
|
+
* Materializes values leaving the script vm into plain JSON before they cross the worker
|
|
35
|
+
* boundary, and renders thrown script values without rejecting the run. The walk rejects
|
|
36
|
+
* values that JSON cannot preserve but trusts model-written workflow scripts: getters and proxy traps may
|
|
37
|
+
* run, and the vm is not a security boundary. The worker provides host-loop isolation and
|
|
38
|
+
* forced termination, not hostile-value containment. See
|
|
39
|
+
* .agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md for the isolation rationale.
|
|
40
|
+
* @module @deepseek-ai/dsh-workflow-worker-thread/realm
|
|
41
|
+
*/
|
|
42
|
+
/** Thrown by {@link materializeFromRealm}; the caller wraps it into the right `WorkflowError` code. */
|
|
43
|
+
var MaterializeError = class extends Error {
|
|
44
|
+
constructor(path, reason) {
|
|
45
|
+
super(`${path}: ${reason}`);
|
|
46
|
+
this.path = path;
|
|
47
|
+
this.reason = reason;
|
|
48
|
+
this.name = "MaterializeError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Render a thrown value to failure text without ever throwing: prefer the
|
|
53
|
+
* `stack` (host or realm — a realm error's `stack` is a plain string read),
|
|
54
|
+
* fall back to `message`, then `String()`. Reading those properties MAY run
|
|
55
|
+
* script code (a getter, `toString`) — accepted under the module's trust
|
|
56
|
+
* premise; if that code itself throws, a fixed label is returned instead.
|
|
57
|
+
* @param error - any value thrown in the host or worker realm.
|
|
58
|
+
* @returns human-readable text for the failure report; prefers the stack.
|
|
59
|
+
*/
|
|
60
|
+
function renderThrown(error) {
|
|
61
|
+
try {
|
|
62
|
+
const stack = error?.stack;
|
|
63
|
+
if (typeof stack === "string" && stack.length > 0) return stack;
|
|
64
|
+
const message = error?.message;
|
|
65
|
+
if (typeof message === "string" && message.length > 0) return message;
|
|
66
|
+
return String(error);
|
|
67
|
+
} catch {
|
|
68
|
+
return "[unrenderable thrown value]";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Whether an object's prototype chain represents a plain data object: `null`, or a prototype
|
|
73
|
+
* whose own prototype is `null` (the realm's `Object.prototype` — which we
|
|
74
|
+
* cannot compare by identity across realms). A `Date`/`Map`/class instance
|
|
75
|
+
* has a longer chain and is rejected.
|
|
76
|
+
*/
|
|
77
|
+
function hasPlainPrototype(value) {
|
|
78
|
+
const proto = Object.getPrototypeOf(value);
|
|
79
|
+
if (proto === null) return true;
|
|
80
|
+
return Object.getPrototypeOf(proto) === null;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Copy `value` (typically from the vm realm) into plain host JSON data. Root `undefined` is
|
|
84
|
+
* returned unchanged; nested `undefined` and values JSON cannot represent losslessly fail
|
|
85
|
+
* with the offending path. Property accessors run normally, and a throwing read is wrapped
|
|
86
|
+
* with its rendered failure.
|
|
87
|
+
*
|
|
88
|
+
* @param value - the realm value to materialize.
|
|
89
|
+
* @param root - the path label for the root value (error messages).
|
|
90
|
+
* @returns the host-realm copy (plain objects/arrays/scalars only).
|
|
91
|
+
* @throws {@link MaterializeError} for unsupported values, cycles, sparse arrays, exotic
|
|
92
|
+
* prototypes, or property reads that throw.
|
|
93
|
+
*/
|
|
94
|
+
function materializeFromRealm(value, root = "value") {
|
|
95
|
+
if (value === void 0) return void 0;
|
|
96
|
+
try {
|
|
97
|
+
return materialize(value, root, /* @__PURE__ */ new Set());
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (error instanceof MaterializeError) throw error;
|
|
100
|
+
throw new MaterializeError(root, `reading the value threw: ${renderThrown(error)}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function materialize(value, path, seen) {
|
|
104
|
+
switch (typeof value) {
|
|
105
|
+
case "boolean":
|
|
106
|
+
case "string": return value;
|
|
107
|
+
case "number":
|
|
108
|
+
if (!Number.isFinite(value)) throw new MaterializeError(path, "non-finite numbers are not JSON data");
|
|
109
|
+
return value;
|
|
110
|
+
case "bigint": throw new MaterializeError(path, "bigints are not JSON data");
|
|
111
|
+
case "function": throw new MaterializeError(path, "functions are not plain JSON data");
|
|
112
|
+
case "symbol": throw new MaterializeError(path, "symbols are not plain JSON data");
|
|
113
|
+
case "undefined": throw new MaterializeError(path, "undefined is not JSON data");
|
|
114
|
+
}
|
|
115
|
+
if (value === null) return null;
|
|
116
|
+
const objectValue = value;
|
|
117
|
+
if (seen.has(objectValue)) throw new MaterializeError(path, "circular references are not JSON data");
|
|
118
|
+
seen.add(objectValue);
|
|
119
|
+
try {
|
|
120
|
+
if (Array.isArray(objectValue)) return materializeArray(objectValue, path, seen);
|
|
121
|
+
return materializeObject(objectValue, path, seen);
|
|
122
|
+
} finally {
|
|
123
|
+
seen.delete(objectValue);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function materializeArray(value, path, seen) {
|
|
127
|
+
const out = [];
|
|
128
|
+
for (let index = 0; index < value.length; index++) {
|
|
129
|
+
if (!(index in value)) throw new MaterializeError(`${path}[${index}]`, "sparse arrays are not JSON data");
|
|
130
|
+
out.push(materialize(value[index], `${path}[${index}]`, seen));
|
|
131
|
+
}
|
|
132
|
+
for (const key of Object.keys(value)) {
|
|
133
|
+
const index = Number(key);
|
|
134
|
+
if (!Number.isInteger(index) || index < 0 || index >= value.length) throw new MaterializeError(`${path}.${key}`, "arrays with non-index properties are not JSON data");
|
|
135
|
+
}
|
|
136
|
+
if (Object.getOwnPropertySymbols(value).length > 0) throw new MaterializeError(path, "symbol-keyed properties are not plain JSON data");
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
function materializeObject(value, path, seen) {
|
|
140
|
+
if (!hasPlainPrototype(value)) throw new MaterializeError(path, "only plain objects and arrays are JSON data (exotic prototype)");
|
|
141
|
+
if (Object.getOwnPropertySymbols(value).length > 0) throw new MaterializeError(path, "symbol-keyed properties are not plain JSON data");
|
|
142
|
+
const out = {};
|
|
143
|
+
for (const key of Object.keys(value)) Object.defineProperty(out, key, {
|
|
144
|
+
value: materialize(value[key], `${path}.${key}`, seen),
|
|
145
|
+
enumerable: true,
|
|
146
|
+
writable: true,
|
|
147
|
+
configurable: true
|
|
148
|
+
});
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region vendor/workflow-engine/compat-seam.ts
|
|
153
|
+
/** Local extensions deliberately absent from the official 0.1.1-rc.2 seam. */
|
|
154
|
+
const WorkflowError$1 = _deepseek_ai_dsh_workflow.WorkflowError;
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region vendor/workflow-engine/runtime.ts
|
|
157
|
+
/**
|
|
158
|
+
* Per-run worker-side vm hooks, child RPC, concurrency/caps, cancellation, and result serialization; it
|
|
159
|
+
* never touches Cordis. Script values leaving the realm are materialized as plain JSON before
|
|
160
|
+
* messaging. Values entering the trusted model-written realm are passed directly; `args` alone is
|
|
161
|
+
* cloned so script mutation cannot alter initialization data. See `./realm.ts` for the trust model.
|
|
162
|
+
*
|
|
163
|
+
* Fatal workflow errors—bad hook arguments, unsupported schemas/options, caps, start failures, and
|
|
164
|
+
* cancellation—propagate through combinators. Only child failures and ordinary stage errors become
|
|
165
|
+
* per-item nulls. Every returned promise has a rejection consumer so dropped script promises cannot
|
|
166
|
+
* kill the worker. A cancelled script that never settles emits nothing; the host force-settles the
|
|
167
|
+
* run within grace and terminates the thread.
|
|
168
|
+
* @module @deepseek-ai/dsh-workflow-worker-thread/runtime
|
|
169
|
+
*/
|
|
170
|
+
/**
|
|
171
|
+
* Build a callable/constructable global that fails before exposing ambient
|
|
172
|
+
* process state. The vm is not a security boundary, but ordinary authored
|
|
173
|
+
* workflows must not accidentally make resumed control flow depend on the
|
|
174
|
+
* clock, randomness, garbage collection, or timer-like atomics.
|
|
175
|
+
*/
|
|
176
|
+
function unavailableNondeterministicGlobal(name) {
|
|
177
|
+
const fail = () => {
|
|
178
|
+
throw new WorkflowError$1(`${name} is unavailable in workflow scripts because runs must derive control flow from args and committed host results`, "INVALID_ARGUMENT");
|
|
179
|
+
};
|
|
180
|
+
/* v8 ignore next -- every call/construct/property operation is intercepted by the Proxy traps below */
|
|
181
|
+
const target = Object.freeze(function unavailableWorkflowGlobal() {
|
|
182
|
+
return fail();
|
|
183
|
+
});
|
|
184
|
+
return Object.freeze(new Proxy(target, {
|
|
185
|
+
apply: fail,
|
|
186
|
+
construct: fail,
|
|
187
|
+
get: fail,
|
|
188
|
+
set: fail
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
/** Clone the deterministic Math surface while replacing its random source. */
|
|
192
|
+
function deterministicMath() {
|
|
193
|
+
const descriptors = Object.getOwnPropertyDescriptors(Math);
|
|
194
|
+
descriptors.random = {
|
|
195
|
+
...descriptors.random,
|
|
196
|
+
value: Object.freeze(() => {
|
|
197
|
+
throw new WorkflowError$1("Math.random() is unavailable in workflow scripts because runs must derive control flow from args and committed host results", "INVALID_ARGUMENT");
|
|
198
|
+
})
|
|
199
|
+
};
|
|
200
|
+
return Object.freeze(Object.defineProperties(Object.create(Reflect.getPrototypeOf(Math)), descriptors));
|
|
201
|
+
}
|
|
202
|
+
/** Define one JSON object key without giving `__proto__` assignment semantics. */
|
|
203
|
+
function defineJsonProperty(target, key, value) {
|
|
204
|
+
Object.defineProperty(target, key, {
|
|
205
|
+
configurable: true,
|
|
206
|
+
enumerable: true,
|
|
207
|
+
writable: true,
|
|
208
|
+
value
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
/** Copy one generated JSON object while retaining every key as data. */
|
|
212
|
+
function copyJsonObject(source) {
|
|
213
|
+
const copy = {};
|
|
214
|
+
for (const [key, value] of Object.entries(source)) defineJsonProperty(copy, key, value);
|
|
215
|
+
return copy;
|
|
216
|
+
}
|
|
217
|
+
/** Define a schema key without giving `__proto__` assignment semantics. */
|
|
218
|
+
function defineSchemaProperty(target, key, value) {
|
|
219
|
+
Object.defineProperty(target, key, {
|
|
220
|
+
configurable: true,
|
|
221
|
+
enumerable: true,
|
|
222
|
+
writable: true,
|
|
223
|
+
value
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/** Whether a bound is a lossless, non-negative safe integer (including rejecting `-0`). */
|
|
227
|
+
function isArrayBound(value) {
|
|
228
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && !Object.is(value, -0);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Validate the plugin's two forward-compatible schema keywords and produce
|
|
232
|
+
* the projection accepted by stock RC2. Traversal follows schema-bearing
|
|
233
|
+
* locations only: annotation payloads are data and must not be rewritten.
|
|
234
|
+
*/
|
|
235
|
+
function prepareObjectSchema(authored) {
|
|
236
|
+
const boundViolations = [];
|
|
237
|
+
const visit = (rawNode, path) => {
|
|
238
|
+
if (typeof rawNode !== "object" || rawNode === null || Array.isArray(rawNode)) return rawNode;
|
|
239
|
+
const node = rawNode;
|
|
240
|
+
const provider = {};
|
|
241
|
+
const hasMin = Object.hasOwn(node, "minItems");
|
|
242
|
+
const hasMax = Object.hasOwn(node, "maxItems");
|
|
243
|
+
const hasOneOf = Object.hasOwn(node, "oneOf");
|
|
244
|
+
for (const keyword of ["minItems", "maxItems"]) {
|
|
245
|
+
if (!Object.hasOwn(node, keyword)) continue;
|
|
246
|
+
if (hasOneOf) boundViolations.push(`${path}.${keyword} is not supported beside oneOf`);
|
|
247
|
+
else if (node.type !== "array") boundViolations.push(Object.hasOwn(node, "type") ? `${path}.${keyword} is not supported on type ${JSON.stringify(node.type)}` : `${path}.${keyword} requires type or oneOf`);
|
|
248
|
+
}
|
|
249
|
+
let minimum;
|
|
250
|
+
let maximum;
|
|
251
|
+
if (!hasOneOf && node.type === "array") {
|
|
252
|
+
if (hasMin) {
|
|
253
|
+
if (!isArrayBound(node.minItems)) boundViolations.push(`${path}.minItems must be a non-negative safe integer`);
|
|
254
|
+
else minimum = node.minItems;
|
|
255
|
+
}
|
|
256
|
+
if (hasMax) {
|
|
257
|
+
if (!isArrayBound(node.maxItems)) boundViolations.push(`${path}.maxItems must be a non-negative safe integer`);
|
|
258
|
+
else maximum = node.maxItems;
|
|
259
|
+
}
|
|
260
|
+
if (minimum !== void 0 && maximum !== void 0 && minimum > maximum) boundViolations.push(`${path}.minItems must not exceed ${path}.maxItems`);
|
|
261
|
+
}
|
|
262
|
+
for (const [key, value] of Object.entries(node)) {
|
|
263
|
+
if (key === "minItems" || key === "maxItems") continue;
|
|
264
|
+
if (key === "items") {
|
|
265
|
+
defineSchemaProperty(provider, key, visit(value, `${path}.items`));
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (key === "oneOf" && Array.isArray(value)) {
|
|
269
|
+
defineSchemaProperty(provider, key, value.map((branch, index) => visit(branch, `${path}.oneOf[${index}]`)));
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (key === "properties" && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
273
|
+
const properties = {};
|
|
274
|
+
for (const [name, child] of Object.entries(value)) defineSchemaProperty(properties, name, visit(child, `${path}.properties.${name}`));
|
|
275
|
+
defineSchemaProperty(provider, key, properties);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
defineSchemaProperty(provider, key, value);
|
|
279
|
+
}
|
|
280
|
+
return provider;
|
|
281
|
+
};
|
|
282
|
+
const provider = visit(authored, "schema");
|
|
283
|
+
let stockViolations = [];
|
|
284
|
+
try {
|
|
285
|
+
(0, _deepseek_ai_dsh_tools.assertObjectJsonSchema)(provider);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
/* v8 ignore next -- assertObjectJsonSchema only throws JsonSchemaError */
|
|
288
|
+
if (!(error instanceof _deepseek_ai_dsh_tools.JsonSchemaError)) throw error;
|
|
289
|
+
stockViolations = error.violations;
|
|
290
|
+
}
|
|
291
|
+
const violations = [...boundViolations, ...stockViolations];
|
|
292
|
+
if (violations.length > 0) throw new _deepseek_ai_dsh_tools.JsonSchemaError(violations);
|
|
293
|
+
return {
|
|
294
|
+
authored,
|
|
295
|
+
provider
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Validate against the authored extended subset while continuing to delegate
|
|
300
|
+
* all ordinary JSON/type/scalar checks to RC2's shared validator. Object and
|
|
301
|
+
* array children are walked here so bounds can participate in exact-one
|
|
302
|
+
* `oneOf` matching instead of the stripped provider branches becoming
|
|
303
|
+
* spuriously overlapping.
|
|
304
|
+
*/
|
|
305
|
+
function schemaValueMatches(schema, value) {
|
|
306
|
+
if (schema.oneOf !== void 0) {
|
|
307
|
+
let matches = 0;
|
|
308
|
+
for (const branch of schema.oneOf) {
|
|
309
|
+
if (schemaValueMatches(branch, value)) matches += 1;
|
|
310
|
+
if (matches > 1) return false;
|
|
311
|
+
}
|
|
312
|
+
return matches === 1;
|
|
313
|
+
}
|
|
314
|
+
if (schema.type === "object") {
|
|
315
|
+
if ((0, _deepseek_ai_dsh_tools.validateJsonSchemaValue)({ type: "object" }, value).length > 0) return false;
|
|
316
|
+
const record = value;
|
|
317
|
+
const properties = schema.properties ?? {};
|
|
318
|
+
for (const required of schema.required ?? []) if (!Object.hasOwn(record, required)) return false;
|
|
319
|
+
if (schema.additionalProperties === false) {
|
|
320
|
+
for (const key of Object.keys(record)) if (!Object.hasOwn(properties, key)) return false;
|
|
321
|
+
}
|
|
322
|
+
for (const [key, childSchema] of Object.entries(properties)) if (Object.hasOwn(record, key) && !schemaValueMatches(childSchema, record[key])) return false;
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
if (schema.type === "array") {
|
|
326
|
+
if ((0, _deepseek_ai_dsh_tools.validateJsonSchemaValue)({ type: "array" }, value).length > 0) return false;
|
|
327
|
+
const array = value;
|
|
328
|
+
const bounded = schema;
|
|
329
|
+
if (bounded.minItems !== void 0 && array.length < bounded.minItems) return false;
|
|
330
|
+
if (bounded.maxItems !== void 0 && array.length > bounded.maxItems) return false;
|
|
331
|
+
if (schema.items !== void 0) {
|
|
332
|
+
for (const item of array) if (!schemaValueMatches(schema.items, item)) return false;
|
|
333
|
+
}
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
return (0, _deepseek_ai_dsh_tools.validateJsonSchemaValue)(schema, value).length === 0;
|
|
337
|
+
}
|
|
338
|
+
/** Deterministic unconstrained candidates used to disambiguate exact-one unions. */
|
|
339
|
+
const ANY_JSON_CANDIDATES = [
|
|
340
|
+
null,
|
|
341
|
+
false,
|
|
342
|
+
true,
|
|
343
|
+
0,
|
|
344
|
+
.5,
|
|
345
|
+
"",
|
|
346
|
+
"value",
|
|
347
|
+
[],
|
|
348
|
+
{}
|
|
349
|
+
];
|
|
350
|
+
/** Count serialized JSON nodes up to a configured smoke-host work limit. */
|
|
351
|
+
function jsonNodeCount(value, limit) {
|
|
352
|
+
let count = 0;
|
|
353
|
+
const pending = [value];
|
|
354
|
+
for (let current = pending.pop(); current !== void 0; current = pending.pop()) {
|
|
355
|
+
count += 1;
|
|
356
|
+
if (count > limit) return count;
|
|
357
|
+
if (Array.isArray(current)) for (const child of current) pending.push(child);
|
|
358
|
+
else if (typeof current === "object" && current !== null) for (const child of Object.values(current)) pending.push(child);
|
|
359
|
+
}
|
|
360
|
+
return count;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Produce deterministic candidates for one already-validated schema node.
|
|
364
|
+
* Returned candidates satisfy the node; an empty list means the supported
|
|
365
|
+
* exact-one vocabulary has no value this smoke host can construct.
|
|
366
|
+
*/
|
|
367
|
+
function cannedSchemaCandidates(schema, maxArrayItems) {
|
|
368
|
+
let candidates;
|
|
369
|
+
if (schema.oneOf !== void 0) {
|
|
370
|
+
candidates = [];
|
|
371
|
+
for (const branch of schema.oneOf) {
|
|
372
|
+
for (const candidate of cannedSchemaCandidates(branch, maxArrayItems)) {
|
|
373
|
+
if (candidates.length >= maxArrayItems) break;
|
|
374
|
+
candidates.push(candidate);
|
|
375
|
+
}
|
|
376
|
+
if (candidates.length >= maxArrayItems) break;
|
|
377
|
+
}
|
|
378
|
+
for (const candidate of ANY_JSON_CANDIDATES) {
|
|
379
|
+
if (candidates.length >= maxArrayItems) break;
|
|
380
|
+
candidates.push(candidate);
|
|
381
|
+
}
|
|
382
|
+
} else if (Object.hasOwn(schema, "const")) candidates = [schema.const];
|
|
383
|
+
else if (schema.enum !== void 0) candidates = [...schema.enum];
|
|
384
|
+
else switch (schema.type) {
|
|
385
|
+
case "object": {
|
|
386
|
+
const properties = schema.properties ?? {};
|
|
387
|
+
const required = schema.required ?? [];
|
|
388
|
+
const base = {};
|
|
389
|
+
const requiredCandidates = /* @__PURE__ */ new Map();
|
|
390
|
+
for (const key of required) {
|
|
391
|
+
const values = cannedSchemaCandidates(properties[key], maxArrayItems);
|
|
392
|
+
if (values.length === 0) return [];
|
|
393
|
+
requiredCandidates.set(key, values);
|
|
394
|
+
defineJsonProperty(base, key, values[0]);
|
|
395
|
+
}
|
|
396
|
+
candidates = [base];
|
|
397
|
+
for (const [key, values] of requiredCandidates) for (const value of values.slice(1)) {
|
|
398
|
+
if (candidates.length >= maxArrayItems) break;
|
|
399
|
+
const alternate = copyJsonObject(base);
|
|
400
|
+
defineJsonProperty(alternate, key, value);
|
|
401
|
+
candidates.push(alternate);
|
|
402
|
+
}
|
|
403
|
+
const optional = Object.entries(properties).filter(([key]) => !requiredCandidates.has(key));
|
|
404
|
+
const withAllOptional = copyJsonObject(base);
|
|
405
|
+
let hasAllOptional = false;
|
|
406
|
+
for (const [key, childSchema] of optional) {
|
|
407
|
+
const values = cannedSchemaCandidates(childSchema, maxArrayItems);
|
|
408
|
+
for (const value of values) {
|
|
409
|
+
if (candidates.length >= maxArrayItems) break;
|
|
410
|
+
const alternate = copyJsonObject(base);
|
|
411
|
+
defineJsonProperty(alternate, key, value);
|
|
412
|
+
candidates.push(alternate);
|
|
413
|
+
}
|
|
414
|
+
if (values[0] !== void 0) {
|
|
415
|
+
defineJsonProperty(withAllOptional, key, values[0]);
|
|
416
|
+
hasAllOptional = true;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (hasAllOptional) candidates.push(withAllOptional);
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
case "array": {
|
|
423
|
+
const arraySchema = schema;
|
|
424
|
+
const minimum = arraySchema.minItems ?? 0;
|
|
425
|
+
if (minimum > maxArrayItems) return [];
|
|
426
|
+
const mayContainItem = arraySchema.maxItems === void 0 || arraySchema.maxItems > 0;
|
|
427
|
+
const itemCandidates = schema.items === void 0 ? [null] : cannedSchemaCandidates(schema.items, maxArrayItems);
|
|
428
|
+
candidates = minimum === 0 ? [[]] : [];
|
|
429
|
+
for (const value of itemCandidates) if (minimum > 0 && 1 + minimum * jsonNodeCount(value, maxArrayItems) <= maxArrayItems) candidates.push(Array.from({ length: minimum }, () => value));
|
|
430
|
+
else if (mayContainItem) candidates.push([value]);
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
case "string":
|
|
434
|
+
candidates = ["", "value"];
|
|
435
|
+
break;
|
|
436
|
+
case "number":
|
|
437
|
+
candidates = [
|
|
438
|
+
0,
|
|
439
|
+
.5,
|
|
440
|
+
1,
|
|
441
|
+
-1
|
|
442
|
+
];
|
|
443
|
+
break;
|
|
444
|
+
case "integer":
|
|
445
|
+
candidates = [
|
|
446
|
+
0,
|
|
447
|
+
1,
|
|
448
|
+
-1
|
|
449
|
+
];
|
|
450
|
+
break;
|
|
451
|
+
case "boolean":
|
|
452
|
+
candidates = [false, true];
|
|
453
|
+
break;
|
|
454
|
+
case "null":
|
|
455
|
+
candidates = [null];
|
|
456
|
+
break;
|
|
457
|
+
case void 0:
|
|
458
|
+
candidates = [...ANY_JSON_CANDIDATES];
|
|
459
|
+
break;
|
|
460
|
+
/* v8 ignore next -- assertObjectJsonSchema validated the closed schema vocabulary recursively. */
|
|
461
|
+
default: return [];
|
|
462
|
+
}
|
|
463
|
+
return candidates.filter((candidate) => jsonNodeCount(candidate, maxArrayItems) <= maxArrayItems).filter((candidate) => schemaValueMatches(schema, candidate)).slice(0, maxArrayItems);
|
|
464
|
+
}
|
|
465
|
+
/** Synthesize one schema-conforming structured result for `validate_only`. */
|
|
466
|
+
function cannedSchemaValue(schema, maxArrayItems) {
|
|
467
|
+
const candidate = cannedSchemaCandidates(schema, maxArrayItems)[0];
|
|
468
|
+
if (candidate !== void 0) return candidate;
|
|
469
|
+
throw new WorkflowError$1("validate_only could not synthesize a canned result that conforms to the agent() schema", "UNSUPPORTED_SCHEMA");
|
|
470
|
+
}
|
|
471
|
+
/** The `agent()` options the script may pass; everything else rejects loud. */
|
|
472
|
+
const SUPPORTED_AGENT_OPTIONS = /* @__PURE__ */ new Set([
|
|
473
|
+
"label",
|
|
474
|
+
"phase",
|
|
475
|
+
"schema",
|
|
476
|
+
"provider",
|
|
477
|
+
"model"
|
|
478
|
+
]);
|
|
479
|
+
/** Deferred Claude Code options we name explicitly in the rejection message. */
|
|
480
|
+
const DEFERRED_AGENT_OPTIONS = /* @__PURE__ */ new Set([
|
|
481
|
+
"effort",
|
|
482
|
+
"isolation",
|
|
483
|
+
"agentType"
|
|
484
|
+
]);
|
|
485
|
+
/** Flatten a child's final output blocks to text (the non-schema `agent()` result). */
|
|
486
|
+
function outputText(blocks) {
|
|
487
|
+
return blocks.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
488
|
+
}
|
|
489
|
+
/** A short display label derived from the prompt when the script passes none. */
|
|
490
|
+
function defaultLabel(prompt) {
|
|
491
|
+
const newline = prompt.indexOf("\n");
|
|
492
|
+
const line = newline === -1 ? prompt : prompt.slice(0, newline);
|
|
493
|
+
return line.length <= 48 ? line : `${line.slice(0, 47)}…`;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* One live script execution inside the worker. Constructed per run by the
|
|
497
|
+
* session; `drive()` is called exactly once and NEVER rejects — every failure
|
|
498
|
+
* becomes a {@link WorkflowResult} with a non-`completed` stop reason. The
|
|
499
|
+
* host owns cancellation and cleanup of any dropped child work.
|
|
500
|
+
*/
|
|
501
|
+
var WorkflowExecution = class {
|
|
502
|
+
constructor(meta, body, args, limits, observer, children, journal, validateOnly = false, initialAgentSpend = 0, initialAgentSeq = initialAgentSpend) {
|
|
503
|
+
this.limits = limits;
|
|
504
|
+
this.observer = observer;
|
|
505
|
+
this.children = children;
|
|
506
|
+
this.validateOnly = validateOnly;
|
|
507
|
+
this.activeSlots = 0;
|
|
508
|
+
this.slotWaiters = [];
|
|
509
|
+
this.completionGate = Promise.withResolvers();
|
|
510
|
+
this.replayedJournalCallIds = /* @__PURE__ */ new Set();
|
|
511
|
+
this.callScopes = new node_async_hooks.AsyncLocalStorage();
|
|
512
|
+
this.rootScope = {
|
|
513
|
+
path: [],
|
|
514
|
+
nextNode: 0
|
|
515
|
+
};
|
|
516
|
+
try {
|
|
517
|
+
this.compiled = new node_vm.Script(`(async () => {\n${body}\n})()`, {
|
|
518
|
+
filename: `workflow:${meta.name}`,
|
|
519
|
+
lineOffset: -1
|
|
520
|
+
});
|
|
521
|
+
} catch (error) {
|
|
522
|
+
throw new WorkflowError$1(`workflow script does not parse: ${String(error)}`, "SCRIPT_PARSE", { cause: error });
|
|
523
|
+
}
|
|
524
|
+
const committedAgents = (journal ?? []).filter((entry) => entry.kind === "agent");
|
|
525
|
+
this.started = Math.max(initialAgentSpend, committedAgents.length);
|
|
526
|
+
let journalMaximum = 0;
|
|
527
|
+
let ordinalMaximum = 0;
|
|
528
|
+
for (const entry of committedAgents) journalMaximum = Math.max(journalMaximum, entry.seq);
|
|
529
|
+
for (const entry of journal ?? []) ordinalMaximum = Math.max(ordinalMaximum, entry.ordinal);
|
|
530
|
+
this.nextAgentSeq = Math.max(initialAgentSeq, this.started, journalMaximum);
|
|
531
|
+
this.nextJournalOrdinal = ordinalMaximum;
|
|
532
|
+
this.journal = indexJournal(journal);
|
|
533
|
+
this.context = node_vm.createContext({}, { name: `workflow:${meta.name}` });
|
|
534
|
+
const globals = {
|
|
535
|
+
agent: (prompt, opts) => this.contain(this.agent(prompt, opts)),
|
|
536
|
+
parallel: (items) => this.contain(this.parallel(items)),
|
|
537
|
+
pipeline: (items, ...stages) => this.contain(this.pipeline(items, stages)),
|
|
538
|
+
phase: (title) => {
|
|
539
|
+
this.phase(title);
|
|
540
|
+
},
|
|
541
|
+
log: (message) => {
|
|
542
|
+
this.log(message);
|
|
543
|
+
},
|
|
544
|
+
complete: (value) => {
|
|
545
|
+
this.complete(value);
|
|
546
|
+
},
|
|
547
|
+
pause: (kind, message) => this.contain(this.gate(kind, message, false)),
|
|
548
|
+
await_user: (kind, message) => this.contain(this.gate(kind, message, true)),
|
|
549
|
+
budget: () => this.budget(),
|
|
550
|
+
write_scratch_file: (name, content) => this.contain(this.writeScratch(name, content)),
|
|
551
|
+
read_scratch_file: (name) => this.contain(this.readScratch(name)),
|
|
552
|
+
Date: unavailableNondeterministicGlobal("Date"),
|
|
553
|
+
Math: deterministicMath(),
|
|
554
|
+
Atomics: unavailableNondeterministicGlobal("Atomics"),
|
|
555
|
+
SharedArrayBuffer: unavailableNondeterministicGlobal("SharedArrayBuffer"),
|
|
556
|
+
WeakRef: unavailableNondeterministicGlobal("WeakRef"),
|
|
557
|
+
FinalizationRegistry: unavailableNondeterministicGlobal("FinalizationRegistry"),
|
|
558
|
+
args
|
|
559
|
+
};
|
|
560
|
+
for (const [key, value] of Object.entries(globals)) this.context[key] = typeof value === "function" ? Object.freeze(value) : value;
|
|
561
|
+
}
|
|
562
|
+
/** Release the gate the script is parked on, if any. */
|
|
563
|
+
resume() {
|
|
564
|
+
this.gateResume?.();
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Whether the run has been cancelled. A METHOD, not an inline property
|
|
568
|
+
* read: `cancel()` mutates `cancelReason` concurrently (the session's
|
|
569
|
+
* message handler), and an inline read after an `await` gets narrowed by
|
|
570
|
+
* control flow into an always-false comparison.
|
|
571
|
+
*/
|
|
572
|
+
isCancelled() {
|
|
573
|
+
return this.cancelReason !== void 0;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Shared hook entry guard: after {@link cancel}, EVERY hook throws
|
|
577
|
+
* `CANCELLED` at its next call — cancellation is the next HOOK boundary,
|
|
578
|
+
* not just the next `agent()`, so a script that caught one cancelled
|
|
579
|
+
* rejection cannot keep emitting progress through `phase`/`log` or enter a
|
|
580
|
+
* combinator.
|
|
581
|
+
*/
|
|
582
|
+
throwIfCancelled() {
|
|
583
|
+
if (this.completed !== void 0 || this.completionError !== void 0) throw COMPLETE_SENTINEL;
|
|
584
|
+
if (this.isCancelled()) throw this.cancelledError();
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Cancel the run: waiting `agent()` slots reject and every future hook call
|
|
588
|
+
* throws `CANCELLED` — the script dies at its next await. A script that
|
|
589
|
+
* never settles anyway (parked on a promise no hook owns) is the HOST's
|
|
590
|
+
* problem: its grace timer force-settles the run and terminates the
|
|
591
|
+
* worker. Idempotent; the first reason wins.
|
|
592
|
+
* @param reason - human-readable cause carried on the CANCELLED error. The
|
|
593
|
+
* host independently aborts the required signal shared by every child.
|
|
594
|
+
*/
|
|
595
|
+
cancel(reason) {
|
|
596
|
+
if (this.cancelReason !== void 0) return;
|
|
597
|
+
this.cancelReason = reason;
|
|
598
|
+
this.cancelError = new WorkflowError$1(`workflow run cancelled: ${this.cancelReason}`, "CANCELLED");
|
|
599
|
+
for (const waiter of this.slotWaiters.splice(0)) waiter.reject(this.cancelledError());
|
|
600
|
+
this.gateResume?.();
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Run the script to settlement. Resolves — never rejects — with the run's
|
|
604
|
+
* {@link WorkflowResult}: the materialized return value on `completed`, the
|
|
605
|
+
* failure message on `error`, and `cancelled` when the script died of
|
|
606
|
+
* cancellation. This method only chooses the result; the session publishes
|
|
607
|
+
* it and the host owns terminal child cancellation.
|
|
608
|
+
* @returns the settled outcome — this promise NEVER rejects (the seam's
|
|
609
|
+
* `result`-never-rejects contract); every failure maps to a variant.
|
|
610
|
+
*/
|
|
611
|
+
async drive() {
|
|
612
|
+
try {
|
|
613
|
+
if (this.isCancelled()) throw this.cancelledError();
|
|
614
|
+
const scriptPromise = this.callScopes.run(this.rootScope, () => this.compiled.runInContext(this.context, { timeout: this.limits.syncTimeoutMs }));
|
|
615
|
+
const scriptResult = this.contain(Promise.resolve(scriptPromise)).then((value) => ({
|
|
616
|
+
kind: "script",
|
|
617
|
+
value
|
|
618
|
+
}));
|
|
619
|
+
const completed = this.completionGate.promise.then(() => ({ kind: "complete" }));
|
|
620
|
+
const settled = await Promise.race([scriptResult, completed]);
|
|
621
|
+
if (settled.kind === "complete") return this.completedResult();
|
|
622
|
+
const raw = settled.value;
|
|
623
|
+
/* v8 ignore next -- complete() resolves completionGate before a caught sentinel can settle scriptPromise */
|
|
624
|
+
if (this.completed !== void 0 || this.completionError !== void 0) return this.completedResult();
|
|
625
|
+
if (this.isCancelled()) throw this.cancelledError();
|
|
626
|
+
const value = raw === void 0 ? null : this.materializeResult(raw);
|
|
627
|
+
const missingReplay = this.unreplayedJournalError();
|
|
628
|
+
if (missingReplay !== void 0) throw missingReplay;
|
|
629
|
+
return {
|
|
630
|
+
value,
|
|
631
|
+
stopReason: "completed",
|
|
632
|
+
agentsStarted: this.started
|
|
633
|
+
};
|
|
634
|
+
} catch (error) {
|
|
635
|
+
/* v8 ignore next -- completionGate wins the race whenever complete() has claimed a terminal */
|
|
636
|
+
if (this.completed !== void 0 || this.completionError !== void 0) return this.completedResult();
|
|
637
|
+
if (this.isCancelled()) return {
|
|
638
|
+
value: null,
|
|
639
|
+
stopReason: "cancelled",
|
|
640
|
+
error: this.cancelledError().message,
|
|
641
|
+
errorCode: "CANCELLED",
|
|
642
|
+
agentsStarted: this.started
|
|
643
|
+
};
|
|
644
|
+
/* v8 ignore next -- the out-of-band completionGate always wins before the sentinel reaches drive() */
|
|
645
|
+
if (error === COMPLETE_SENTINEL) return this.completedResult();
|
|
646
|
+
return {
|
|
647
|
+
value: null,
|
|
648
|
+
stopReason: "error",
|
|
649
|
+
error: renderThrown(error),
|
|
650
|
+
...error instanceof WorkflowError$1 ? { errorCode: error.code } : {},
|
|
651
|
+
agentsStarted: this.started
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
/** Materialize and report the `complete(value)` terminal. */
|
|
656
|
+
completedResult() {
|
|
657
|
+
if (this.completionError !== void 0) return {
|
|
658
|
+
value: null,
|
|
659
|
+
stopReason: "error",
|
|
660
|
+
error: this.completionError.message,
|
|
661
|
+
errorCode: this.completionError.code,
|
|
662
|
+
agentsStarted: this.started
|
|
663
|
+
};
|
|
664
|
+
const missingReplay = this.unreplayedJournalError();
|
|
665
|
+
if (missingReplay !== void 0) return {
|
|
666
|
+
value: null,
|
|
667
|
+
stopReason: "error",
|
|
668
|
+
error: missingReplay.message,
|
|
669
|
+
errorCode: missingReplay.code,
|
|
670
|
+
agentsStarted: this.started
|
|
671
|
+
};
|
|
672
|
+
return {
|
|
673
|
+
value: this.completed.value,
|
|
674
|
+
stopReason: "completed",
|
|
675
|
+
agentsStarted: this.started
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Attach a no-op rejection consumer WITHOUT changing what the caller
|
|
680
|
+
* receives: if the script drops the promise (no await), cancellation cannot
|
|
681
|
+
* become an unhandled rejection (which would kill the worker thread); if
|
|
682
|
+
* the script does await it, it still observes the rejection.
|
|
683
|
+
*/
|
|
684
|
+
contain(promise) {
|
|
685
|
+
promise.catch(() => {});
|
|
686
|
+
return promise;
|
|
687
|
+
}
|
|
688
|
+
cancelledError() {
|
|
689
|
+
/* v8 ignore next */
|
|
690
|
+
return this.cancelError ?? new WorkflowError$1("workflow run cancelled", "CANCELLED");
|
|
691
|
+
}
|
|
692
|
+
/** Materialize the script's return value; violations become RESULT_UNSERIALIZABLE. */
|
|
693
|
+
materializeResult(raw) {
|
|
694
|
+
try {
|
|
695
|
+
return materializeFromRealm(raw, "workflow result");
|
|
696
|
+
} catch (error) {
|
|
697
|
+
/* v8 ignore next -- defensive rethrow arm: materializeFromRealm only throws MaterializeError */
|
|
698
|
+
if (!(error instanceof MaterializeError)) throw error;
|
|
699
|
+
throw new WorkflowError$1(`the workflow's return value is not plain JSON data — ${error.message}. Return only JSON-serializable objects/arrays/scalars.`, "RESULT_UNSERIALIZABLE", { cause: error });
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Acquire one concurrency slot (FIFO). Cancellation rejects QUEUED waiters
|
|
704
|
+
* (see {@link cancel}); the callers guard their own entry and post-acquire
|
|
705
|
+
* windows, so no cancelled-precheck is duplicated here.
|
|
706
|
+
*/
|
|
707
|
+
acquireSlot() {
|
|
708
|
+
if (this.activeSlots < this.limits.maxConcurrentAgents) {
|
|
709
|
+
this.activeSlots += 1;
|
|
710
|
+
return Promise.resolve();
|
|
711
|
+
}
|
|
712
|
+
return new Promise((resolve, reject) => {
|
|
713
|
+
this.slotWaiters.push({
|
|
714
|
+
resolve: () => {
|
|
715
|
+
this.activeSlots += 1;
|
|
716
|
+
resolve();
|
|
717
|
+
},
|
|
718
|
+
reject
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
releaseSlot() {
|
|
723
|
+
this.activeSlots -= 1;
|
|
724
|
+
const next = this.slotWaiters.shift();
|
|
725
|
+
if (next) next.resolve();
|
|
726
|
+
}
|
|
727
|
+
/** Claim the next deterministic node under the current combinator branch. */
|
|
728
|
+
claimCallId(_kind) {
|
|
729
|
+
const scope = this.currentScope();
|
|
730
|
+
scope.nextNode += 1;
|
|
731
|
+
return [...scope.path, scope.nextNode];
|
|
732
|
+
}
|
|
733
|
+
/** Resolve and verify one committed replay entry for the current call. */
|
|
734
|
+
replayEntry(callId, kind, fingerprint) {
|
|
735
|
+
const key = callId.join(".");
|
|
736
|
+
const replay = this.journal.get(key);
|
|
737
|
+
if (replay === void 0) return void 0;
|
|
738
|
+
if (replay.kind !== kind || replay.fingerprint !== fingerprint) throw new WorkflowError$1(`workflow journal diverged at ${callId}: the replayed ${kind} request does not match the committed request`, "JOURNAL_DIVERGENCE");
|
|
739
|
+
this.replayedJournalCallIds.add(key);
|
|
740
|
+
return replay;
|
|
741
|
+
}
|
|
742
|
+
/** Detect a resumed path that skipped a previously committed host call. */
|
|
743
|
+
unreplayedJournalError() {
|
|
744
|
+
for (const callId of this.journal.keys()) if (!this.replayedJournalCallIds.has(callId)) return new WorkflowError$1(`workflow journal diverged: the resumed path did not replay committed call ${callId}`, "JOURNAL_DIVERGENCE");
|
|
745
|
+
}
|
|
746
|
+
/** Append one completed host call unless this is a non-persistent smoke check. */
|
|
747
|
+
commitJournal(entry) {
|
|
748
|
+
if (this.validateOnly) return;
|
|
749
|
+
this.nextJournalOrdinal += 1;
|
|
750
|
+
this.observer.journalCommit({
|
|
751
|
+
...entry,
|
|
752
|
+
ordinal: this.nextJournalOrdinal
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
/** Atomically admit one direct-agent reservation for every new panel item. */
|
|
756
|
+
reservePanel(scopes) {
|
|
757
|
+
const reservations = scopes.flatMap((scope) => scope.reservation === void 0 ? [] : [scope.reservation]);
|
|
758
|
+
if (this.started + reservations.length > this.limits.maxTotalAgents) throw this.agentCapError(reservations.length);
|
|
759
|
+
this.started += reservations.length;
|
|
760
|
+
}
|
|
761
|
+
/** Consume a panel reservation or admit one standalone/nested agent call. */
|
|
762
|
+
spendAgentBudget() {
|
|
763
|
+
const reservation = this.callScopes.getStore()?.reservation;
|
|
764
|
+
if (reservation !== void 0 && reservation.available) {
|
|
765
|
+
reservation.available = false;
|
|
766
|
+
this.nextAgentSeq += 1;
|
|
767
|
+
return this.nextAgentSeq;
|
|
768
|
+
}
|
|
769
|
+
if (this.started >= this.limits.maxTotalAgents) throw this.agentCapError(1);
|
|
770
|
+
this.started += 1;
|
|
771
|
+
this.nextAgentSeq += 1;
|
|
772
|
+
return this.nextAgentSeq;
|
|
773
|
+
}
|
|
774
|
+
/** Build the fatal error for a budget admission that would exceed the cap. */
|
|
775
|
+
agentCapError(requested) {
|
|
776
|
+
return new WorkflowError$1(`this run cannot admit ${requested} agent${requested === 1 ? "" : "s"}: ${this.started} of ${this.limits.maxTotalAgents} logical-agent budget is already spent and the total agent cap (${this.limits.maxTotalAgents}) would be exceeded — raise the applicable maxTotalAgents limit if the scale is intentional`, "AGENT_CAP");
|
|
777
|
+
}
|
|
778
|
+
/** The `agent(prompt, opts)` hook. */
|
|
779
|
+
async agent(rawPrompt, rawOpts) {
|
|
780
|
+
this.throwIfCancelled();
|
|
781
|
+
if (typeof rawPrompt !== "string" || rawPrompt.length === 0) throw new WorkflowError$1("agent() requires a non-empty prompt string", "INVALID_ARGUMENT");
|
|
782
|
+
const prepared = this.readAgentOptions(rawOpts);
|
|
783
|
+
const opts = prepared.options;
|
|
784
|
+
const label = opts.label ?? defaultLabel(rawPrompt);
|
|
785
|
+
const phase = opts.phase ?? this.currentScope().currentPhase;
|
|
786
|
+
const callId = this.claimCallId("agent");
|
|
787
|
+
const fingerprint = fingerprintHostCall("agent", {
|
|
788
|
+
prompt: rawPrompt,
|
|
789
|
+
options: {
|
|
790
|
+
...opts,
|
|
791
|
+
label,
|
|
792
|
+
...phase === void 0 ? {} : { phase }
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
const replay = this.replayEntry(callId, "agent", fingerprint);
|
|
796
|
+
if (replay !== void 0) return replay.result;
|
|
797
|
+
const seq = this.spendAgentBudget();
|
|
798
|
+
if (this.validateOnly) return opts.schema !== void 0 ? cannedSchemaValue(opts.schema, this.limits.maxItemsPerCall) : "";
|
|
799
|
+
await this.acquireSlot();
|
|
800
|
+
try {
|
|
801
|
+
this.throwIfCancelled();
|
|
802
|
+
let run;
|
|
803
|
+
try {
|
|
804
|
+
run = await this.children.startAgent({
|
|
805
|
+
prompt: rawPrompt,
|
|
806
|
+
...prepared.providerSchema !== void 0 ? { schema: prepared.providerSchema } : {},
|
|
807
|
+
...opts.provider !== void 0 ? { provider: opts.provider } : {},
|
|
808
|
+
...opts.model !== void 0 ? { model: opts.model } : {}
|
|
809
|
+
});
|
|
810
|
+
} catch (error) {
|
|
811
|
+
if (this.isCancelled()) throw this.cancelledError();
|
|
812
|
+
throw new WorkflowError$1(`agent() could not start a child: ${renderThrown(error)}`, "AGENT_START", { cause: error });
|
|
813
|
+
}
|
|
814
|
+
if (this.isCancelled()) {
|
|
815
|
+
await run.dispose();
|
|
816
|
+
throw this.cancelledError();
|
|
817
|
+
}
|
|
818
|
+
const info = {
|
|
819
|
+
seq,
|
|
820
|
+
label,
|
|
821
|
+
...phase !== void 0 ? { phase } : {},
|
|
822
|
+
childId: (0, _deepseek_ai_dsh_session.SessionId)(run.id)
|
|
823
|
+
};
|
|
824
|
+
this.observer.agentStart(info);
|
|
825
|
+
try {
|
|
826
|
+
let result;
|
|
827
|
+
try {
|
|
828
|
+
result = await run.result;
|
|
829
|
+
} catch (error) {
|
|
830
|
+
if (this.isCancelled()) {
|
|
831
|
+
this.observer.agentEnd({
|
|
832
|
+
...info,
|
|
833
|
+
outcome: "cancelled"
|
|
834
|
+
});
|
|
835
|
+
throw this.cancelledError();
|
|
836
|
+
}
|
|
837
|
+
this.observer.agentEnd({
|
|
838
|
+
...info,
|
|
839
|
+
outcome: "failed"
|
|
840
|
+
});
|
|
841
|
+
throw new WorkflowError$1(`child agent run failed: ${renderThrown(error)}`, "AGENT_RESULT", { cause: error });
|
|
842
|
+
}
|
|
843
|
+
if (this.isCancelled()) {
|
|
844
|
+
this.observer.agentEnd({
|
|
845
|
+
...info,
|
|
846
|
+
outcome: "cancelled"
|
|
847
|
+
});
|
|
848
|
+
throw this.cancelledError();
|
|
849
|
+
}
|
|
850
|
+
if (result.stopReason === "completed") {
|
|
851
|
+
if (opts.schema !== void 0) {
|
|
852
|
+
if (result.structured === void 0) {
|
|
853
|
+
this.commitJournal({
|
|
854
|
+
kind: "agent",
|
|
855
|
+
seq,
|
|
856
|
+
callId,
|
|
857
|
+
fingerprint,
|
|
858
|
+
result: null
|
|
859
|
+
});
|
|
860
|
+
this.observer.agentEnd({
|
|
861
|
+
...info,
|
|
862
|
+
outcome: "failed"
|
|
863
|
+
});
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
const structured = result.structured;
|
|
867
|
+
if (!schemaValueMatches(opts.schema, structured)) {
|
|
868
|
+
this.commitJournal({
|
|
869
|
+
kind: "agent",
|
|
870
|
+
seq,
|
|
871
|
+
callId,
|
|
872
|
+
fingerprint,
|
|
873
|
+
result: null
|
|
874
|
+
});
|
|
875
|
+
this.observer.agentEnd({
|
|
876
|
+
...info,
|
|
877
|
+
outcome: "failed"
|
|
878
|
+
});
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
this.commitJournal({
|
|
882
|
+
kind: "agent",
|
|
883
|
+
seq,
|
|
884
|
+
callId,
|
|
885
|
+
fingerprint,
|
|
886
|
+
result: structured
|
|
887
|
+
});
|
|
888
|
+
this.observer.agentEnd({
|
|
889
|
+
...info,
|
|
890
|
+
outcome: "completed"
|
|
891
|
+
});
|
|
892
|
+
return result.structured;
|
|
893
|
+
}
|
|
894
|
+
const text = outputText(result.output);
|
|
895
|
+
this.commitJournal({
|
|
896
|
+
kind: "agent",
|
|
897
|
+
seq,
|
|
898
|
+
callId,
|
|
899
|
+
fingerprint,
|
|
900
|
+
result: text
|
|
901
|
+
});
|
|
902
|
+
this.observer.agentEnd({
|
|
903
|
+
...info,
|
|
904
|
+
outcome: "completed"
|
|
905
|
+
});
|
|
906
|
+
return text;
|
|
907
|
+
}
|
|
908
|
+
this.commitJournal({
|
|
909
|
+
kind: "agent",
|
|
910
|
+
seq,
|
|
911
|
+
callId,
|
|
912
|
+
fingerprint,
|
|
913
|
+
result: null
|
|
914
|
+
});
|
|
915
|
+
this.observer.agentEnd({
|
|
916
|
+
...info,
|
|
917
|
+
outcome: "failed"
|
|
918
|
+
});
|
|
919
|
+
return null;
|
|
920
|
+
} finally {
|
|
921
|
+
await run.dispose();
|
|
922
|
+
}
|
|
923
|
+
} finally {
|
|
924
|
+
this.releaseSlot();
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
/** Materialize + validate the `agent()` options bag from the realm. */
|
|
928
|
+
readAgentOptions(rawOpts) {
|
|
929
|
+
if (rawOpts === void 0) return { options: {} };
|
|
930
|
+
let opts;
|
|
931
|
+
try {
|
|
932
|
+
opts = materializeFromRealm(rawOpts, "agent() options");
|
|
933
|
+
} catch (error) {
|
|
934
|
+
/* v8 ignore next -- defensive rethrow arm: materializeFromRealm only throws MaterializeError */
|
|
935
|
+
if (!(error instanceof MaterializeError)) throw error;
|
|
936
|
+
throw new WorkflowError$1(`agent() options must be plain JSON data — ${error.message}`, "INVALID_ARGUMENT", { cause: error });
|
|
937
|
+
}
|
|
938
|
+
if (typeof opts !== "object" || opts === null || Array.isArray(opts)) throw new WorkflowError$1("agent() options must be an object", "INVALID_ARGUMENT");
|
|
939
|
+
const record = opts;
|
|
940
|
+
for (const key of Object.keys(record)) {
|
|
941
|
+
if (SUPPORTED_AGENT_OPTIONS.has(key)) continue;
|
|
942
|
+
if (DEFERRED_AGENT_OPTIONS.has(key)) throw new WorkflowError$1(`agent() option "${key}" is deferred and not supported by this engine (supported: label, phase, schema, provider, model)`, "UNSUPPORTED_OPTION");
|
|
943
|
+
throw new WorkflowError$1(`agent() option "${key}" is not recognized (supported: label, phase, schema, provider, model)`, "UNSUPPORTED_OPTION");
|
|
944
|
+
}
|
|
945
|
+
for (const key of [
|
|
946
|
+
"label",
|
|
947
|
+
"phase",
|
|
948
|
+
"provider",
|
|
949
|
+
"model"
|
|
950
|
+
]) if (record[key] !== void 0 && typeof record[key] !== "string") throw new WorkflowError$1(`agent() option "${key}" must be a string`, "INVALID_ARGUMENT");
|
|
951
|
+
let schema;
|
|
952
|
+
let providerSchema;
|
|
953
|
+
if (record.schema !== void 0) try {
|
|
954
|
+
const prepared = prepareObjectSchema(record.schema);
|
|
955
|
+
schema = prepared.authored;
|
|
956
|
+
providerSchema = prepared.provider;
|
|
957
|
+
} catch (error) {
|
|
958
|
+
/* v8 ignore next -- defensive rethrow arm: assertObjectJsonSchema only throws JsonSchemaError */
|
|
959
|
+
if (!(error instanceof _deepseek_ai_dsh_tools.JsonSchemaError)) throw error;
|
|
960
|
+
throw new WorkflowError$1(`agent() schema is outside the supported subset — ${error.message}`, "UNSUPPORTED_SCHEMA", { cause: error });
|
|
961
|
+
}
|
|
962
|
+
return {
|
|
963
|
+
options: {
|
|
964
|
+
...record.label !== void 0 ? { label: record.label } : {},
|
|
965
|
+
...record.phase !== void 0 ? { phase: record.phase } : {},
|
|
966
|
+
...record.provider !== void 0 ? { provider: record.provider } : {},
|
|
967
|
+
...record.model !== void 0 ? { model: record.model } : {},
|
|
968
|
+
...schema !== void 0 ? { schema } : {}
|
|
969
|
+
},
|
|
970
|
+
...providerSchema !== void 0 ? { providerSchema } : {}
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* The `parallel(items)` hook. Declarative job maps preflight and reserve as
|
|
975
|
+
* one atomic panel; arbitrary thunks admit their unknowable agent calls at
|
|
976
|
+
* execution time. Every item is a barrier slot; ordinary failures become
|
|
977
|
+
* `null` and fatal workflow errors propagate.
|
|
978
|
+
*/
|
|
979
|
+
async parallel(rawItems) {
|
|
980
|
+
this.throwIfCancelled();
|
|
981
|
+
if (!Array.isArray(rawItems)) throw new WorkflowError$1("parallel() requires an array of zero-argument functions or job maps", "INVALID_ARGUMENT");
|
|
982
|
+
this.assertItemCap(rawItems.length, "parallel()");
|
|
983
|
+
const panelPath = this.claimCallId("parallel");
|
|
984
|
+
const items = rawItems.map((item, index) => this.parallelItem(item, index, [
|
|
985
|
+
...panelPath,
|
|
986
|
+
index + 1,
|
|
987
|
+
1
|
|
988
|
+
]));
|
|
989
|
+
if (items.some((item) => item.kind === "job") && items.some((item) => item.kind === "thunk")) throw new WorkflowError$1("parallel() cannot mix function thunks and declarative job maps in one panel", "INVALID_ARGUMENT");
|
|
990
|
+
const inheritedPhase = this.currentScope().currentPhase;
|
|
991
|
+
const branches = items.map((item, index) => ({
|
|
992
|
+
item,
|
|
993
|
+
scope: {
|
|
994
|
+
path: [...panelPath, index + 1],
|
|
995
|
+
nextNode: 0,
|
|
996
|
+
...inheritedPhase === void 0 ? {} : { currentPhase: inheritedPhase },
|
|
997
|
+
...item.reservesAgent ? { reservation: { available: true } } : {}
|
|
998
|
+
}
|
|
999
|
+
}));
|
|
1000
|
+
this.reservePanel(branches.map((branch) => branch.scope));
|
|
1001
|
+
return Promise.all(branches.map(({ item, scope }) => this.callScopes.run(scope, async () => {
|
|
1002
|
+
try {
|
|
1003
|
+
return await item.run();
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
if ((0, _deepseek_ai_dsh_workflow.isFatalWorkflowError)(error)) throw error;
|
|
1006
|
+
return null;
|
|
1007
|
+
}
|
|
1008
|
+
})));
|
|
1009
|
+
}
|
|
1010
|
+
/** Accept one `parallel()` item as a zero-arg thunk or a Grok job map `{ prompt, ...opts }`. */
|
|
1011
|
+
parallelItem(item, index, callId) {
|
|
1012
|
+
if (typeof item === "function") return {
|
|
1013
|
+
run: item,
|
|
1014
|
+
kind: "thunk",
|
|
1015
|
+
reservesAgent: false
|
|
1016
|
+
};
|
|
1017
|
+
let job;
|
|
1018
|
+
try {
|
|
1019
|
+
job = materializeFromRealm(item, `parallel() item ${index}`);
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
/* v8 ignore next -- defensive rethrow arm: materializeFromRealm only throws MaterializeError */
|
|
1022
|
+
if (!(error instanceof MaterializeError)) throw error;
|
|
1023
|
+
throw new WorkflowError$1(`parallel() item ${index} must be a function or plain job map — ${error.message}`, "INVALID_ARGUMENT", { cause: error });
|
|
1024
|
+
}
|
|
1025
|
+
if (typeof job !== "object" || job === null || Array.isArray(job)) throw new WorkflowError$1(`parallel() item ${index} is not a function or job map`, "INVALID_ARGUMENT");
|
|
1026
|
+
const record = job;
|
|
1027
|
+
const prompt = record.prompt;
|
|
1028
|
+
if (typeof prompt !== "string" || prompt.length === 0) throw new WorkflowError$1(`parallel() job ${index} requires a non-empty "prompt" string`, "INVALID_ARGUMENT");
|
|
1029
|
+
const rawOpts = {};
|
|
1030
|
+
for (const key of Object.keys(record)) {
|
|
1031
|
+
if (key === "prompt") continue;
|
|
1032
|
+
rawOpts[key] = record[key];
|
|
1033
|
+
}
|
|
1034
|
+
const opts = this.readAgentOptions(rawOpts).options;
|
|
1035
|
+
const label = opts.label ?? defaultLabel(prompt);
|
|
1036
|
+
const phase = opts.phase ?? this.currentScope().currentPhase;
|
|
1037
|
+
const effectiveOptions = {
|
|
1038
|
+
...opts,
|
|
1039
|
+
label,
|
|
1040
|
+
...phase === void 0 ? {} : { phase }
|
|
1041
|
+
};
|
|
1042
|
+
return {
|
|
1043
|
+
run: () => this.agent(prompt, effectiveOptions),
|
|
1044
|
+
kind: "job",
|
|
1045
|
+
reservesAgent: this.replayEntry(callId, "agent", fingerprintHostCall("agent", {
|
|
1046
|
+
prompt,
|
|
1047
|
+
options: effectiveOptions
|
|
1048
|
+
})) === void 0
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
/** The `pipeline(items, ...stages)` hook: per-item stage chains, NO cross-stage barrier. */
|
|
1052
|
+
async pipeline(rawItems, rawStages) {
|
|
1053
|
+
this.throwIfCancelled();
|
|
1054
|
+
if (!Array.isArray(rawItems)) throw new WorkflowError$1("pipeline() requires an items array", "INVALID_ARGUMENT");
|
|
1055
|
+
this.assertItemCap(rawItems.length, "pipeline()");
|
|
1056
|
+
if (rawStages.length === 0) throw new WorkflowError$1("pipeline() requires at least one stage function", "INVALID_ARGUMENT");
|
|
1057
|
+
const stages = rawStages.map((stage, index) => {
|
|
1058
|
+
if (typeof stage !== "function") throw new WorkflowError$1(`pipeline() stage ${index} is not a function`, "INVALID_ARGUMENT");
|
|
1059
|
+
return stage;
|
|
1060
|
+
});
|
|
1061
|
+
const pipelinePath = this.claimCallId("pipeline");
|
|
1062
|
+
const inheritedPhase = this.currentScope().currentPhase;
|
|
1063
|
+
return Promise.all(rawItems.map((item, index) => this.callScopes.run({
|
|
1064
|
+
path: [...pipelinePath, index + 1],
|
|
1065
|
+
nextNode: 0,
|
|
1066
|
+
...inheritedPhase === void 0 ? {} : { currentPhase: inheritedPhase }
|
|
1067
|
+
}, async () => {
|
|
1068
|
+
let value = item;
|
|
1069
|
+
try {
|
|
1070
|
+
for (const stage of stages) value = await stage(value, item, index);
|
|
1071
|
+
return value;
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
if ((0, _deepseek_ai_dsh_workflow.isFatalWorkflowError)(error)) throw error;
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
})));
|
|
1077
|
+
}
|
|
1078
|
+
assertItemCap(length, hook) {
|
|
1079
|
+
if (length > this.limits.maxItemsPerCall) throw new WorkflowError$1(`${hook} received ${length} items — over the per-call cap (${this.limits.maxItemsPerCall}); split the work or raise maxItemsPerCall in the engine config`, "ITEM_CAP");
|
|
1080
|
+
}
|
|
1081
|
+
/** The `phase(title)` hook: sets the current label for subsequent `agent()` calls and notifies observers. */
|
|
1082
|
+
phase(title) {
|
|
1083
|
+
this.throwIfCancelled();
|
|
1084
|
+
if (typeof title !== "string" || title.length === 0) throw new WorkflowError$1("phase() requires a non-empty title string", "INVALID_ARGUMENT");
|
|
1085
|
+
const callId = this.claimCallId("phase");
|
|
1086
|
+
const fingerprint = fingerprintHostCall("phase", { title });
|
|
1087
|
+
const replay = this.replayEntry(callId, "phase", fingerprint);
|
|
1088
|
+
this.currentScope().currentPhase = title;
|
|
1089
|
+
if (replay !== void 0) return;
|
|
1090
|
+
this.observer.phase(title);
|
|
1091
|
+
this.commitJournal({
|
|
1092
|
+
kind: "phase",
|
|
1093
|
+
callId,
|
|
1094
|
+
fingerprint,
|
|
1095
|
+
title
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
/** The `log(message)` hook: narration to observers. */
|
|
1099
|
+
log(message) {
|
|
1100
|
+
this.throwIfCancelled();
|
|
1101
|
+
if (typeof message !== "string") throw new WorkflowError$1("log() requires a message string", "INVALID_ARGUMENT");
|
|
1102
|
+
const callId = this.claimCallId("log");
|
|
1103
|
+
const fingerprint = fingerprintHostCall("log", { message });
|
|
1104
|
+
if (this.replayEntry(callId, "log", fingerprint) !== void 0) return;
|
|
1105
|
+
this.observer.log(message);
|
|
1106
|
+
this.commitJournal({
|
|
1107
|
+
kind: "log",
|
|
1108
|
+
callId,
|
|
1109
|
+
fingerprint,
|
|
1110
|
+
message
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
/** Resolve the deterministic call scope for a root hook or combinator branch. */
|
|
1114
|
+
currentScope() {
|
|
1115
|
+
return this.callScopes.getStore();
|
|
1116
|
+
}
|
|
1117
|
+
/** The `complete(value)` hook: terminate the run successfully with a JSON value. */
|
|
1118
|
+
complete(value) {
|
|
1119
|
+
this.throwIfCancelled();
|
|
1120
|
+
try {
|
|
1121
|
+
this.completed = { value: value === void 0 ? null : this.materializeResult(value) };
|
|
1122
|
+
} catch (error) {
|
|
1123
|
+
/* v8 ignore next -- materializeResult totalizes every failure as WorkflowError. */
|
|
1124
|
+
if (!(error instanceof WorkflowError$1)) throw new Error("materializing a workflow result threw outside the documented error type", { cause: error });
|
|
1125
|
+
this.completionError = error;
|
|
1126
|
+
}
|
|
1127
|
+
this.completionGate.resolve();
|
|
1128
|
+
throw COMPLETE_SENTINEL;
|
|
1129
|
+
}
|
|
1130
|
+
/** The `budget()` hook: this run's logical agent budget and its spend. */
|
|
1131
|
+
budget() {
|
|
1132
|
+
this.throwIfCancelled();
|
|
1133
|
+
const total = this.limits.maxTotalAgents;
|
|
1134
|
+
const spent = this.started;
|
|
1135
|
+
return {
|
|
1136
|
+
total,
|
|
1137
|
+
spent,
|
|
1138
|
+
reserved: 0,
|
|
1139
|
+
remaining: Math.max(0, total - spent)
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
/** The `pause()`/`await_user()` gate: park the run until a resume message releases it. */
|
|
1143
|
+
async gate(rawKind, rawMessage, resumable) {
|
|
1144
|
+
this.throwIfCancelled();
|
|
1145
|
+
if (typeof rawKind !== "string" || rawKind.length === 0) throw new WorkflowError$1(`${resumable ? "await_user" : "pause"}() requires a non-empty kind string`, "INVALID_ARGUMENT");
|
|
1146
|
+
const kind = this.readGateKind(rawKind, resumable);
|
|
1147
|
+
const message = rawMessage === void 0 ? "" : typeof rawMessage === "string" ? rawMessage : void 0;
|
|
1148
|
+
if (message === void 0) throw new WorkflowError$1(`${resumable ? "await_user" : "pause"}() message must be a string`, "INVALID_ARGUMENT");
|
|
1149
|
+
if (this.validateOnly) {
|
|
1150
|
+
const diagnostic = `would ${resumable ? "await_user" : "pause"} (${kind}): ${message}`;
|
|
1151
|
+
this.observer.log(diagnostic);
|
|
1152
|
+
this.complete(diagnostic);
|
|
1153
|
+
}
|
|
1154
|
+
const callId = resumable ? this.claimCallId("await-user") : void 0;
|
|
1155
|
+
const fingerprint = resumable ? fingerprintHostCall("await-user", {
|
|
1156
|
+
kind,
|
|
1157
|
+
message
|
|
1158
|
+
}) : void 0;
|
|
1159
|
+
if (callId !== void 0 && fingerprint !== void 0 && this.replayEntry(callId, "await-user", fingerprint) !== void 0) return;
|
|
1160
|
+
if (this.gateResume !== void 0) throw new WorkflowError$1("workflow scripts may park on only one pause()/await_user() gate at a time", "INVALID_ARGUMENT");
|
|
1161
|
+
while (true) {
|
|
1162
|
+
this.throwIfCancelled();
|
|
1163
|
+
const gate = {
|
|
1164
|
+
kind,
|
|
1165
|
+
message,
|
|
1166
|
+
resumable
|
|
1167
|
+
};
|
|
1168
|
+
this.observer.gate(gate);
|
|
1169
|
+
await new Promise((resolve) => {
|
|
1170
|
+
this.gateResume = resolve;
|
|
1171
|
+
});
|
|
1172
|
+
this.gateResume = void 0;
|
|
1173
|
+
this.throwIfCancelled();
|
|
1174
|
+
if (resumable) {
|
|
1175
|
+
this.commitJournal({
|
|
1176
|
+
kind: "await-user",
|
|
1177
|
+
callId,
|
|
1178
|
+
fingerprint
|
|
1179
|
+
});
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
/** Normalize a gate kind with its `backoff`/`blocked` aliases. */
|
|
1185
|
+
readGateKind(rawKind, resumable) {
|
|
1186
|
+
switch (rawKind) {
|
|
1187
|
+
case "user":
|
|
1188
|
+
case "back_off":
|
|
1189
|
+
case "backoff":
|
|
1190
|
+
case "no_progress":
|
|
1191
|
+
case "verification":
|
|
1192
|
+
case "blocked":
|
|
1193
|
+
case "infra": break;
|
|
1194
|
+
default: throw new WorkflowError$1(`${resumable ? "await_user" : "pause"}() kind "${rawKind}" is not recognized (user, back_off, no_progress, verification, infra)`, "INVALID_ARGUMENT");
|
|
1195
|
+
}
|
|
1196
|
+
return rawKind === "backoff" ? "back_off" : rawKind === "blocked" ? "verification" : rawKind;
|
|
1197
|
+
}
|
|
1198
|
+
/** The `write_scratch_file(name, content)` hook: write one single-component scratch file. */
|
|
1199
|
+
async writeScratch(rawName, rawContent) {
|
|
1200
|
+
this.throwIfCancelled();
|
|
1201
|
+
const name = this.readScratchName(rawName);
|
|
1202
|
+
if (typeof rawContent !== "string") throw new WorkflowError$1("write_scratch_file() content must be a string", "INVALID_ARGUMENT");
|
|
1203
|
+
const callId = this.claimCallId("scratch-write");
|
|
1204
|
+
const fingerprint = fingerprintHostCall("scratch-write", {
|
|
1205
|
+
name,
|
|
1206
|
+
content: rawContent
|
|
1207
|
+
});
|
|
1208
|
+
if (this.replayEntry(callId, "scratch-write", fingerprint) !== void 0) return;
|
|
1209
|
+
await this.children.writeScratch(name, rawContent);
|
|
1210
|
+
this.commitJournal({
|
|
1211
|
+
kind: "scratch-write",
|
|
1212
|
+
callId,
|
|
1213
|
+
fingerprint
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
/** The `read_scratch_file(name)` hook: read one single-component scratch file. */
|
|
1217
|
+
async readScratch(rawName) {
|
|
1218
|
+
this.throwIfCancelled();
|
|
1219
|
+
const name = this.readScratchName(rawName);
|
|
1220
|
+
const callId = this.claimCallId("scratch-read");
|
|
1221
|
+
const fingerprint = fingerprintHostCall("scratch-read", { name });
|
|
1222
|
+
const replay = this.replayEntry(callId, "scratch-read", fingerprint);
|
|
1223
|
+
if (replay !== void 0) return replay.content;
|
|
1224
|
+
const content = await this.children.readScratch(name);
|
|
1225
|
+
this.commitJournal({
|
|
1226
|
+
kind: "scratch-read",
|
|
1227
|
+
callId,
|
|
1228
|
+
fingerprint,
|
|
1229
|
+
...content === void 0 ? {} : { content }
|
|
1230
|
+
});
|
|
1231
|
+
return content;
|
|
1232
|
+
}
|
|
1233
|
+
/** Validate a single-component scratch file name (no separators or traversal). */
|
|
1234
|
+
readScratchName(rawName) {
|
|
1235
|
+
if (typeof rawName !== "string" || !SCRATCH_NAME.test(rawName)) throw new WorkflowError$1("scratch file name must be a single component (letters, digits, . _ -)", "INVALID_ARGUMENT");
|
|
1236
|
+
return rawName;
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
/** Index a journal while rejecting ambiguous replay identities. */
|
|
1240
|
+
function indexJournal(entries) {
|
|
1241
|
+
const byCallId = /* @__PURE__ */ new Map();
|
|
1242
|
+
const agentSequences = /* @__PURE__ */ new Set();
|
|
1243
|
+
let priorOrdinal = 0;
|
|
1244
|
+
for (const entry of entries ?? []) {
|
|
1245
|
+
if (!Number.isSafeInteger(entry.ordinal) || entry.ordinal !== priorOrdinal + 1) throw new WorkflowError$1("workflow journal entry ordinal must be the next positive safe integer", "JOURNAL_DIVERGENCE");
|
|
1246
|
+
priorOrdinal = entry.ordinal;
|
|
1247
|
+
if (!Array.isArray(entry.callId) || entry.callId.length === 0 || entry.callId.some((part) => !Number.isSafeInteger(part) || part <= 0) || byCallId.has(entry.callId.join("."))) throw new WorkflowError$1(`workflow journal repeats or omits call identity ${JSON.stringify(entry.callId)}`, "JOURNAL_DIVERGENCE");
|
|
1248
|
+
if (entry.kind === "agent" && (!Number.isSafeInteger(entry.seq) || entry.seq < 1)) throw new WorkflowError$1("workflow journal agent seq must be a positive safe integer", "JOURNAL_DIVERGENCE");
|
|
1249
|
+
if (entry.kind === "agent" && agentSequences.has(entry.seq)) throw new WorkflowError$1(`workflow journal repeats agent sequence ${entry.seq}`, "JOURNAL_DIVERGENCE");
|
|
1250
|
+
if (!/^[a-f0-9]{64}$/u.test(entry.fingerprint)) throw new WorkflowError$1("workflow journal fingerprint must be a lowercase SHA-256 digest", "JOURNAL_DIVERGENCE");
|
|
1251
|
+
byCallId.set(entry.callId.join("."), entry);
|
|
1252
|
+
if (entry.kind === "agent") agentSequences.add(entry.seq);
|
|
1253
|
+
}
|
|
1254
|
+
return byCallId;
|
|
1255
|
+
}
|
|
1256
|
+
/** SHA-256 one canonical effective host request for journal replay validation. */
|
|
1257
|
+
function fingerprintHostCall(kind, request) {
|
|
1258
|
+
return (0, node_crypto.createHash)("sha256").update(canonicalJson({
|
|
1259
|
+
kind,
|
|
1260
|
+
request
|
|
1261
|
+
})).digest("hex");
|
|
1262
|
+
}
|
|
1263
|
+
/** Serialize JSON-like data with recursively sorted object keys. */
|
|
1264
|
+
function canonicalJson(value) {
|
|
1265
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
1266
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
1267
|
+
return `{${Object.entries(value).filter(([, child]) => child !== void 0).sort(([left], [right]) => left < right ? -1 : 1).map(([key, child]) => `${JSON.stringify(key)}:${canonicalJson(child)}`).join(",")}}`;
|
|
1268
|
+
}
|
|
1269
|
+
/** Single-component scratch file name grammar. */
|
|
1270
|
+
const SCRATCH_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1271
|
+
/** Sentinel thrown by `complete()`; drive() recognizes it to terminate successfully. */
|
|
1272
|
+
const COMPLETE_SENTINEL = /* @__PURE__ */ new Error("workflow completed");
|
|
1273
|
+
//#endregion
|
|
1274
|
+
//#region vendor/workflow-engine/session.ts
|
|
1275
|
+
/**
|
|
1276
|
+
* The worker-side handle for one started child agent ({@link ChildHandle}):
|
|
1277
|
+
* every member is an RPC to the host keyed by this call's `callId`, resolved
|
|
1278
|
+
* by the session's message handler through the bridge's pending entry.
|
|
1279
|
+
*/
|
|
1280
|
+
var RpcChildHandle = class {
|
|
1281
|
+
constructor(post, callId, entry, id) {
|
|
1282
|
+
this.post = post;
|
|
1283
|
+
this.callId = callId;
|
|
1284
|
+
this.entry = entry;
|
|
1285
|
+
this.id = id;
|
|
1286
|
+
this.result = entry.settled.promise;
|
|
1287
|
+
}
|
|
1288
|
+
dispose() {
|
|
1289
|
+
this.post("child-dispose", { callId: this.callId });
|
|
1290
|
+
return this.entry.disposed.promise;
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
/**
|
|
1294
|
+
* The worker-side child-RPC bridge ({@link ChildPort}): allocates callIds,
|
|
1295
|
+
* posts the start/dispose RPCs, and owns the per-call pending
|
|
1296
|
+
* book-keeping the session's message handler settles via the `onChild*`
|
|
1297
|
+
* entry points.
|
|
1298
|
+
*/
|
|
1299
|
+
var ChildRpcBridge = class {
|
|
1300
|
+
constructor(post) {
|
|
1301
|
+
this.post = post;
|
|
1302
|
+
this.nextCallId = 0;
|
|
1303
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
1304
|
+
this.pendingScratch = /* @__PURE__ */ new Map();
|
|
1305
|
+
}
|
|
1306
|
+
async startAgent(request) {
|
|
1307
|
+
this.nextCallId += 1;
|
|
1308
|
+
const callId = this.nextCallId;
|
|
1309
|
+
const entry = {
|
|
1310
|
+
started: Promise.withResolvers(),
|
|
1311
|
+
settled: Promise.withResolvers(),
|
|
1312
|
+
disposed: Promise.withResolvers()
|
|
1313
|
+
};
|
|
1314
|
+
entry.settled.promise.catch(() => {});
|
|
1315
|
+
this.pending.set(callId, entry);
|
|
1316
|
+
this.post("child-start", {
|
|
1317
|
+
callId,
|
|
1318
|
+
request
|
|
1319
|
+
});
|
|
1320
|
+
const childId = await entry.started.promise;
|
|
1321
|
+
return new RpcChildHandle(this.post, callId, entry, childId);
|
|
1322
|
+
}
|
|
1323
|
+
async writeScratch(name, content) {
|
|
1324
|
+
const callId = this.claimCallId();
|
|
1325
|
+
const resolve = Promise.withResolvers();
|
|
1326
|
+
this.pendingScratch.set(callId, resolve);
|
|
1327
|
+
this.post("scratch-write", {
|
|
1328
|
+
callId,
|
|
1329
|
+
name,
|
|
1330
|
+
content
|
|
1331
|
+
});
|
|
1332
|
+
await resolve.promise;
|
|
1333
|
+
}
|
|
1334
|
+
async readScratch(name) {
|
|
1335
|
+
const callId = this.claimCallId();
|
|
1336
|
+
const resolve = Promise.withResolvers();
|
|
1337
|
+
this.pendingScratch.set(callId, resolve);
|
|
1338
|
+
this.post("scratch-read", {
|
|
1339
|
+
callId,
|
|
1340
|
+
name
|
|
1341
|
+
});
|
|
1342
|
+
return (await resolve.promise).content;
|
|
1343
|
+
}
|
|
1344
|
+
claimCallId() {
|
|
1345
|
+
this.nextCallId += 1;
|
|
1346
|
+
return this.nextCallId;
|
|
1347
|
+
}
|
|
1348
|
+
/** The host established a published child; releases the `startAgent` await. */
|
|
1349
|
+
onChildStarted(callId, childId) {
|
|
1350
|
+
this.pending.get(callId)?.started.resolve(childId);
|
|
1351
|
+
}
|
|
1352
|
+
/** Asynchronous provider start failed; reject and retire the pending RPC. */
|
|
1353
|
+
onChildStartError(callId, rendered) {
|
|
1354
|
+
const entry = this.pending.get(callId);
|
|
1355
|
+
this.pending.delete(callId);
|
|
1356
|
+
entry?.started.reject(new Error(rendered));
|
|
1357
|
+
}
|
|
1358
|
+
/** The child's terminal result arrived. */
|
|
1359
|
+
onChildSettled(callId, result) {
|
|
1360
|
+
this.pending.get(callId)?.settled.resolve(result);
|
|
1361
|
+
}
|
|
1362
|
+
/** The child's `result` rejected host-side (an infrastructure fault, relayed as fatal). */
|
|
1363
|
+
onChildFailed(callId, rendered) {
|
|
1364
|
+
this.pending.get(callId)?.settled.reject(new Error(rendered));
|
|
1365
|
+
}
|
|
1366
|
+
/** The host acked the dispose; the call's book-keeping is complete. */
|
|
1367
|
+
onChildDisposed(callId) {
|
|
1368
|
+
const entry = this.pending.get(callId);
|
|
1369
|
+
this.pending.delete(callId);
|
|
1370
|
+
entry?.disposed.resolve();
|
|
1371
|
+
}
|
|
1372
|
+
/** The host completed a scratch write. */
|
|
1373
|
+
onScratchWritten(callId) {
|
|
1374
|
+
const entry = this.pendingScratch.get(callId);
|
|
1375
|
+
this.pendingScratch.delete(callId);
|
|
1376
|
+
entry?.resolve({});
|
|
1377
|
+
}
|
|
1378
|
+
/** The host completed a scratch read (content absent = file missing). */
|
|
1379
|
+
onScratchReadResult(callId, content) {
|
|
1380
|
+
const entry = this.pendingScratch.get(callId);
|
|
1381
|
+
this.pendingScratch.delete(callId);
|
|
1382
|
+
entry?.resolve({ ...content !== void 0 ? { content } : {} });
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
/**
|
|
1386
|
+
* Narrow the nullable `parentPort` the bootstrap reads from
|
|
1387
|
+
* `node:worker_threads`.
|
|
1388
|
+
* @param port - `parentPort` as imported (null on the main thread).
|
|
1389
|
+
* @returns the port, non-null.
|
|
1390
|
+
*/
|
|
1391
|
+
function requireParentPort(port) {
|
|
1392
|
+
if (port === null) throw new Error("the workflow worker entry must be loaded inside a worker thread (no parentPort)");
|
|
1393
|
+
return port;
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Run one workflow script to settlement against `port`, posting the terminal result message
|
|
1397
|
+
* exactly once; resolves after that post (stray children may still be winding down through the
|
|
1398
|
+
* port — the host owns their teardown and ultimately terminates the thread). It never rejects:
|
|
1399
|
+
* constructor failure becomes an error result. Host pre-parse makes syntax failure here a likely
|
|
1400
|
+
* Node-version skew, but the session still reports it instead of dying silently.
|
|
1401
|
+
* @param port - the channel to the host (the real `parentPort`, or one side
|
|
1402
|
+
* of an in-process `MessageChannel` in tests).
|
|
1403
|
+
* @param init - the run payload the host provided as `workerData`.
|
|
1404
|
+
*/
|
|
1405
|
+
async function runWorkerSession(port, init) {
|
|
1406
|
+
const post = (type, payload) => {
|
|
1407
|
+
port.postMessage({
|
|
1408
|
+
type,
|
|
1409
|
+
...payload
|
|
1410
|
+
});
|
|
1411
|
+
};
|
|
1412
|
+
const children = new ChildRpcBridge(post);
|
|
1413
|
+
const observer = {
|
|
1414
|
+
phase: (title) => {
|
|
1415
|
+
post("phase", { title });
|
|
1416
|
+
},
|
|
1417
|
+
log: (message) => {
|
|
1418
|
+
post("log", { message });
|
|
1419
|
+
},
|
|
1420
|
+
agentStart: (info) => {
|
|
1421
|
+
post("agent-start", { info });
|
|
1422
|
+
},
|
|
1423
|
+
agentEnd: (info) => {
|
|
1424
|
+
post("agent-end", { info });
|
|
1425
|
+
},
|
|
1426
|
+
gate: (gate) => {
|
|
1427
|
+
post("gate", { gate });
|
|
1428
|
+
},
|
|
1429
|
+
journalCommit: (entry) => {
|
|
1430
|
+
post("journal-commit", { entry });
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
let execution;
|
|
1434
|
+
try {
|
|
1435
|
+
execution = new WorkflowExecution(init.meta, init.body, init.args, init.limits, observer, children, init.journal, init.validateOnly, init.initialAgentSpend, init.initialAgentSeq);
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
post("ready", {});
|
|
1438
|
+
post("result", { result: {
|
|
1439
|
+
value: null,
|
|
1440
|
+
stopReason: "error",
|
|
1441
|
+
error: renderThrown(error),
|
|
1442
|
+
...error instanceof _deepseek_ai_dsh_workflow.WorkflowError ? { errorCode: error.code } : {},
|
|
1443
|
+
agentsStarted: Math.max(init.initialAgentSpend ?? 0, init.journal?.filter((entry) => entry.kind === "agent").length ?? 0)
|
|
1444
|
+
} });
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
const gate = Promise.withResolvers();
|
|
1448
|
+
port.on("message", (message) => {
|
|
1449
|
+
switch (message.type) {
|
|
1450
|
+
case "go":
|
|
1451
|
+
gate.resolve();
|
|
1452
|
+
break;
|
|
1453
|
+
case "cancel":
|
|
1454
|
+
execution.cancel(message.reason);
|
|
1455
|
+
gate.resolve();
|
|
1456
|
+
break;
|
|
1457
|
+
case "resume":
|
|
1458
|
+
execution.resume();
|
|
1459
|
+
break;
|
|
1460
|
+
case "child-started":
|
|
1461
|
+
children.onChildStarted(message.callId, message.childId);
|
|
1462
|
+
break;
|
|
1463
|
+
case "child-start-error":
|
|
1464
|
+
children.onChildStartError(message.callId, message.rendered);
|
|
1465
|
+
break;
|
|
1466
|
+
case "child-settled":
|
|
1467
|
+
children.onChildSettled(message.callId, message.result);
|
|
1468
|
+
break;
|
|
1469
|
+
case "child-failed":
|
|
1470
|
+
children.onChildFailed(message.callId, message.rendered);
|
|
1471
|
+
break;
|
|
1472
|
+
case "child-disposed":
|
|
1473
|
+
children.onChildDisposed(message.callId);
|
|
1474
|
+
break;
|
|
1475
|
+
case "scratch-written":
|
|
1476
|
+
children.onScratchWritten(message.callId);
|
|
1477
|
+
break;
|
|
1478
|
+
case "scratch-read-result":
|
|
1479
|
+
children.onScratchReadResult(message.callId, message.content);
|
|
1480
|
+
break;
|
|
1481
|
+
/* v8 ignore next 2 -- closed engine-owned union; the arm only makes adding a message type a compile error */
|
|
1482
|
+
default: (0, _deepseek_ai_dsh_llm.assertNever)(message, "host-to-worker message");
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
post("ready", {});
|
|
1486
|
+
await gate.promise;
|
|
1487
|
+
post("result", { result: await execution.drive() });
|
|
1488
|
+
}
|
|
1489
|
+
//#endregion
|
|
1490
|
+
//#region vendor/workflow-engine/worker.ts
|
|
1491
|
+
/**
|
|
1492
|
+
* Single-statement worker entry that boots `runWorkerSession` on real `parentPort`. Logic remains in
|
|
1493
|
+
* the session module for in-process MessageChannel coverage; importing this entry on the main thread
|
|
1494
|
+
* exercises `requireParentPort`'s failure path.
|
|
1495
|
+
* @module @deepseek-ai/dsh-workflow-worker-thread/worker
|
|
1496
|
+
*/
|
|
1497
|
+
runWorkerSession(requireParentPort(node_worker_threads.parentPort), node_worker_threads.workerData);
|
|
1498
|
+
//#endregion
|
|
1499
|
+
|
|
1500
|
+
//# sourceMappingURL=worker.cjs.map
|