@deepseek-ai/dsh-workflow-ptc 0.1.6-alpha.1

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/lib/index.js ADDED
@@ -0,0 +1,658 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { availableParallelism } from "node:os";
3
+ import * as vm from "node:vm";
4
+ import z from "@deepseek-ai/schemastery";
5
+ import WorkflowEngine, { WorkflowError, WorkflowRunId } from "@deepseek-ai/dsh-workflow";
6
+ import { SessionId } from "@deepseek-ai/dsh-session";
7
+ import { assertObjectJsonSchema } from "@deepseek-ai/dsh-tools";
8
+ import { assertNever, snapshotJsonValue } from "@deepseek-ai/dsh-util-values";
9
+ //#region lib/types/guest-source.js
10
+ /** Generated by scripts/gen-workflow-guest.ts. Do not edit directly. */
11
+ /** Self-contained ESM guest transported into the mounted PTC runtime. */
12
+ const WORKFLOW_GUEST_SOURCE = "import * as vm from \"node:vm\";\n//#region packages/workflow/workflow-ptc/src/realm.ts\n/**\n* Materializes script VM values as plain JSON and renders thrown values.\n* Getters and proxy traps may execute inside the confined Node process;\n* process isolation and cancellation belong to PTC, not the VM.\n* @module @deepseek-ai/dsh-workflow-ptc/realm\n*/\n/** Thrown by {@link materializeFromRealm}; the caller wraps it into the right `WorkflowError` code. */\nvar MaterializeError = class extends Error {\n path;\n reason;\n constructor(path, reason) {\n super(`${path}: ${reason}`);\n this.path = path;\n this.reason = reason;\n this.name = \"MaterializeError\";\n }\n};\n/**\n* Render a thrown value to failure text without ever throwing: prefer the\n* `stack` (host or realm — a realm error's `stack` is a plain string read),\n* fall back to `message`, then `String()`. Reading those properties MAY run\n* script code; if that code itself throws, a fixed label is returned instead.\n* @param error - any value thrown in the host or guest realm.\n* @returns human-readable text for the failure report; prefers the stack.\n*/\nfunction renderThrown(error) {\n try {\n const stack = error?.stack;\n if (typeof stack === \"string\" && stack.length > 0) return stack;\n const message = error?.message;\n if (typeof message === \"string\" && message.length > 0) return message;\n return String(error);\n } catch {\n return \"[unrenderable thrown value]\";\n }\n}\n/**\n* Whether an object's prototype chain represents a plain data object: `null`, or a prototype\n* whose own prototype is `null` (the realm's `Object.prototype` — which we\n* cannot compare by identity across realms). A `Date`/`Map`/class instance\n* has a longer chain and is rejected.\n*/\nfunction hasPlainPrototype(value) {\n const proto = Object.getPrototypeOf(value);\n if (proto === null) return true;\n return Object.getPrototypeOf(proto) === null;\n}\n/**\n* Copy `value` (typically from the vm realm) into plain host JSON data. Root `undefined` is\n* returned unchanged; nested `undefined` and values JSON cannot represent losslessly fail\n* with the offending path. Property accessors run normally, and a throwing read is wrapped\n* with its rendered failure.\n*\n* @param value - the realm value to materialize.\n* @param root - the path label for the root value (error messages).\n* @returns the host-realm copy (plain objects/arrays/scalars only).\n* @throws {@link MaterializeError} for unsupported values, cycles, sparse arrays, exotic\n* prototypes, or property reads that throw.\n*/\nfunction materializeFromRealm(value, root = \"value\") {\n if (value === void 0) return void 0;\n try {\n return materialize(value, root, /* @__PURE__ */ new Set());\n } catch (error) {\n if (error instanceof MaterializeError) throw error;\n throw new MaterializeError(root, `reading the value threw: ${renderThrown(error)}`);\n }\n}\nfunction materialize(value, path, seen) {\n switch (typeof value) {\n case \"boolean\":\n case \"string\": return value;\n case \"number\":\n if (!Number.isFinite(value)) throw new MaterializeError(path, \"non-finite numbers are not JSON data\");\n return value;\n case \"bigint\": throw new MaterializeError(path, \"bigints are not JSON data\");\n case \"function\": throw new MaterializeError(path, \"functions are not plain JSON data\");\n case \"symbol\": throw new MaterializeError(path, \"symbols are not plain JSON data\");\n case \"undefined\": throw new MaterializeError(path, \"undefined is not JSON data\");\n case \"object\": break;\n }\n if (value === null) return null;\n const objectValue = value;\n if (seen.has(objectValue)) throw new MaterializeError(path, \"circular references are not JSON data\");\n seen.add(objectValue);\n try {\n if (Array.isArray(objectValue)) return materializeArray(objectValue, path, seen);\n return materializeObject(objectValue, path, seen);\n } finally {\n seen.delete(objectValue);\n }\n}\nfunction materializeArray(value, path, seen) {\n const out = [];\n for (let index = 0; index < value.length; index++) {\n if (!(index in value)) throw new MaterializeError(`${path}[${index}]`, \"sparse arrays are not JSON data\");\n out.push(materialize(value[index], `${path}[${index}]`, seen));\n }\n for (const key of Object.keys(value)) {\n const index = Number(key);\n if (!Number.isInteger(index) || index < 0 || index >= value.length) throw new MaterializeError(`${path}.${key}`, \"arrays with non-index properties are not JSON data\");\n }\n if (Object.getOwnPropertySymbols(value).length > 0) throw new MaterializeError(path, \"symbol-keyed properties are not plain JSON data\");\n return out;\n}\nfunction materializeObject(value, path, seen) {\n if (!hasPlainPrototype(value)) throw new MaterializeError(path, \"only plain objects and arrays are JSON data (exotic prototype)\");\n if (Object.getOwnPropertySymbols(value).length > 0) throw new MaterializeError(path, \"symbol-keyed properties are not plain JSON data\");\n const out = {};\n for (const key of Object.keys(value)) Object.defineProperty(out, key, {\n value: materialize(value[key], `${path}.${key}`, seen),\n enumerable: true,\n writable: true,\n configurable: true\n });\n return out;\n}\n//#endregion\n//#region packages/util/brand/src/index.ts\n/**\n* Apply a compile-time string brand without changing the value.\n* @param value - string admitted by the domain that owns the target brand.\n* @returns the same string with the requested compile-time brand.\n*/\nfunction brandString(value) {\n return value;\n}\n//#endregion\n//#region packages/llm/llm/src/error.ts\n/**\n* Harness error base with a stable machine-routable code and chained cause.\n* Package errors extend it so tool results and replay can retain failure class.\n* @module @deepseek-ai/dsh-llm/error\n*/\n/**\n* Base class for all harness errors. Carries a `code` (stable, programmatic —\n* e.g. `NO_ADAPTER`, `INVALID_ARGS`, `INVARIANT`) distinct from the\n* human-readable `message`, and supports `cause` chaining via the standard\n* `ErrorOptions`. `name` defaults to the subclass constructor name.\n*/\nvar HarnessError = class extends Error {\n /** Stable machine-routable failure class (e.g. `RATE_LIMIT`); route on this, never by parsing `message`. */\n code;\n constructor(message, code, options) {\n super(message, options);\n this.code = code;\n this.name = new.target.name;\n }\n};\nnew RegExp(String.raw`(?:^|[^a-z0-9])context[\\s_-](?:length|window)[\\s_-]` + String.raw`(?:exceed(?:ed|s)?|overflow(?:ed)?|limit[\\s_-]exceeded)(?:$|[^a-z0-9])`, \"i\");\nnew RegExp(String.raw`\\b(?:request|prompt|input|messages?)\\s+(?:is\\s+|are\\s+)?` + String.raw`too\\s+(?:large|long)\\s+for\\s+(?:(?:this|the)\\s+)?` + String.raw`(?:model(?:'s)?\\s+)?context(?:\\s+window)?\\b`, \"i\");\nnew RegExp(String.raw`\\b(?:input|prompt|request|messages?)\\b.{0,40}` + String.raw`\\b(?:exceed(?:s|ed)?|overflows?|is\\s+larger\\s+than)\\b.{0,40}` + String.raw`\\b(?:the\\s+)?(?:model(?:'s)?\\s+)?context(?:\\s+(?:length|window))?\\b`, \"i\");\n//#endregion\n//#region packages/util/values/src/index.ts\n/**\n* Mark an unreachable closed-union branch.\n* @param value - impossible value; an unhandled typed variant fails at the call site.\n* @param context - optional switch-site label included in the failure message.\n* @returns never; a runtime value that escaped its type always throws.\n*/\nfunction assertNever(value, context) {\n const rendered = JSON.stringify(value) ?? String(value);\n throw new Error(`unreachable variant${context ? ` in ${context}` : \"\"}: ${rendered}`);\n}\n/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */\nfunction hasIntrinsicConstructor$1(prototype, name) {\n const constructor = Object.getOwnPropertyDescriptor(prototype, \"constructor\")?.value;\n if (typeof constructor !== \"function\") return false;\n try {\n return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;\n } catch {\n return false;\n }\n}\n/** Whether a candidate is one realm's intrinsic `Object.prototype`. */\nfunction isIntrinsicObjectPrototype$1(value) {\n return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor$1(value, \"Object\");\n}\n/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */\nfunction hasPlainArrayPrototype$1(value) {\n const prototype = Object.getPrototypeOf(value);\n if (!Array.isArray(prototype) || !hasIntrinsicConstructor$1(prototype, \"Array\")) return false;\n const objectPrototype = Object.getPrototypeOf(prototype);\n return typeof objectPrototype === \"object\" && objectPrototype !== null && isIntrinsicObjectPrototype$1(objectPrototype);\n}\n/** Whether an object is a plain or null-prototype record from any JavaScript realm. */\nfunction hasPlainObjectPrototype(value) {\n const prototype = Object.getPrototypeOf(value);\n return prototype === null || typeof prototype === \"object\" && isIntrinsicObjectPrototype$1(prototype);\n}\n/** Return every JSON-visible object key, or reject own data JSON would discard. */\nfunction enumerableStringKeys(value) {\n const keys = Reflect.ownKeys(value);\n if (keys.some((key) => typeof key !== \"string\" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;\n return keys;\n}\n/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */\nfunction walkJsonValue(value, detach) {\n const ancestors = /* @__PURE__ */ new Set();\n let root;\n const assign = (destination, item) => {\n if (destination === void 0) return;\n if (destination.kind === \"root\") root = item;\n else if (destination.kind === \"array\") destination.target[destination.index] = item;\n else Object.defineProperty(destination.target, destination.key, {\n value: item,\n enumerable: true,\n configurable: true,\n writable: true\n });\n };\n const tasks = [{\n kind: \"visit\",\n value,\n ...detach ? { destination: { kind: \"root\" } } : {}\n }];\n for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {\n if (task.kind === \"leave\") {\n ancestors.delete(task.source);\n continue;\n }\n if (task.kind === \"array-item\") {\n if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;\n tasks.push({\n kind: \"visit\",\n value: task.source[task.index],\n ...task.target === void 0 ? {} : { destination: {\n kind: \"array\",\n target: task.target,\n index: task.index\n } }\n });\n continue;\n }\n if (task.kind === \"object-property\") {\n tasks.push({\n kind: \"visit\",\n value: task.source[task.key],\n ...task.target === void 0 ? {} : { destination: {\n kind: \"object\",\n target: task.target,\n key: task.key\n } }\n });\n continue;\n }\n const current = task.value;\n if (current === null) {\n assign(task.destination, null);\n continue;\n }\n if (typeof current === \"boolean\" || typeof current === \"string\") {\n assign(task.destination, current);\n continue;\n }\n if (typeof current === \"number\") {\n if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;\n assign(task.destination, current);\n continue;\n }\n if (typeof current !== \"object\") return void 0;\n if (ancestors.has(current)) return void 0;\n if (Array.isArray(current)) {\n if (!hasPlainArrayPrototype$1(current)) return void 0;\n const length = current.length;\n if (Reflect.ownKeys(current).length !== length + 1) return void 0;\n const target = detach ? [] : void 0;\n if (target !== void 0) assign(task.destination, target);\n ancestors.add(current);\n tasks.push({\n kind: \"leave\",\n source: current\n });\n for (let index = length - 1; index >= 0; index--) tasks.push({\n kind: \"array-item\",\n source: current,\n index,\n ...target === void 0 ? {} : { target }\n });\n continue;\n }\n if (!hasPlainObjectPrototype(current)) return void 0;\n const keys = enumerableStringKeys(current);\n if (keys === void 0) return void 0;\n const target = detach ? {} : void 0;\n if (target !== void 0) assign(task.destination, target);\n ancestors.add(current);\n tasks.push({\n kind: \"leave\",\n source: current\n });\n for (let index = keys.length - 1; index >= 0; index--) {\n const key = keys[index];\n /* v8 ignore next -- the loop is bounded by the captured key count. */\n if (key === void 0) return void 0;\n tasks.push({\n kind: \"object-property\",\n source: current,\n key,\n ...target === void 0 ? {} : { target }\n });\n }\n }\n return detach ? root : true;\n}\n/**\n* Test the same lossless JSON rules as {@link snapshotJsonValue} without detaching the value.\n* @param value - candidate value to test.\n* @returns whether the value survives a JSON round trip without loss.\n*/\nfunction isJsonValue(value) {\n return walkJsonValue(value, false) === true;\n}\n//#endregion\n//#region packages/core/tools/src/json-schema.ts\n/**\n* Enforced JSON Schema subset shared by tool outputs, generated PTC mode\n* types, subagents, and workflows. The subset accepts any JSON root, an\n* annotation-only schema for unconstrained JSON, one scalar `type`, object\n* `properties`/`required`/boolean `additionalProperties`, array `items`,\n* type-correct scalar `enum`/`const`, and exact-one `oneOf`.\n*\n* Unsupported or misplaced keywords reject rather than being accepted without\n* enforcement. Consumers that require an object root apply\n* {@link assertObjectJsonSchema} before accepting input.\n* @module dsh-tools/json-schema\n*/\n/**\n* Thrown when a raw schema falls outside the enforced subset. `violations`\n* lists every offending path instead of stopping at the first author error.\n*/\nvar JsonSchemaError = class extends HarnessError {\n /** Individual schema violations in walk order. */\n violations;\n constructor(violations) {\n super(`unsupported JSON schema: ${violations.join(\"; \")}`, \"UNSUPPORTED_SCHEMA\");\n this.name = \"JsonSchemaError\";\n this.violations = violations;\n }\n};\nconst CONSTRAINT_KEYWORDS = new Set([\n \"type\",\n \"oneOf\",\n \"properties\",\n \"required\",\n \"additionalProperties\",\n \"items\",\n \"enum\",\n \"const\"\n]);\nconst ANNOTATION_KEYWORDS = new Set([\n \"description\",\n \"title\",\n \"default\",\n \"examples\"\n]);\nconst SCHEMA_TYPES = [\n \"object\",\n \"array\",\n \"string\",\n \"number\",\n \"integer\",\n \"boolean\",\n \"null\"\n];\n/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */\nfunction hasIntrinsicConstructor(prototype, name) {\n const constructor = Object.getOwnPropertyDescriptor(prototype, \"constructor\")?.value;\n if (typeof constructor !== \"function\") return false;\n try {\n return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;\n } catch {\n return false;\n }\n}\n/** Whether a candidate is one realm's intrinsic `Object.prototype`. */\nfunction isIntrinsicObjectPrototype(value) {\n return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, \"Object\");\n}\n/**\n* Test for a realm-agnostic plain JSON record without accepting arrays or\n* exotic objects.\n* @param value - candidate record from any JavaScript realm.\n* @returns Whether the value has a plain-object prototype chain.\n*/\nfunction isPlainJsonRecord(value) {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n try {\n const prototype = Object.getPrototypeOf(value);\n return prototype === null || typeof prototype === \"object\" && isIntrinsicObjectPrototype(prototype);\n } catch {\n return false;\n }\n}\n/** Whether an array uses one realm's intrinsic `Array.prototype`. */\nfunction hasPlainArrayPrototype(value) {\n const prototype = Object.getPrototypeOf(value);\n if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, \"Array\")) return false;\n const objectPrototype = Object.getPrototypeOf(prototype);\n return typeof objectPrototype === \"object\" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);\n}\n/** Return whether a record contains only own enumerable string keys. */\nfunction hasOnlyEnumerableStringKeys(value) {\n try {\n return Reflect.ownKeys(value).every((key) => typeof key === \"string\" && Object.prototype.propertyIsEnumerable.call(value, key));\n } catch {\n return false;\n }\n}\n/**\n* Test for an ordinary schema record whose keys survive JSON projection.\n* @param value - candidate record from any JavaScript realm.\n* @returns Whether the record has an intrinsic prototype and only own enumerable string keys.\n*/\nfunction isJsonSchemaRecord(value) {\n return isPlainJsonRecord(value) && hasOnlyEnumerableStringKeys(value);\n}\n/**\n* Test for a dense ordinary array with no JSON-invisible decorations.\n* @param value - candidate array from any JavaScript realm.\n* @returns Whether the array is intrinsic, dense, and undecorated.\n*/\nfunction isPlainJsonArray(value) {\n if (!Array.isArray(value)) return false;\n try {\n if (!hasPlainArrayPrototype(value) || Reflect.ownKeys(value).length !== value.length + 1) return false;\n for (let index = 0; index < value.length; index++) if (!Object.hasOwn(value, index)) return false;\n return true;\n } catch {\n return false;\n }\n}\n/** Lossless finite JSON number, excluding negative zero. */\nfunction isJsonNumber(value) {\n return typeof value === \"number\" && Number.isFinite(value) && !Object.is(value, -0);\n}\n/** Whether a scalar is valid for one declared schema type. */\nfunction scalarMatches(type, value) {\n switch (type) {\n case \"string\": return typeof value === \"string\";\n case \"number\": return isJsonNumber(value);\n case \"integer\": return isJsonNumber(value) && Number.isInteger(value);\n case \"boolean\": return typeof value === \"boolean\";\n case \"null\": return value === null;\n /* v8 ignore next -- JsonSchemaScalarType is closed; this retains compile-time exhaustiveness. */\n default: return assertNever(type, \"JsonSchemaType\");\n }\n}\n/** Keywords that are invalid beside `oneOf`. */\nconst ONE_OF_SIBLING_KEYWORDS = [\n \"properties\",\n \"required\",\n \"additionalProperties\",\n \"items\",\n \"enum\",\n \"const\"\n];\n/** Validate object-only fields after its property schemas have been visited. */\nfunction checkObjectSchemaTail(node, path, properties, violations) {\n const hasRequired = Object.hasOwn(node, \"required\");\n const required = hasRequired ? node.required : void 0;\n if (hasRequired) if (!isPlainJsonArray(required) || required.some((entry) => typeof entry !== \"string\")) violations.push(`${path}.required must be an array of strings`);\n else {\n const declared = isJsonSchemaRecord(properties) ? properties : {};\n for (const key of required) if (!Object.hasOwn(declared, key)) violations.push(`${path}.required names \"${key}\" which is not in properties`);\n }\n if (Object.hasOwn(node, \"additionalProperties\") && typeof node.additionalProperties !== \"boolean\") violations.push(`${path}.additionalProperties must be a boolean`);\n}\n/** Collect every violation for one raw schema tree without using the JavaScript call stack. */\nfunction checkSchemaNode(root, rootPath, violations, seen) {\n const tasks = [{\n kind: \"enter\",\n node: root,\n path: rootPath\n }];\n for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {\n if (task.kind === \"leave\") {\n seen.delete(task.node);\n continue;\n }\n if (task.kind === \"one-of-tail\") {\n for (const key of ONE_OF_SIBLING_KEYWORDS) if (Object.hasOwn(task.node, key)) violations.push(`${task.path}.${key} is not supported beside oneOf`);\n continue;\n }\n if (task.kind === \"object-tail\") {\n checkObjectSchemaTail(task.node, task.path, task.properties, violations);\n continue;\n }\n const { node, path } = task;\n if (!isJsonSchemaRecord(node)) {\n violations.push(`${path} must be a schema object`);\n continue;\n }\n if (seen.has(node)) {\n violations.push(`${path} is circular`);\n continue;\n }\n seen.add(node);\n tasks.push({\n kind: \"leave\",\n node\n });\n for (const key of Object.keys(node)) {\n if (CONSTRAINT_KEYWORDS.has(key)) continue;\n if (ANNOTATION_KEYWORDS.has(key)) {\n try {\n if (!isJsonValue(node[key])) violations.push(`${path}.${key} annotation must be lossless JSON data`);\n } catch {\n violations.push(`${path}.${key} annotation must be lossless JSON data`);\n }\n continue;\n }\n violations.push(`${path}.${key} is not a supported keyword (subset: type/oneOf/properties/required/additionalProperties/items/enum/const + annotations)`);\n }\n if (Object.hasOwn(node, \"description\") && typeof node.description !== \"string\") violations.push(`${path}.description must be a string`);\n if (Object.hasOwn(node, \"title\") && typeof node.title !== \"string\") violations.push(`${path}.title must be a string`);\n const hasType = Object.hasOwn(node, \"type\");\n const hasOneOf = Object.hasOwn(node, \"oneOf\");\n if (hasType && hasOneOf) {\n violations.push(`${path} cannot declare both type and oneOf`);\n continue;\n }\n if (!hasType && !hasOneOf) {\n for (const key of ONE_OF_SIBLING_KEYWORDS) if (Object.hasOwn(node, key)) violations.push(`${path}.${key} requires type or oneOf`);\n continue;\n }\n if (hasOneOf) {\n const oneOf = node.oneOf;\n tasks.push({\n kind: \"one-of-tail\",\n node,\n path\n });\n if (!isPlainJsonArray(oneOf) || oneOf.length < 2) violations.push(`${path}.oneOf must be an array of at least two schemas`);\n else for (let index = oneOf.length - 1; index >= 0; index--) tasks.push({\n kind: \"enter\",\n node: oneOf[index],\n path: `${path}.oneOf[${index}]`\n });\n continue;\n }\n const type = node.type;\n if (typeof type !== \"string\" || !SCHEMA_TYPES.includes(type)) {\n violations.push(Array.isArray(type) ? `${path}.type must be a single type string (type arrays are not supported)` : `${path}.type must be one of ${SCHEMA_TYPES.join(\"/\")}`);\n continue;\n }\n const schemaType = type;\n for (const [key, types] of Object.entries({\n properties: [\"object\"],\n required: [\"object\"],\n additionalProperties: [\"object\"],\n items: [\"array\"],\n enum: [\n \"string\",\n \"number\",\n \"integer\",\n \"boolean\",\n \"null\"\n ],\n const: [\n \"string\",\n \"number\",\n \"integer\",\n \"boolean\",\n \"null\"\n ]\n })) if (Object.hasOwn(node, key) && !types.includes(schemaType)) violations.push(`${path}.${key} is not supported on type \"${schemaType}\"`);\n switch (schemaType) {\n case \"object\": {\n const properties = Object.hasOwn(node, \"properties\") ? node.properties : void 0;\n tasks.push({\n kind: \"object-tail\",\n node,\n path,\n properties\n });\n if (Object.hasOwn(node, \"properties\")) if (!isJsonSchemaRecord(properties)) violations.push(`${path}.properties must be an object of schemas`);\n else {\n const entries = Object.entries(properties);\n for (let index = entries.length - 1; index >= 0; index--) {\n const entry = entries[index];\n /* v8 ignore next -- the loop is bounded by the captured entry count. */\n if (entry === void 0) continue;\n tasks.push({\n kind: \"enter\",\n node: entry[1],\n path: `${path}.properties.${entry[0]}`\n });\n }\n }\n break;\n }\n case \"array\":\n if (Object.hasOwn(node, \"items\")) tasks.push({\n kind: \"enter\",\n node: node.items,\n path: `${path}.items`\n });\n break;\n case \"string\":\n case \"number\":\n case \"integer\":\n case \"boolean\":\n case \"null\": {\n const hasEnum = Object.hasOwn(node, \"enum\");\n const allowed = hasEnum ? node.enum : void 0;\n const enumValid = isPlainJsonArray(allowed) && allowed.length > 0 && allowed.every((entry) => scalarMatches(schemaType, entry));\n if (hasEnum && !enumValid) violations.push(`${path}.enum must be a non-empty array of ${schemaType} values`);\n const hasConst = Object.hasOwn(node, \"const\");\n const declaredConst = hasConst ? node.const : void 0;\n const constValid = scalarMatches(schemaType, declaredConst);\n if (hasConst) {\n if (!constValid) violations.push(`${path}.const must be a ${schemaType} value`);\n else if (enumValid && !allowed.includes(declaredConst)) violations.push(`${path}.const must be one of ${path}.enum when both are declared`);\n }\n break;\n }\n /* v8 ignore next -- schemaType was narrowed from the closed SCHEMA_TYPES table above. */\n default: assertNever(schemaType, \"JsonSchemaType\");\n }\n }\n}\n/**\n* Assert the enforced subset plus the object-root constraint retained by\n* subagent and workflow structured outputs.\n* @param schema - untrusted caller-supplied schema.\n* @returns Assertion that the schema belongs to the supported subset and has an object root.\n*/\nfunction assertObjectJsonSchema(schema) {\n const violations = [];\n checkSchemaNode(schema, \"schema\", violations, /* @__PURE__ */ new Set());\n if (violations.length === 0 && (!isJsonSchemaRecord(schema) || !Object.hasOwn(schema, \"type\") || schema.type !== \"object\")) violations.push(\"schema.type must be \\\"object\\\" (structured output is object-rooted)\");\n if (violations.length > 0) throw new JsonSchemaError(violations);\n}\n//#endregion\n//#region packages/workflow/workflow/src/index.ts\n/**\n* Typed error for workflow-seam failures. Extends {@link HarnessError}, so the\n* `code` is machine-routable taxonomy. `fatal` drives the combinator\n* discipline: `parallel()`/`pipeline()` re-throw a fatal error (a typo'd\n* option or a tripped cap must kill the script loudly), and reserve the\n* per-item `null` for child-run failures and ordinary in-stage script errors.\n* Every {@link WorkflowErrorCode} is fatal; the flag exists so the\n* distinction is explicit at every catch site rather than implied.\n*/\nvar WorkflowError = class extends HarnessError {\n /** Whether combinators must propagate this error instead of nulling the item. */\n fatal;\n constructor(message, code, options) {\n super(message, code, options);\n this.name = \"WorkflowError\";\n this.fatal = options?.fatal ?? true;\n }\n};\n/**\n* Whether combinators must re-throw `error` instead of mapping the item to `null`.\n* @param error - any thrown value; fatality is host `instanceof` (unforgeable from a script realm).\n* @returns true iff `error` is a {@link WorkflowError} whose `fatal` flag is set.\n*/\nfunction isFatalWorkflowError(error) {\n return error instanceof WorkflowError && error.fatal;\n}\n//#endregion\n//#region packages/workflow/workflow-ptc/src/runtime.ts\n/**\n* Workflow VM hooks, child callbacks, ordinary concurrency limits and result serialization.\n* PTC owns process confinement and cancellation. Fatal hook and provider failures propagate\n* through combinators; ordinary child failures and stage errors become per-item nulls.\n* @module @deepseek-ai/dsh-workflow-ptc/runtime\n*/\n/** The `agent()` options the script may pass; everything else rejects loud. */\nconst SUPPORTED_AGENT_OPTIONS = new Set([\n \"label\",\n \"phase\",\n \"schema\",\n \"provider\",\n \"model\"\n]);\n/** Deferred Claude Code options we name explicitly in the rejection message. */\nconst DEFERRED_AGENT_OPTIONS = new Set([\n \"effort\",\n \"isolation\",\n \"agentType\"\n]);\n/** Flatten a child's final output blocks to text (the non-schema `agent()` result). */\nfunction outputText(blocks) {\n return blocks.filter((block) => block.type === \"text\").map((block) => block.text).join(\"\");\n}\n/** A short display label derived from the prompt when the script passes none. */\nfunction defaultLabel(prompt) {\n const newline = prompt.indexOf(\"\\n\");\n const line = newline === -1 ? prompt : prompt.slice(0, newline);\n return line.length <= 48 ? line : `${line.slice(0, 47)}…`;\n}\n/**\n* One script execution inside the confined Node process. The host owns\n* cancellation and cleanup of any dropped child work.\n*/\nvar WorkflowExecution = class {\n limits;\n observer;\n children;\n /** 1-based count of `agent()` calls started (the `agentsStarted` result field). */\n started = 0;\n activeSlots = 0;\n slotWaiters = [];\n currentPhase;\n context;\n compiled;\n constructor(meta, body, args, limits, observer, children) {\n this.limits = limits;\n this.observer = observer;\n this.children = children;\n try {\n this.compiled = new vm.Script(`(async () => {\\n${body}\\n})()`, {\n filename: `workflow:${meta.name}`,\n lineOffset: -1\n });\n } catch (error) {\n throw new WorkflowError(`workflow script does not parse: ${String(error)}`, \"SCRIPT_PARSE\", { cause: error });\n }\n this.context = vm.createContext({}, { name: `workflow:${meta.name}` });\n const globals = {\n agent: (prompt, opts) => this.contain(this.agent(prompt, opts)),\n parallel: (thunks) => this.contain(this.parallel(thunks)),\n pipeline: (items, ...stages) => this.contain(this.pipeline(items, stages)),\n phase: (title) => {\n this.phase(title);\n },\n log: (message) => {\n this.log(message);\n },\n args\n };\n for (const [key, value] of Object.entries(globals)) this.context[key] = typeof value === \"function\" ? Object.freeze(value) : value;\n }\n /**\n * Run the script and materialize its JSON return value.\n * @returns A completed or error result; script failures never reject.\n */\n async drive() {\n try {\n const scriptPromise = this.compiled.runInContext(this.context, { timeout: this.limits.syncTimeoutMs });\n const raw = await this.contain(Promise.resolve(scriptPromise));\n return {\n value: raw === void 0 ? null : this.materializeResult(raw),\n stopReason: \"completed\",\n agentsStarted: this.started\n };\n } catch (error) {\n return {\n value: null,\n stopReason: \"error\",\n error: renderThrown(error),\n agentsStarted: this.started\n };\n }\n }\n /**\n * Attach a no-op rejection consumer WITHOUT changing what the caller\n * receives: if the script drops the promise, a host rejection cannot become\n * an unhandled rejection that kills the process; if\n * the script does await it, it still observes the rejection.\n */\n contain(promise) {\n promise.catch(() => {});\n return promise;\n }\n /** Materialize the script's return value; violations become RESULT_UNSERIALIZABLE. */\n materializeResult(raw) {\n try {\n return materializeFromRealm(raw, \"workflow result\");\n } catch (error) {\n /* v8 ignore next -- defensive rethrow arm: materializeFromRealm only throws MaterializeError */\n if (!(error instanceof MaterializeError)) throw error;\n throw new WorkflowError(`the workflow's return value is not plain JSON data — ${error.message}. Return only JSON-serializable objects/arrays/scalars.`, \"RESULT_UNSERIALIZABLE\", { cause: error });\n }\n }\n /** Acquire one concurrency slot in FIFO order. */\n acquireSlot() {\n if (this.activeSlots < this.limits.maxConcurrentAgents) {\n this.activeSlots += 1;\n return Promise.resolve();\n }\n return new Promise((resolve) => {\n this.slotWaiters.push(() => {\n this.activeSlots += 1;\n resolve();\n });\n });\n }\n releaseSlot() {\n this.activeSlots -= 1;\n const next = this.slotWaiters.shift();\n if (next) next();\n }\n /** The `agent(prompt, opts)` hook. */\n async agent(rawPrompt, rawOpts) {\n if (typeof rawPrompt !== \"string\" || rawPrompt.length === 0) throw new WorkflowError(\"agent() requires a non-empty prompt string\", \"INVALID_ARGUMENT\");\n const opts = this.readAgentOptions(rawOpts);\n if (this.started >= this.limits.maxTotalAgents) throw new WorkflowError(`this run reached its total agent cap (${this.limits.maxTotalAgents}) — a runaway-loop backstop; raise the applicable maxTotalAgents limit if the scale is intentional`, \"AGENT_CAP\");\n this.started += 1;\n const seq = this.started;\n const label = opts.label ?? defaultLabel(rawPrompt);\n const phase = opts.phase ?? this.currentPhase;\n await this.acquireSlot();\n try {\n let run;\n try {\n run = await this.children.startAgent({\n prompt: rawPrompt,\n ...opts.schema !== void 0 ? { schema: opts.schema } : {},\n ...opts.provider !== void 0 ? { provider: opts.provider } : {},\n ...opts.model !== void 0 ? { model: opts.model } : {}\n });\n } catch (error) {\n throw new WorkflowError(`agent() could not start a child: ${renderThrown(error)}`, \"AGENT_START\", { cause: error });\n }\n const info = {\n seq,\n label,\n ...phase !== void 0 ? { phase } : {},\n childId: brandString(run.id)\n };\n this.observer.agentStart(info);\n try {\n let result;\n try {\n result = await run.result;\n } catch (error) {\n this.observer.agentEnd({\n ...info,\n outcome: \"failed\"\n });\n throw new WorkflowError(`child agent run failed: ${renderThrown(error)}`, \"AGENT_RESULT\", { cause: error });\n }\n if (result.stopReason === \"completed\") {\n if (opts.schema !== void 0) {\n if (result.structured === void 0) {\n this.observer.agentEnd({\n ...info,\n outcome: \"failed\"\n });\n return null;\n }\n this.observer.agentEnd({\n ...info,\n outcome: \"completed\"\n });\n return result.structured;\n }\n this.observer.agentEnd({\n ...info,\n outcome: \"completed\"\n });\n return outputText(result.output);\n }\n this.observer.agentEnd({\n ...info,\n outcome: \"failed\"\n });\n return null;\n } finally {\n await run.dispose();\n }\n } finally {\n this.releaseSlot();\n }\n }\n /** Materialize + validate the `agent()` options bag from the realm. */\n readAgentOptions(rawOpts) {\n if (rawOpts === void 0) return {};\n let opts;\n try {\n opts = materializeFromRealm(rawOpts, \"agent() options\");\n } catch (error) {\n /* v8 ignore next -- defensive rethrow arm: materializeFromRealm only throws MaterializeError */\n if (!(error instanceof MaterializeError)) throw error;\n throw new WorkflowError(`agent() options must be plain JSON data — ${error.message}`, \"INVALID_ARGUMENT\", { cause: error });\n }\n if (typeof opts !== \"object\" || opts === null || Array.isArray(opts)) throw new WorkflowError(\"agent() options must be an object\", \"INVALID_ARGUMENT\");\n const record = opts;\n for (const key of Object.keys(record)) {\n if (SUPPORTED_AGENT_OPTIONS.has(key)) continue;\n if (DEFERRED_AGENT_OPTIONS.has(key)) throw new WorkflowError(`agent() option \"${key}\" is deferred and not supported by this engine (supported: label, phase, schema, provider, model)`, \"UNSUPPORTED_OPTION\");\n throw new WorkflowError(`agent() option \"${key}\" is not recognized (supported: label, phase, schema, provider, model)`, \"UNSUPPORTED_OPTION\");\n }\n for (const key of [\n \"label\",\n \"phase\",\n \"provider\",\n \"model\"\n ]) if (record[key] !== void 0 && typeof record[key] !== \"string\") throw new WorkflowError(`agent() option \"${key}\" must be a string`, \"INVALID_ARGUMENT\");\n let schema;\n if (record.schema !== void 0) try {\n assertObjectJsonSchema(record.schema);\n schema = record.schema;\n } catch (error) {\n /* v8 ignore next -- defensive rethrow arm: assertObjectJsonSchema only throws JsonSchemaError */\n if (!(error instanceof JsonSchemaError)) throw error;\n throw new WorkflowError(`agent() schema is outside the supported subset — ${error.message}`, \"UNSUPPORTED_SCHEMA\", { cause: error });\n }\n return {\n ...record.label !== void 0 ? { label: record.label } : {},\n ...record.phase !== void 0 ? { phase: record.phase } : {},\n ...record.provider !== void 0 ? { provider: record.provider } : {},\n ...record.model !== void 0 ? { model: record.model } : {},\n ...schema !== void 0 ? { schema } : {}\n };\n }\n /** The `parallel(thunks)` hook: each thunk caught → `null`; fatal errors propagate. */\n async parallel(rawThunks) {\n if (!Array.isArray(rawThunks)) throw new WorkflowError(\"parallel() requires an array of zero-argument functions\", \"INVALID_ARGUMENT\");\n this.assertItemCap(rawThunks.length, \"parallel()\");\n const thunks = rawThunks.map((thunk, index) => {\n if (typeof thunk !== \"function\") throw new WorkflowError(`parallel() item ${index} is not a function`, \"INVALID_ARGUMENT\");\n return thunk;\n });\n return Promise.all(thunks.map(async (thunk) => {\n try {\n return await thunk();\n } catch (error) {\n if (isFatalWorkflowError(error)) throw error;\n return null;\n }\n }));\n }\n /** The `pipeline(items, ...stages)` hook: per-item stage chains, NO cross-stage barrier. */\n async pipeline(rawItems, rawStages) {\n if (!Array.isArray(rawItems)) throw new WorkflowError(\"pipeline() requires an items array\", \"INVALID_ARGUMENT\");\n this.assertItemCap(rawItems.length, \"pipeline()\");\n if (rawStages.length === 0) throw new WorkflowError(\"pipeline() requires at least one stage function\", \"INVALID_ARGUMENT\");\n const stages = rawStages.map((stage, index) => {\n if (typeof stage !== \"function\") throw new WorkflowError(`pipeline() stage ${index} is not a function`, \"INVALID_ARGUMENT\");\n return stage;\n });\n return Promise.all(rawItems.map(async (item, index) => {\n let value = item;\n try {\n for (const stage of stages) value = await stage(value, item, index);\n return value;\n } catch (error) {\n if (isFatalWorkflowError(error)) throw error;\n return null;\n }\n }));\n }\n assertItemCap(length, hook) {\n if (length > this.limits.maxItemsPerCall) throw new WorkflowError(`${hook} received ${length} items — over the per-call cap (${this.limits.maxItemsPerCall}); split the work or raise maxItemsPerCall in the engine config`, \"ITEM_CAP\");\n }\n /** The `phase(title)` hook: sets the current label for subsequent `agent()` calls and notifies observers. */\n phase(title) {\n if (typeof title !== \"string\" || title.length === 0) throw new WorkflowError(\"phase() requires a non-empty title string\", \"INVALID_ARGUMENT\");\n this.currentPhase = title;\n this.observer.phase(title);\n }\n /** The `log(message)` hook: narration to observers. */\n log(message) {\n if (typeof message !== \"string\") throw new WorkflowError(\"log() requires a message string\", \"INVALID_ARGUMENT\");\n this.observer.log(message);\n }\n};\n//#endregion\n//#region packages/workflow/workflow-ptc/src/guest.ts\n/**\n* Run a workflow with one progress batch in flight. Drain progress before child\n* disposal and the terminal result; PTC and the host own cancellation and cleanup.\n* @param host - JSON callbacks owned by this workflow run.\n* @returns The script result after progress delivery; initialization failures reject.\n*/\nasync function runWorkflowGuest(host) {\n const init = await host.begin({});\n let queued = [];\n let inFlight;\n let progressError;\n const flush = () => {\n if (inFlight !== void 0 || queued.length === 0) return;\n const batch = queued;\n queued = [];\n inFlight = host.progress(batch).then(() => {\n inFlight = void 0;\n flush();\n }, (error) => {\n progressError = renderThrown(error);\n queued = [];\n inFlight = void 0;\n });\n };\n const send = (event) => {\n if (progressError !== void 0) return;\n queued.push(event);\n flush();\n };\n const drain = async () => {\n while (inFlight !== void 0) await inFlight;\n };\n const result = await new WorkflowExecution(init.meta, init.body, init.args, init.limits, {\n phase: (title) => {\n send({\n type: \"phase\",\n title\n });\n },\n log: (message) => {\n send({\n type: \"log\",\n message\n });\n },\n agentStart: (info) => {\n send({\n type: \"agent-start\",\n info\n });\n },\n agentEnd: (info) => {\n send({\n type: \"agent-end\",\n info\n });\n }\n }, { async startAgent(request) {\n const { callId, childId } = await host.startChild(request);\n const result = host.childResult({ callId });\n result.catch(() => {});\n return {\n id: childId,\n result,\n async dispose() {\n await drain();\n await host.disposeChild({ callId });\n }\n };\n } }).drive();\n await drain();\n return progressError === void 0 ? result : {\n value: null,\n stopReason: \"error\",\n error: progressError,\n agentsStarted: result.agentsStarted\n };\n}\n//#endregion\nexport { runWorkflowGuest };\n//# sourceURL=dsh-workflow-guest.js\n";
13
+ //#endregion
14
+ //#region lib/types/realm.js
15
+ /**
16
+ * Materializes script VM values as plain JSON and renders thrown values.
17
+ * Getters and proxy traps may execute inside the confined Node process;
18
+ * process isolation and cancellation belong to PTC, not the VM.
19
+ * @module @deepseek-ai/dsh-workflow-ptc/realm
20
+ */
21
+ /** Thrown by {@link materializeFromRealm}; the caller wraps it into the right `WorkflowError` code. */
22
+ var MaterializeError = class extends Error {
23
+ path;
24
+ reason;
25
+ constructor(path, reason) {
26
+ super(`${path}: ${reason}`);
27
+ this.path = path;
28
+ this.reason = reason;
29
+ this.name = "MaterializeError";
30
+ }
31
+ };
32
+ /**
33
+ * Render a thrown value to failure text without ever throwing: prefer the
34
+ * `stack` (host or realm — a realm error's `stack` is a plain string read),
35
+ * fall back to `message`, then `String()`. Reading those properties MAY run
36
+ * script code; if that code itself throws, a fixed label is returned instead.
37
+ * @param error - any value thrown in the host or guest realm.
38
+ * @returns human-readable text for the failure report; prefers the stack.
39
+ */
40
+ function renderThrown(error) {
41
+ try {
42
+ const stack = error?.stack;
43
+ if (typeof stack === "string" && stack.length > 0) return stack;
44
+ const message = error?.message;
45
+ if (typeof message === "string" && message.length > 0) return message;
46
+ return String(error);
47
+ } catch {
48
+ return "[unrenderable thrown value]";
49
+ }
50
+ }
51
+ /**
52
+ * Whether an object's prototype chain represents a plain data object: `null`, or a prototype
53
+ * whose own prototype is `null` (the realm's `Object.prototype` — which we
54
+ * cannot compare by identity across realms). A `Date`/`Map`/class instance
55
+ * has a longer chain and is rejected.
56
+ */
57
+ function hasPlainPrototype(value) {
58
+ const proto = Object.getPrototypeOf(value);
59
+ if (proto === null) return true;
60
+ return Object.getPrototypeOf(proto) === null;
61
+ }
62
+ /**
63
+ * Copy `value` (typically from the vm realm) into plain host JSON data. Root `undefined` is
64
+ * returned unchanged; nested `undefined` and values JSON cannot represent losslessly fail
65
+ * with the offending path. Property accessors run normally, and a throwing read is wrapped
66
+ * with its rendered failure.
67
+ *
68
+ * @param value - the realm value to materialize.
69
+ * @param root - the path label for the root value (error messages).
70
+ * @returns the host-realm copy (plain objects/arrays/scalars only).
71
+ * @throws {@link MaterializeError} for unsupported values, cycles, sparse arrays, exotic
72
+ * prototypes, or property reads that throw.
73
+ */
74
+ function materializeFromRealm(value, root = "value") {
75
+ if (value === void 0) return void 0;
76
+ try {
77
+ return materialize(value, root, /* @__PURE__ */ new Set());
78
+ } catch (error) {
79
+ if (error instanceof MaterializeError) throw error;
80
+ throw new MaterializeError(root, `reading the value threw: ${renderThrown(error)}`);
81
+ }
82
+ }
83
+ function materialize(value, path, seen) {
84
+ switch (typeof value) {
85
+ case "boolean":
86
+ case "string": return value;
87
+ case "number":
88
+ if (!Number.isFinite(value)) throw new MaterializeError(path, "non-finite numbers are not JSON data");
89
+ return value;
90
+ case "bigint": throw new MaterializeError(path, "bigints are not JSON data");
91
+ case "function": throw new MaterializeError(path, "functions are not plain JSON data");
92
+ case "symbol": throw new MaterializeError(path, "symbols are not plain JSON data");
93
+ case "undefined": throw new MaterializeError(path, "undefined is not JSON data");
94
+ case "object": break;
95
+ }
96
+ if (value === null) return null;
97
+ const objectValue = value;
98
+ if (seen.has(objectValue)) throw new MaterializeError(path, "circular references are not JSON data");
99
+ seen.add(objectValue);
100
+ try {
101
+ if (Array.isArray(objectValue)) return materializeArray(objectValue, path, seen);
102
+ return materializeObject(objectValue, path, seen);
103
+ } finally {
104
+ seen.delete(objectValue);
105
+ }
106
+ }
107
+ function materializeArray(value, path, seen) {
108
+ const out = [];
109
+ for (let index = 0; index < value.length; index++) {
110
+ if (!(index in value)) throw new MaterializeError(`${path}[${index}]`, "sparse arrays are not JSON data");
111
+ out.push(materialize(value[index], `${path}[${index}]`, seen));
112
+ }
113
+ for (const key of Object.keys(value)) {
114
+ const index = Number(key);
115
+ if (!Number.isInteger(index) || index < 0 || index >= value.length) throw new MaterializeError(`${path}.${key}`, "arrays with non-index properties are not JSON data");
116
+ }
117
+ if (Object.getOwnPropertySymbols(value).length > 0) throw new MaterializeError(path, "symbol-keyed properties are not plain JSON data");
118
+ return out;
119
+ }
120
+ function materializeObject(value, path, seen) {
121
+ if (!hasPlainPrototype(value)) throw new MaterializeError(path, "only plain objects and arrays are JSON data (exotic prototype)");
122
+ if (Object.getOwnPropertySymbols(value).length > 0) throw new MaterializeError(path, "symbol-keyed properties are not plain JSON data");
123
+ const out = {};
124
+ for (const key of Object.keys(value)) Object.defineProperty(out, key, {
125
+ value: materialize(value[key], `${path}.${key}`, seen),
126
+ enumerable: true,
127
+ writable: true,
128
+ configurable: true
129
+ });
130
+ return out;
131
+ }
132
+ //#endregion
133
+ //#region lib/types/host.js
134
+ const GUEST_URL = `data:text/javascript,${encodeURIComponent(WORKFLOW_GUEST_SOURCE)}`;
135
+ const PROGRAM = `const { runWorkflowGuest } = await import(${JSON.stringify(GUEST_URL)}); return await runWorkflowGuest(workflowHost);`;
136
+ function object(value) {
137
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("workflow binding requires an object");
138
+ return value;
139
+ }
140
+ function text(value, name) {
141
+ if (typeof value !== "string") throw new Error(`workflow ${name} must be a string`);
142
+ return value;
143
+ }
144
+ function json(value) {
145
+ const result = snapshotJsonValue(value);
146
+ if (result === void 0) throw new Error("workflow binding value must be lossless JSON");
147
+ return result;
148
+ }
149
+ function childRequest(value) {
150
+ const request = object(value);
151
+ const prompt = text(request.prompt, "prompt");
152
+ const provider = request.provider === void 0 ? void 0 : text(request.provider, "provider");
153
+ const model = request.model === void 0 ? void 0 : text(request.model, "model");
154
+ let schema;
155
+ if (request.schema !== void 0) {
156
+ const candidate = object(request.schema);
157
+ assertObjectJsonSchema(candidate);
158
+ schema = candidate;
159
+ }
160
+ return {
161
+ prompt,
162
+ ...provider === void 0 ? {} : { provider },
163
+ ...model === void 0 ? {} : { model },
164
+ ...schema === void 0 ? {} : { schema }
165
+ };
166
+ }
167
+ function agentInfo(value) {
168
+ const info = object(value);
169
+ if (!Number.isSafeInteger(info.seq) || info.seq < 1) throw new Error("workflow agent sequence must be a positive integer");
170
+ return {
171
+ seq: info.seq,
172
+ label: text(info.label, "agent label"),
173
+ childId: SessionId(text(info.childId, "child id")),
174
+ ...info.phase === void 0 ? {} : { phase: text(info.phase, "agent phase") }
175
+ };
176
+ }
177
+ function progress(value) {
178
+ const event = object(value);
179
+ switch (event.type) {
180
+ case "phase": return {
181
+ type: "phase",
182
+ title: text(event.title, "phase")
183
+ };
184
+ case "log": return {
185
+ type: "log",
186
+ message: text(event.message, "log")
187
+ };
188
+ case "agent-start": return {
189
+ type: "agent-start",
190
+ info: agentInfo(event.info)
191
+ };
192
+ case "agent-end": {
193
+ const info = object(event.info);
194
+ if (info.outcome !== "completed" && info.outcome !== "failed" && info.outcome !== "cancelled") throw new Error("invalid workflow agent outcome");
195
+ return {
196
+ type: "agent-end",
197
+ info: {
198
+ ...agentInfo(info),
199
+ outcome: info.outcome
200
+ }
201
+ };
202
+ }
203
+ default: throw new Error("invalid workflow progress event");
204
+ }
205
+ }
206
+ function progressBatch(value) {
207
+ if (!Array.isArray(value)) throw new Error("workflow progress requires an array of events");
208
+ return value.map(progress);
209
+ }
210
+ function workflowResult(value) {
211
+ const result = object(value);
212
+ if (result.stopReason !== "completed" && result.stopReason !== "error" && result.stopReason !== "cancelled") throw new Error("invalid workflow stop reason");
213
+ if (!Number.isSafeInteger(result.agentsStarted) || result.agentsStarted < 0) throw new Error("invalid workflow agent count");
214
+ if (!Object.hasOwn(result, "value")) throw new Error("workflow result is missing its value");
215
+ return {
216
+ value: result.value,
217
+ stopReason: result.stopReason,
218
+ agentsStarted: result.agentsStarted,
219
+ ...result.error === void 0 ? {} : { error: text(result.error, "error") }
220
+ };
221
+ }
222
+ /**
223
+ * Holder-owned workflow. Cancellation stops the program immediately; settlement waits for
224
+ * its managed process and every admitted child startup/disposal. Engine unload does not
225
+ * invalidate the captured runtime or subagent handles.
226
+ */
227
+ var PtcWorkflowRun = class {
228
+ ctx;
229
+ subagents;
230
+ runtime;
231
+ id;
232
+ meta;
233
+ parent;
234
+ init;
235
+ provider;
236
+ policy;
237
+ observer;
238
+ signal;
239
+ result;
240
+ controller = new AbortController();
241
+ children = /* @__PURE__ */ new Map();
242
+ pending = /* @__PURE__ */ new Set();
243
+ liveAgents = /* @__PURE__ */ new Map();
244
+ started = 0;
245
+ terminal = false;
246
+ cancelReason;
247
+ disposed;
248
+ externalAbort;
249
+ constructor(ctx, subagents, runtime, id, meta, parent, init, provider, policy, observer, signal) {
250
+ this.ctx = ctx;
251
+ this.subagents = subagents;
252
+ this.runtime = runtime;
253
+ this.id = id;
254
+ this.meta = meta;
255
+ this.parent = parent;
256
+ this.init = init;
257
+ this.provider = provider;
258
+ this.policy = policy;
259
+ this.observer = observer;
260
+ this.signal = signal;
261
+ this.externalAbort = () => {
262
+ this.cancel("workflow signal aborted");
263
+ };
264
+ if (signal?.aborted) this.externalAbort();
265
+ else signal?.addEventListener("abort", this.externalAbort, { once: true });
266
+ this.result = Promise.resolve().then(() => this.drive());
267
+ }
268
+ /**
269
+ * Stop the script and abort pending and published children.
270
+ * @param reason - Human-readable cancellation cause; the first request wins.
271
+ */
272
+ cancel(reason = "workflow cancelled") {
273
+ if (this.terminal || this.cancelReason !== void 0) return;
274
+ this.cancelReason = reason;
275
+ this.controller.abort(reason);
276
+ for (const record of this.children.values()) this.disposeChild(record);
277
+ }
278
+ /**
279
+ * Cancel unfinished work and await the program and child cleanup.
280
+ * @returns One shared completion promise for repeated disposal calls.
281
+ */
282
+ dispose() {
283
+ this.cancel("workflow disposed");
284
+ this.disposed ??= this.result.then(() => {});
285
+ return this.disposed;
286
+ }
287
+ requireActive() {
288
+ this.controller.signal.throwIfAborted();
289
+ }
290
+ track(task) {
291
+ this.pending.add(task);
292
+ task.then(() => {
293
+ this.pending.delete(task);
294
+ }, () => {
295
+ this.pending.delete(task);
296
+ });
297
+ return task;
298
+ }
299
+ bindings() {
300
+ return {
301
+ begin: () => {
302
+ this.requireActive();
303
+ return Promise.resolve(json(this.init));
304
+ },
305
+ startChild: (value) => this.track(this.startChild(childRequest(value))),
306
+ childResult: (value) => this.track(this.childResult(this.child(value))),
307
+ disposeChild: async (value) => {
308
+ await this.disposeChild(this.child(value));
309
+ return null;
310
+ },
311
+ progress: (value) => {
312
+ for (const event of progressBatch(value)) this.onProgress(event);
313
+ return Promise.resolve(null);
314
+ }
315
+ };
316
+ }
317
+ child(value) {
318
+ this.requireActive();
319
+ const callId = object(value).callId;
320
+ if (!Number.isSafeInteger(callId)) throw new Error("workflow child call id must be an integer");
321
+ const record = this.children.get(callId);
322
+ if (record === void 0) throw new Error("workflow child call is not active");
323
+ return record;
324
+ }
325
+ async startChild(request) {
326
+ this.requireActive();
327
+ const callId = ++this.started;
328
+ const run = await this.subagents.start(this.provider, {
329
+ prompt: [{
330
+ type: "text",
331
+ text: request.prompt
332
+ }],
333
+ parent: this.parent,
334
+ signal: this.controller.signal,
335
+ ...request.schema === void 0 ? {} : { outputSchema: request.schema },
336
+ ...request.provider === void 0 && request.model === void 0 ? {} : { agentOptions: {
337
+ ...request.provider === void 0 ? {} : { provider: request.provider },
338
+ ...request.model === void 0 ? {} : { model: request.model }
339
+ } }
340
+ });
341
+ const record = {
342
+ callId,
343
+ run
344
+ };
345
+ this.children.set(callId, record);
346
+ if (this.controller.signal.aborted) {
347
+ await this.disposeChild(record);
348
+ throw new Error("workflow child started after cancellation");
349
+ }
350
+ return {
351
+ callId,
352
+ childId: run.id
353
+ };
354
+ }
355
+ async childResult(record) {
356
+ const signal = this.controller.signal;
357
+ signal.throwIfAborted();
358
+ const aborted = Promise.withResolvers();
359
+ const onAbort = () => {
360
+ aborted.reject(signal.reason);
361
+ };
362
+ signal.addEventListener("abort", onAbort, { once: true });
363
+ try {
364
+ const result = await Promise.race([record.run.result, aborted.promise]);
365
+ return json({
366
+ output: result.output,
367
+ stopReason: result.stopReason,
368
+ ...result.structured === void 0 ? {} : { structured: result.structured }
369
+ });
370
+ } finally {
371
+ signal.removeEventListener("abort", onAbort);
372
+ }
373
+ }
374
+ disposeChild(record) {
375
+ record.disposal ??= Promise.resolve().then(() => record.run.dispose()).catch((error) => {
376
+ this.ctx.logger.warn(`workflow-ptc: child dispose failed: ${renderThrown(error)}`);
377
+ }).finally(() => {
378
+ this.children.delete(record.callId);
379
+ });
380
+ return record.disposal;
381
+ }
382
+ onProgress(event) {
383
+ this.requireActive();
384
+ switch (event.type) {
385
+ case "phase":
386
+ this.observer.phase(event.title);
387
+ break;
388
+ case "log":
389
+ this.observer.log(event.message);
390
+ break;
391
+ case "agent-start":
392
+ this.liveAgents.set(event.info.seq, event.info);
393
+ this.observer.agentStart(event.info);
394
+ break;
395
+ case "agent-end":
396
+ this.endAgent(event.info);
397
+ break;
398
+ /* v8 ignore next -- progress() validates the closed message union before dispatch. */
399
+ default: assertNever(event, "workflow progress");
400
+ }
401
+ }
402
+ endAgent(info) {
403
+ if (!this.liveAgents.delete(info.seq)) return;
404
+ this.observer.agentEnd(info);
405
+ }
406
+ cancelled() {
407
+ return {
408
+ value: null,
409
+ stopReason: "cancelled",
410
+ error: `workflow run cancelled: ${this.cancelReason}`,
411
+ agentsStarted: this.started
412
+ };
413
+ }
414
+ async drive() {
415
+ let result;
416
+ try {
417
+ const outcome = await this.runtime.run(this.runtime.resolve({
418
+ program: PROGRAM,
419
+ bindings: [{
420
+ global: "workflowHost",
421
+ functions: this.bindings()
422
+ }],
423
+ cwd: this.policy.workspaceRoot,
424
+ sandboxPolicy: this.policy,
425
+ timeoutMs: null,
426
+ signal: this.controller.signal
427
+ }));
428
+ this.terminal = true;
429
+ if (this.cancelReason !== void 0) result = this.cancelled();
430
+ else if (outcome.error !== void 0) result = {
431
+ value: null,
432
+ stopReason: "error",
433
+ error: `workflow execution failed (${outcome.error.kind}): ${outcome.error.message}`,
434
+ agentsStarted: this.started
435
+ };
436
+ else result = workflowResult(outcome.value);
437
+ } catch (error) {
438
+ this.terminal = true;
439
+ result = this.cancelReason === void 0 ? {
440
+ value: null,
441
+ stopReason: "error",
442
+ error: renderThrown(error),
443
+ agentsStarted: this.started
444
+ } : this.cancelled();
445
+ } finally {
446
+ this.terminal = true;
447
+ this.signal?.removeEventListener("abort", this.externalAbort);
448
+ this.controller.abort("workflow settled");
449
+ for (const record of this.children.values()) this.disposeChild(record);
450
+ while (this.pending.size > 0) await Promise.allSettled([...this.pending]);
451
+ await Promise.all([...this.children.values()].map((record) => this.disposeChild(record)));
452
+ this.children.clear();
453
+ for (const info of this.liveAgents.values()) this.endAgent({
454
+ ...info,
455
+ outcome: "cancelled"
456
+ });
457
+ }
458
+ return result;
459
+ }
460
+ };
461
+ //#endregion
462
+ //#region lib/types/meta.js
463
+ /**
464
+ * Meta validation checks caller-provided DATA against the {@link WorkflowMeta}
465
+ * contract and rejects every violation by name. Meta arrives as schema-checked
466
+ * JSON data, never evaluated script text. Model-written JavaScript executes
467
+ * inside the confined PTC process.
468
+ * @module @deepseek-ai/dsh-workflow-ptc/meta
469
+ */
470
+ /** Collect shape violations for a meta value (plain JSON data by the seam contract). */
471
+ function validateMetaShape(meta) {
472
+ const violations = [];
473
+ if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return { violations: ["meta must be an object"] };
474
+ const record = meta;
475
+ const known = new Set([
476
+ "name",
477
+ "description",
478
+ "whenToUse",
479
+ "phases"
480
+ ]);
481
+ for (const key of Object.keys(record)) if (!known.has(key)) violations.push(`meta.${key} is not a recognized field (name/description/whenToUse/phases)`);
482
+ if (typeof record.name !== "string" || record.name.length === 0) violations.push("meta.name must be a non-empty string");
483
+ if (typeof record.description !== "string" || record.description.length === 0) violations.push("meta.description must be a non-empty string");
484
+ if (record.whenToUse !== void 0 && typeof record.whenToUse !== "string") violations.push("meta.whenToUse must be a string");
485
+ const phases = [];
486
+ if (record.phases !== void 0) if (!Array.isArray(record.phases)) violations.push("meta.phases must be an array");
487
+ else record.phases.forEach((phase, index) => {
488
+ if (typeof phase !== "object" || phase === null || Array.isArray(phase)) {
489
+ violations.push(`meta.phases[${index}] must be an object`);
490
+ return;
491
+ }
492
+ const entry = phase;
493
+ for (const key of Object.keys(entry)) if (![
494
+ "title",
495
+ "detail",
496
+ "provider",
497
+ "model"
498
+ ].includes(key)) violations.push(`meta.phases[${index}].${key} is not a recognized field`);
499
+ if (typeof entry.title !== "string" || entry.title.length === 0) violations.push(`meta.phases[${index}].title must be a non-empty string`);
500
+ if (entry.detail !== void 0 && typeof entry.detail !== "string") violations.push(`meta.phases[${index}].detail must be a string`);
501
+ if (entry.provider !== void 0 && typeof entry.provider !== "string") violations.push(`meta.phases[${index}].provider must be a string`);
502
+ if (entry.model !== void 0 && typeof entry.model !== "string") violations.push(`meta.phases[${index}].model must be a string`);
503
+ if (violations.length === 0) phases.push({
504
+ title: entry.title,
505
+ ...entry.detail !== void 0 ? { detail: entry.detail } : {},
506
+ ...entry.provider !== void 0 ? { provider: entry.provider } : {},
507
+ ...entry.model !== void 0 ? { model: entry.model } : {}
508
+ });
509
+ });
510
+ if (violations.length > 0) return { violations };
511
+ return {
512
+ violations,
513
+ meta: {
514
+ name: record.name,
515
+ description: record.description,
516
+ ...record.whenToUse !== void 0 ? { whenToUse: record.whenToUse } : {},
517
+ ...record.phases !== void 0 ? { phases } : {}
518
+ }
519
+ };
520
+ }
521
+ /**
522
+ * Validate a caller-provided meta value against the {@link WorkflowMeta}
523
+ * contract. Throws `META_INVALID` naming every violation (unknown fields,
524
+ * missing/mistyped `name`/`description`, malformed `phases`); the returned
525
+ * meta is a NORMALIZED copy built from the validated fields, so the engine
526
+ * never aliases the caller's object.
527
+ * @param value - the meta data from the start request (plain JSON by the seam contract).
528
+ * @returns the validated, normalized meta block.
529
+ */
530
+ function validateMeta(value) {
531
+ const { meta, violations } = validateMetaShape(value);
532
+ if (meta === void 0) throw new WorkflowError(`invalid meta: ${violations.join("; ")}`, "META_INVALID");
533
+ return meta;
534
+ }
535
+ //#endregion
536
+ //#region lib/types/index.js
537
+ /**
538
+ * Workflow orchestration through the shared sandboxed Node PTC executor.
539
+ * The VM supplies script helpers; the process applies the calling Session's file policy.
540
+ * @module @deepseek-ai/dsh-workflow-ptc
541
+ */
542
+ /** A body that still carries the Claude Code-style meta header (meta rides the seam as data here). */
543
+ const META_STATEMENT = /^\s*export\s+const\s+meta\b/;
544
+ /**
545
+ * Reject invalid JavaScript synchronously before publishing a workflow run.
546
+ * The guest compiles the same async wrapper in its own process.
547
+ */
548
+ function assertBodyParses(body, name) {
549
+ if (META_STATEMENT.test(body)) throw new WorkflowError("workflow meta rides the `meta` request field, not the script: remove the `export const meta = {...}` statement from the body", "SCRIPT_PARSE");
550
+ try {
551
+ new vm.Script(`(async () => {\n${body}\n})()`, {
552
+ filename: `workflow:${name}`,
553
+ lineOffset: -1
554
+ });
555
+ } catch (error) {
556
+ throw new WorkflowError(`workflow script does not parse: ${String(error)}`, "SCRIPT_PARSE", { cause: error });
557
+ }
558
+ }
559
+ /** Resolve one run's provider route before publishing work. */
560
+ function resolveSubagentProvider(ctx, configured, override) {
561
+ const provider = override ?? configured;
562
+ if (provider.length === 0 || provider !== provider.trim()) throw new WorkflowError("workflow subagentProvider must be a non-empty normalized string", "INVALID_ARGUMENT");
563
+ if (ctx.subagents.getProvider(provider) === void 0) throw new WorkflowError(`no subagent provider registered for "${provider}"`, "AGENT_START");
564
+ return provider;
565
+ }
566
+ /** Resolve one run's total-child cap against the engine deployment ceiling. */
567
+ function resolveMaxTotalAgents(requested, ceiling) {
568
+ if (requested === void 0) return ceiling;
569
+ if (!Number.isSafeInteger(requested) || requested < 1) throw new WorkflowError("workflow maxTotalAgents must be a positive safe integer", "INVALID_ARGUMENT");
570
+ if (requested > ceiling) throw new WorkflowError(`workflow maxTotalAgents ${requested} exceeds the engine ceiling ${ceiling}`, "INVALID_ARGUMENT");
571
+ return requested;
572
+ }
573
+ /**
574
+ * The PTC-backed workflow engine. `start()` validates the script up front
575
+ * (meta + a host-side body parse) and returns a {@link WorkflowRun} whose
576
+ * `result` never rejects; the `workflow/*` events fire around the run per
577
+ * the seam contract.
578
+ */
579
+ var PtcWorkflowEngine = class extends WorkflowEngine {
580
+ static inject = [
581
+ "subagents",
582
+ "ptcRuntime",
583
+ "sandboxPolicy"
584
+ ];
585
+ static Config = z.object({
586
+ provider: z.string().default("spawn"),
587
+ maxConcurrentAgents: z.natural().default(0),
588
+ maxTotalAgents: z.natural().min(1).default(1e3),
589
+ maxItemsPerCall: z.natural().min(1).default(4096),
590
+ syncTimeoutMs: z.natural().min(1).default(5e3)
591
+ });
592
+ config;
593
+ constructor(ctx, config) {
594
+ super(ctx);
595
+ if (ctx.ptcRuntime.language !== "typescript") throw new Error("workflow-ptc requires the Node TypeScript PTC runtime");
596
+ this.config = config;
597
+ }
598
+ /**
599
+ * Validate and execute a workflow script in a sandboxed Node process. Throws
600
+ * {@link WorkflowError} synchronously (`META_INVALID` for a malformed meta
601
+ * block, `SCRIPT_PARSE` for a body that does not compile) for a request
602
+ * that cannot begin; once a run is returned, every failure resolves through
603
+ * `result.stopReason` instead.
604
+ * @param request - the script body, its meta data and `args`, the parent
605
+ * agent, and an optional cancel signal.
606
+ * @returns the live run (its `result` resolves when the script settles).
607
+ */
608
+ start(request) {
609
+ const meta = validateMeta(request.meta);
610
+ assertBodyParses(request.script, meta.name);
611
+ const subagentProvider = resolveSubagentProvider(this.ctx, this.config.provider, request.subagentProvider);
612
+ const maxTotalAgents = resolveMaxTotalAgents(request.maxTotalAgents, this.config.maxTotalAgents);
613
+ const id = WorkflowRunId(randomUUID());
614
+ const info = {
615
+ id,
616
+ meta
617
+ };
618
+ const limits = {
619
+ maxConcurrentAgents: this.config.maxConcurrentAgents === 0 ? Math.min(16, Math.max(1, availableParallelism() - 2)) : this.config.maxConcurrentAgents,
620
+ maxTotalAgents,
621
+ maxItemsPerCall: this.config.maxItemsPerCall,
622
+ syncTimeoutMs: this.config.syncTimeoutMs
623
+ };
624
+ const init = {
625
+ meta,
626
+ body: request.script,
627
+ ...request.args !== void 0 ? { args: structuredClone(request.args) } : {},
628
+ limits
629
+ };
630
+ const runCtx = this.ctx;
631
+ const subagents = runCtx.subagents;
632
+ const run = new PtcWorkflowRun(runCtx, subagents, runCtx.ptcRuntime, id, meta, request.parent, init, subagentProvider, runCtx.sandboxPolicy.resolve({ session: request.parent.session }), {
633
+ phase: (title) => {
634
+ this.emitWorkflowEvent("workflow/phase", info, title);
635
+ },
636
+ log: (message) => {
637
+ this.emitWorkflowEvent("workflow/log", info, message);
638
+ },
639
+ agentStart: (agent) => {
640
+ this.emitWorkflowEvent("workflow/agent-start", info, agent);
641
+ },
642
+ agentEnd: (agent) => {
643
+ this.emitWorkflowEvent("workflow/agent-end", info, agent);
644
+ }
645
+ }, request.signal);
646
+ this.emitWorkflowEvent("workflow/start", info);
647
+ run.result.then((settled) => {
648
+ this.emitWorkflowEvent("workflow/end", info, {
649
+ stopReason: settled.stopReason,
650
+ ...settled.error !== void 0 ? { error: settled.error } : {},
651
+ agentsStarted: settled.agentsStarted
652
+ });
653
+ });
654
+ return run;
655
+ }
656
+ };
657
+ //#endregion
658
+ export { MaterializeError, PtcWorkflowEngine as default, materializeFromRealm, validateMeta };