@deepseek-ai/dsh-ptc-runtime-node 0.2.1-alpha.1 → 0.2.1-alpha.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/lib/index.js CHANGED
@@ -1,55 +1,39 @@
1
1
  import { stripTypeScriptTypes } from "node:module";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
2
3
  import { dirname, isAbsolute, resolve } from "node:path";
3
- import z from "@deepseek-ai/schemastery";
4
4
  import { DUNDER_MEMBER, PORTABLE_RESERVED_WORDS, PtcRuntime, RESERVED_BINDING_GLOBALS, RESERVED_ERROR_MEMBERS } from "@deepseek-ai/dsh-ptc-runtime";
5
5
  import { MAX_TIMER_DELAY_MS, clampTimeout } from "@deepseek-ai/dsh-timeout";
6
6
  import { SandboxUnavailableError, classifyRunnerFailure, isRunnerSpawnFailure } from "@deepseek-ai/dsh-sandbox";
7
- import { snapshotJsonValue } from "@deepseek-ai/dsh-util-values";
7
+ import { assertNever, snapshotJsonValue } from "@deepseek-ai/dsh-util-values";
8
8
  import { Buffer as Buffer$1 } from "node:buffer";
9
9
  import { fileURLToPath, pathToFileURL } from "node:url";
10
- //#region lib/types/bindings.js
11
- /** Validate and capture the binding names available to one program. */
12
- const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
10
+ import z from "@deepseek-ai/schemastery";
11
+ //#region lib/types/json-wire.js
13
12
  /**
14
- * Reject unusable namespaces before starting a process.
15
- * @param request - Host-owned binding declarations.
16
- * @returns Namespaces keyed by their declared global.
13
+ * Lossless-JSON snapshots for the dependency-free source bootstrap closure.
14
+ * @module @deepseek-ai/dsh-ptc-runtime-node/json-wire
17
15
  */
18
- function validateBindings(request) {
19
- const bindings = /* @__PURE__ */ new Map();
20
- for (const namespace of request.bindings) {
21
- if (!IDENTIFIER.test(namespace.global) || PORTABLE_RESERVED_WORDS.has(namespace.global)) throw new Error(`dsh-ptc-runtime-node: binding global ${JSON.stringify(namespace.global)} is not a usable identifier`);
22
- if (RESERVED_BINDING_GLOBALS.has(namespace.global)) throw new Error(`dsh-ptc-runtime-node: reserved binding global ${JSON.stringify(namespace.global)}`);
23
- if (bindings.has(namespace.global)) throw new Error(`dsh-ptc-runtime-node: duplicate binding global ${JSON.stringify(namespace.global)}`);
24
- bindings.set(namespace.global, namespace);
25
- }
26
- const errorClassNames = /* @__PURE__ */ new Set();
27
- for (const namespace of request.bindings) {
28
- const descriptor = namespace.errorClass;
29
- if (!descriptor) continue;
30
- if (!IDENTIFIER.test(descriptor.name) || PORTABLE_RESERVED_WORDS.has(descriptor.name)) throw new Error(`dsh-ptc-runtime-node: binding error class ${JSON.stringify(descriptor.name)} is not a usable identifier`);
31
- if (RESERVED_BINDING_GLOBALS.has(descriptor.name)) throw new Error(`dsh-ptc-runtime-node: reserved binding global ${JSON.stringify(descriptor.name)}`);
32
- if (bindings.has(descriptor.name) || errorClassNames.has(descriptor.name)) throw new Error(`dsh-ptc-runtime-node: duplicate injected global ${JSON.stringify(descriptor.name)}`);
33
- const member = descriptor.memberNameProperty;
34
- if (member.length === 0 || RESERVED_ERROR_MEMBERS.has(member) || DUNDER_MEMBER.test(member)) throw new Error(`dsh-ptc-runtime-node: binding error member property ${JSON.stringify(descriptor.memberNameProperty)} is not usable`);
35
- errorClassNames.add(descriptor.name);
36
- }
37
- return bindings;
38
- }
39
- //#endregion
40
- //#region lib/types/output-json.js
41
- /** JSON string-prefix accounting for the outer-output ledger. @module @deepseek-ai/dsh-ptc-runtime-node/output-json */
42
- const intrinsicReflectApply$1 = Reflect.apply;
16
+ const intrinsicFunctionToString = Reflect.get(Function.prototype, "toString");
17
+ const intrinsicReflectApply$1 = Reflect.get(Reflect, "apply");
18
+ const IntrinsicError = Error;
19
+ const IntrinsicSet = Set;
43
20
  const intrinsicArrayIsArray$1 = Array.isArray;
44
- const IntrinsicBuffer = Buffer;
45
- const intrinsicBufferByteLength = Reflect.get(Buffer, "byteLength");
21
+ const intrinsicArrayPrototype = Array.prototype;
22
+ const intrinsicNumberIsFinite = Number.isFinite;
23
+ const intrinsicNumberIsSafeInteger = Number.isSafeInteger;
46
24
  const intrinsicObjectCreate$1 = Object.create;
47
25
  const intrinsicObjectDefineProperty$1 = Object.defineProperty;
26
+ const intrinsicObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
27
+ const intrinsicObjectGetPrototypeOf = Object.getPrototypeOf;
28
+ const intrinsicObjectHasOwn = Object.hasOwn;
29
+ const intrinsicObjectIs = Object.is;
48
30
  const intrinsicObjectKeys$1 = Object.keys;
49
- const intrinsicString = String;
50
- const intrinsicStringCharCodeAt = Reflect.get(String.prototype, "charCodeAt");
51
- const intrinsicStringCodePointAt = Reflect.get(String.prototype, "codePointAt");
52
- const intrinsicStringSlice = Reflect.get(String.prototype, "slice");
31
+ const intrinsicObjectPrototype = Object.prototype;
32
+ const intrinsicObjectPropertyIsEnumerable = Reflect.get(intrinsicObjectPrototype, "propertyIsEnumerable");
33
+ const intrinsicReflectOwnKeys = Reflect.ownKeys;
34
+ const intrinsicSetAdd = Reflect.get(Set.prototype, "add");
35
+ Reflect.get(Set.prototype, "delete");
36
+ const intrinsicSetHas = Reflect.get(Set.prototype, "has");
53
37
  /** Build a data descriptor that cannot inherit model-defined accessor fields. */
54
38
  function dataDescriptor$1(value) {
55
39
  const descriptor = intrinsicObjectCreate$1(null);
@@ -64,10 +48,6 @@ function defineEnumerableDataProperty$1(target, key, value) {
64
48
  descriptor.writable = true;
65
49
  intrinsicObjectDefineProperty$1(target, key, descriptor);
66
50
  }
67
- /** UTF-8 byte length through the module-captured Node intrinsic. */
68
- function byteLength(text) {
69
- return intrinsicReflectApply$1(intrinsicBufferByteLength, IntrinsicBuffer, [text, "utf8"]);
70
- }
71
51
  /** Append without consulting a model-mutated `Array.prototype`. */
72
52
  function append$1(target, value) {
73
53
  defineEnumerableDataProperty$1(target, target.length, value);
@@ -80,124 +60,438 @@ function takeLast$1(target) {
80
60
  intrinsicObjectDefineProperty$1(target, "length", dataDescriptor$1(index));
81
61
  return value;
82
62
  }
83
- /** One code-point-aligned character from a string. */
84
- function characterAt(text, index) {
85
- return intrinsicReflectApply$1(intrinsicStringSlice, text, [index, index + (intrinsicReflectApply$1(intrinsicStringCodePointAt, text, [index]) > 65535 ? 2 : 1)]);
63
+ /** Whether one captured-intrinsic Set contains a value. */
64
+ function setHas(target, value) {
65
+ return intrinsicReflectApply$1(intrinsicSetHas, target, [value]);
86
66
  }
87
- /** Serialized bytes contributed by one complete Unicode code point inside JSON quotes. */
88
- function serializedCharacterBytes(character) {
89
- if (character.length === 2) return 4;
90
- if (character === "\"" || character === "\\") return 2;
91
- const code = intrinsicReflectApply$1(intrinsicStringCharCodeAt, character, [0]);
92
- if (code >= 55296 && code <= 57343) return 6;
93
- if (code < 32) return code === 8 || code === 9 || code === 10 || code === 12 || code === 13 ? 2 : 6;
94
- return byteLength(character);
67
+ /** Add to one captured-intrinsic Set. */
68
+ function setAdd(target, value) {
69
+ intrinsicReflectApply$1(intrinsicSetAdd, target, [value]);
95
70
  }
96
- /**
97
- * Measure one JSON string without materializing its complete escaped form.
98
- * @param text - the candidate string.
99
- * @param maxBytes - largest serialized size the caller can admit.
100
- * @returns Exact serialized bytes, or `undefined` as soon as the cap is crossed.
101
- */
102
- function jsonStringBytesUpTo(text, maxBytes) {
103
- if (maxBytes < 2) return void 0;
104
- let bytes = 2;
105
- for (let index = 0; index < text.length;) {
106
- const character = characterAt(text, index);
107
- bytes += serializedCharacterBytes(character);
108
- if (bytes > maxBytes) return void 0;
109
- index += character.length;
71
+ /** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
72
+ function hasIntrinsicConstructor(prototype, name) {
73
+ const constructor = intrinsicObjectGetOwnPropertyDescriptor(prototype, "constructor")?.value;
74
+ if (typeof constructor !== "function") return false;
75
+ try {
76
+ return constructor.name === name && constructor.prototype === prototype && intrinsicReflectApply$1(intrinsicFunctionToString, constructor, []) === `function ${name}() { [native code] }`;
77
+ } catch {
78
+ return false;
110
79
  }
111
- return bytes;
80
+ }
81
+ /** Whether a candidate is a foreign realm's intrinsic `Object.prototype`. */
82
+ function isForeignIntrinsicObjectPrototype(value) {
83
+ return intrinsicObjectGetPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
84
+ }
85
+ /** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
86
+ function hasPlainArrayPrototype(value) {
87
+ const prototype = intrinsicObjectGetPrototypeOf(value);
88
+ if (prototype === intrinsicArrayPrototype) return true;
89
+ if (!intrinsicArrayIsArray$1(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
90
+ const objectPrototype = intrinsicObjectGetPrototypeOf(prototype);
91
+ return typeof objectPrototype === "object" && objectPrototype !== null && isForeignIntrinsicObjectPrototype(objectPrototype);
92
+ }
93
+ /** Whether an object is a plain or null-prototype record from any JavaScript realm. */
94
+ function hasPlainObjectPrototype(value) {
95
+ const prototype = intrinsicObjectGetPrototypeOf(value);
96
+ return prototype === null || prototype === intrinsicObjectPrototype || typeof prototype === "object" && isForeignIntrinsicObjectPrototype(prototype);
97
+ }
98
+ /** Return every JSON-visible object key, or reject own data JSON would discard. */
99
+ function enumerableStringKeys(value) {
100
+ const keys = intrinsicReflectOwnKeys(value);
101
+ for (let index = 0; index < keys.length; index++) {
102
+ const key = keys[index];
103
+ if (typeof key !== "string" || !intrinsicReflectApply$1(intrinsicObjectPropertyIsEnumerable, value, [key])) return void 0;
104
+ }
105
+ return keys;
112
106
  }
113
107
  /**
114
- * Measure one lossless JSON value without allocating its serialized form.
115
- * @param value - already validated lossless JSON.
116
- * @param maxBytes - largest serialized size the caller can admit.
117
- * @returns Exact serialized bytes, or `undefined` as soon as the cap is crossed.
108
+ * Flatten one validated JSON value for the process control channel.
109
+ * @param value - the lossless JSON value to transport.
110
+ * @returns a pre-order token stream whose own nesting is bounded.
118
111
  */
119
- function jsonValueBytesUpTo(value, maxBytes) {
120
- let bytes = 0;
121
- const add = (cost) => {
122
- bytes += cost;
123
- return bytes <= maxBytes;
124
- };
125
- const tasks = [{
126
- kind: "value",
127
- value
128
- }];
129
- for (let task = takeLast$1(tasks); task !== void 0; task = takeLast$1(tasks)) {
130
- if (task.kind === "value") {
131
- const current = task.value;
132
- if (current === null) {
133
- if (!add(4)) return void 0;
134
- } else if (typeof current === "string") {
135
- const stringBytes = jsonStringBytesUpTo(current, maxBytes - bytes);
136
- if (stringBytes === void 0) return void 0;
137
- bytes += stringBytes;
138
- } else if (typeof current === "number") {
139
- if (!add(byteLength(intrinsicString(current)))) return void 0;
140
- } else if (typeof current === "boolean") {
141
- if (!add(current ? 4 : 5)) return void 0;
142
- } else if (intrinsicArrayIsArray$1(current)) {
143
- if (!add(2)) return void 0;
144
- if (current.length > 0) append$1(tasks, {
145
- kind: "array",
146
- value: current,
147
- index: 0
148
- });
149
- } else {
150
- if (!add(2)) return void 0;
151
- const keys = intrinsicObjectKeys$1(current);
152
- if (keys.length > 0) append$1(tasks, {
153
- kind: "object",
154
- value: current,
155
- keys,
156
- index: 0
157
- });
158
- }
112
+ function encodePtcJsonWire(value) {
113
+ const wire = [];
114
+ const pending = [value];
115
+ for (let current = takeLast$1(pending); current !== void 0; current = takeLast$1(pending)) {
116
+ if (current === null || typeof current === "boolean" || typeof current === "number" || typeof current === "string") {
117
+ append$1(wire, current);
159
118
  continue;
160
119
  }
161
- if (task.index > 0 && !add(1)) return void 0;
162
- if (task.kind === "array") {
163
- const item = task.value[task.index];
164
- if (item === void 0) return void 0;
165
- if (task.index + 1 < task.value.length) append$1(tasks, {
166
- ...task,
167
- index: task.index + 1
168
- });
169
- append$1(tasks, {
170
- kind: "value",
171
- value: item
120
+ if (intrinsicArrayIsArray$1(current)) {
121
+ append$1(wire, {
122
+ kind: "array",
123
+ length: current.length
172
124
  });
125
+ for (let index = current.length - 1; index >= 0; index--) {
126
+ const item = current[index];
127
+ if (item === void 0) throw new IntrinsicError("cannot encode a sparse JSON array");
128
+ append$1(pending, item);
129
+ }
173
130
  continue;
174
131
  }
175
- const key = task.keys[task.index];
176
- /* v8 ignore next -- an object frame is created and advanced only for an existing Object.keys entry. */
177
- if (key === void 0) return void 0;
178
- const keyBytes = jsonStringBytesUpTo(key, maxBytes - bytes);
179
- if (keyBytes === void 0) return void 0;
180
- if (!add(keyBytes + 1)) return void 0;
181
- const item = task.value[key];
182
- if (item === void 0) return void 0;
183
- if (task.index + 1 < task.keys.length) append$1(tasks, {
184
- ...task,
185
- index: task.index + 1
186
- });
187
- append$1(tasks, {
188
- kind: "value",
189
- value: item
132
+ const keys = intrinsicObjectKeys$1(current);
133
+ append$1(wire, {
134
+ kind: "object",
135
+ keys
190
136
  });
137
+ for (let index = keys.length - 1; index >= 0; index--) {
138
+ const key = keys[index];
139
+ /* v8 ignore next -- the loop is bounded by the captured key count. */
140
+ if (key === void 0) throw new IntrinsicError("cannot encode a missing JSON object key");
141
+ const item = current[key];
142
+ if (item === void 0) throw new IntrinsicError("cannot encode an undefined JSON object property");
143
+ append$1(pending, item);
144
+ }
145
+ }
146
+ return wire;
147
+ }
148
+ /** Whether an array contains exactly its dense indexed slots and `length`. */
149
+ function isDenseArray(value) {
150
+ if (!hasPlainArrayPrototype(value) || intrinsicReflectOwnKeys(value).length !== value.length + 1) return false;
151
+ for (let index = 0; index < value.length; index++) if (!intrinsicObjectHasOwn(value, index)) return false;
152
+ return true;
153
+ }
154
+ /** Whether one exact string-key list contains a key, without consulting its prototype. */
155
+ function keysContain(keys, expected) {
156
+ for (let index = 0; index < keys.length; index++) if (keys[index] === expected) return true;
157
+ return false;
158
+ }
159
+ /** Return one exact container marker, or reject any extra/missing fields. */
160
+ function containerToken(value) {
161
+ if (intrinsicArrayIsArray$1(value) || !hasPlainObjectPrototype(value)) return void 0;
162
+ const keys = enumerableStringKeys(value);
163
+ if (keys === void 0) return void 0;
164
+ const token = value;
165
+ if (token.kind === "array") {
166
+ if (keys.length !== 2 || !keysContain(keys, "kind") || !keysContain(keys, "length")) return void 0;
167
+ const length = token.length;
168
+ return typeof length === "number" && intrinsicNumberIsSafeInteger(length) && length >= 0 ? {
169
+ kind: "array",
170
+ length
171
+ } : void 0;
172
+ }
173
+ if (token.kind === "object") {
174
+ if (keys.length !== 2 || !keysContain(keys, "kind") || !keysContain(keys, "keys")) return void 0;
175
+ const objectKeys = token.keys;
176
+ if (!intrinsicArrayIsArray$1(objectKeys) || !isDenseArray(objectKeys)) return void 0;
177
+ const unique = new IntrinsicSet();
178
+ const normalizedKeys = [];
179
+ const objectKeyValues = objectKeys;
180
+ for (let index = 0; index < objectKeyValues.length; index++) {
181
+ const key = objectKeyValues[index];
182
+ if (typeof key !== "string" || setHas(unique, key)) return void 0;
183
+ setAdd(unique, key);
184
+ append$1(normalizedKeys, key);
185
+ }
186
+ return {
187
+ kind: "object",
188
+ keys: normalizedKeys
189
+ };
191
190
  }
192
- return bytes;
193
191
  }
194
192
  /**
195
- * Return the longest code-point-aligned prefix whose JSON string encoding,
196
- * including its surrounding quotes, fits `maxBytes`.
197
- *
198
- * @param text - the candidate string.
199
- * @param maxBytes - serialized JSON-string bytes available.
200
- * @returns the fitting prefix, or an empty string when even useful content cannot fit.
193
+ * Rebuild one lossless JSON value from the flat process wire format.
194
+ * Malformed or incomplete traffic returns `undefined`; traversal is iterative
195
+ * and therefore independent of the transported value's application depth.
196
+ * @param input - untrusted message-port payload.
197
+ * @returns the detached JSON value, or `undefined` when the wire is invalid.
198
+ */
199
+ function decodePtcJsonWire(input) {
200
+ try {
201
+ if (!intrinsicArrayIsArray$1(input) || !isDenseArray(input) || input.length === 0) return void 0;
202
+ const wire = input;
203
+ const frames = [];
204
+ let root;
205
+ let rootAssigned = false;
206
+ const attach = (value) => {
207
+ const parent = frames[frames.length - 1];
208
+ if (!parent) {
209
+ if (rootAssigned) return false;
210
+ root = value;
211
+ rootAssigned = true;
212
+ return true;
213
+ }
214
+ /* v8 ignore next -- completed frames are popped before another token can attach. */
215
+ if (parent.index >= (parent.kind === "array" ? parent.length : parent.keys.length)) return false;
216
+ if (parent.kind === "array") append$1(parent.target, value);
217
+ else {
218
+ const key = parent.keys[parent.index];
219
+ /* v8 ignore next -- object frames are built from validated keys and their exact length. */
220
+ if (key === void 0) return false;
221
+ defineEnumerableDataProperty$1(parent.target, key, value);
222
+ }
223
+ parent.index += 1;
224
+ return true;
225
+ };
226
+ for (let tokenIndex = 0; tokenIndex < wire.length; tokenIndex++) {
227
+ const token = wire[tokenIndex];
228
+ let value;
229
+ let frame;
230
+ if (token === null || typeof token === "boolean" || typeof token === "string") value = token;
231
+ else if (typeof token === "number") {
232
+ if (!intrinsicNumberIsFinite(token) || intrinsicObjectIs(token, -0)) return void 0;
233
+ value = token;
234
+ } else {
235
+ if (typeof token !== "object") return void 0;
236
+ const marker = containerToken(token);
237
+ if (!marker) return void 0;
238
+ const remainingTokens = wire.length - tokenIndex - 1;
239
+ if (marker.kind === "array") {
240
+ if (marker.length > remainingTokens) return void 0;
241
+ const target = [];
242
+ value = target;
243
+ if (marker.length > 0) frame = {
244
+ kind: "array",
245
+ target,
246
+ length: marker.length,
247
+ index: 0
248
+ };
249
+ } else {
250
+ if (marker.keys.length > remainingTokens) return void 0;
251
+ const target = {};
252
+ value = target;
253
+ if (marker.keys.length > 0) frame = {
254
+ kind: "object",
255
+ target,
256
+ keys: marker.keys,
257
+ index: 0
258
+ };
259
+ }
260
+ }
261
+ if (!attach(value)) return void 0;
262
+ if (frame) append$1(frames, frame);
263
+ while (frames.length > 0) {
264
+ const current = frames[frames.length - 1];
265
+ /* v8 ignore next -- the loop condition guarantees a final frame. */
266
+ if (current === void 0) break;
267
+ if (current.index < (current.kind === "array" ? current.length : current.keys.length)) break;
268
+ takeLast$1(frames);
269
+ }
270
+ }
271
+ return frames.length === 0 ? root : void 0;
272
+ } catch {
273
+ return;
274
+ }
275
+ }
276
+ //#endregion
277
+ //#region lib/types/bindings.js
278
+ /** Validate and capture the binding names available to one program. */
279
+ const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
280
+ /**
281
+ * Reject unusable namespaces before starting a process.
282
+ * @param request - Host-owned binding declarations.
283
+ * @returns Namespaces keyed by their declared global.
284
+ */
285
+ function validateBindings(request) {
286
+ const bindings = /* @__PURE__ */ new Map();
287
+ for (const namespace of request.bindings) {
288
+ if (!IDENTIFIER.test(namespace.global) || PORTABLE_RESERVED_WORDS.has(namespace.global)) throw new Error(`dsh-ptc-runtime-node: binding global ${JSON.stringify(namespace.global)} is not a usable identifier`);
289
+ if (RESERVED_BINDING_GLOBALS.has(namespace.global)) throw new Error(`dsh-ptc-runtime-node: reserved binding global ${JSON.stringify(namespace.global)}`);
290
+ if (bindings.has(namespace.global)) throw new Error(`dsh-ptc-runtime-node: duplicate binding global ${JSON.stringify(namespace.global)}`);
291
+ bindings.set(namespace.global, namespace);
292
+ }
293
+ const errorClassNames = /* @__PURE__ */ new Set();
294
+ for (const namespace of request.bindings) {
295
+ const descriptor = namespace.errorClass;
296
+ if (!descriptor) continue;
297
+ if (!IDENTIFIER.test(descriptor.name) || PORTABLE_RESERVED_WORDS.has(descriptor.name)) throw new Error(`dsh-ptc-runtime-node: binding error class ${JSON.stringify(descriptor.name)} is not a usable identifier`);
298
+ if (RESERVED_BINDING_GLOBALS.has(descriptor.name)) throw new Error(`dsh-ptc-runtime-node: reserved binding global ${JSON.stringify(descriptor.name)}`);
299
+ if (bindings.has(descriptor.name) || errorClassNames.has(descriptor.name)) throw new Error(`dsh-ptc-runtime-node: duplicate injected global ${JSON.stringify(descriptor.name)}`);
300
+ const member = descriptor.memberNameProperty;
301
+ if (member.length === 0 || RESERVED_ERROR_MEMBERS.has(member) || DUNDER_MEMBER.test(member)) throw new Error(`dsh-ptc-runtime-node: binding error member property ${JSON.stringify(descriptor.memberNameProperty)} is not usable`);
302
+ errorClassNames.add(descriptor.name);
303
+ }
304
+ return bindings;
305
+ }
306
+ /**
307
+ * Invoke a host binding and encode its lossless result or bounded-channel rejection.
308
+ * @param id - Program-issued correlation identifier.
309
+ * @param binding - Current evaluation's declared function, absent if it was removed.
310
+ * @param args - Validated JSON arguments detached from the program.
311
+ * @returns A reply suitable for the runtime's bounded control channel.
312
+ */
313
+ async function replyToBinding(id, binding, args) {
314
+ try {
315
+ if (!binding) throw new Error("binding is unavailable in this evaluation; reacquire the current tool");
316
+ const value = snapshotJsonValue(await binding(args));
317
+ if (value === void 0) throw new Error("binding resolution must be lossless JSON");
318
+ return {
319
+ type: "reply",
320
+ id,
321
+ ok: true,
322
+ value: encodePtcJsonWire(value)
323
+ };
324
+ } catch (error) {
325
+ return {
326
+ type: "reply",
327
+ id,
328
+ ok: false,
329
+ message: error instanceof Error ? error.message : String(error)
330
+ };
331
+ }
332
+ }
333
+ //#endregion
334
+ //#region lib/types/output-json.js
335
+ /** JSON string-prefix accounting for the outer-output ledger. @module @deepseek-ai/dsh-ptc-runtime-node/output-json */
336
+ const intrinsicReflectApply = Reflect.apply;
337
+ const intrinsicArrayIsArray = Array.isArray;
338
+ const IntrinsicBuffer = Buffer;
339
+ const intrinsicBufferByteLength = Reflect.get(Buffer, "byteLength");
340
+ const intrinsicObjectCreate = Object.create;
341
+ const intrinsicObjectDefineProperty = Object.defineProperty;
342
+ const intrinsicObjectKeys = Object.keys;
343
+ const intrinsicString = String;
344
+ const intrinsicStringCharCodeAt = Reflect.get(String.prototype, "charCodeAt");
345
+ const intrinsicStringCodePointAt = Reflect.get(String.prototype, "codePointAt");
346
+ const intrinsicStringSlice = Reflect.get(String.prototype, "slice");
347
+ /** Build a data descriptor that cannot inherit model-defined accessor fields. */
348
+ function dataDescriptor(value) {
349
+ const descriptor = intrinsicObjectCreate(null);
350
+ descriptor.value = value;
351
+ return descriptor;
352
+ }
353
+ /** Define an ordinary enumerable data slot without a prototype-bearing descriptor. */
354
+ function defineEnumerableDataProperty(target, key, value) {
355
+ const descriptor = dataDescriptor(value);
356
+ descriptor.enumerable = true;
357
+ descriptor.configurable = true;
358
+ descriptor.writable = true;
359
+ intrinsicObjectDefineProperty(target, key, descriptor);
360
+ }
361
+ /** UTF-8 byte length through the module-captured Node intrinsic. */
362
+ function byteLength(text) {
363
+ return intrinsicReflectApply(intrinsicBufferByteLength, IntrinsicBuffer, [text, "utf8"]);
364
+ }
365
+ /** Append without consulting a model-mutated `Array.prototype`. */
366
+ function append(target, value) {
367
+ defineEnumerableDataProperty(target, target.length, value);
368
+ }
369
+ /** Pop without consulting a model-mutated `Array.prototype`. */
370
+ function takeLast(target) {
371
+ if (target.length === 0) return void 0;
372
+ const index = target.length - 1;
373
+ const value = target[index];
374
+ intrinsicObjectDefineProperty(target, "length", dataDescriptor(index));
375
+ return value;
376
+ }
377
+ /** One code-point-aligned character from a string. */
378
+ function characterAt(text, index) {
379
+ return intrinsicReflectApply(intrinsicStringSlice, text, [index, index + (intrinsicReflectApply(intrinsicStringCodePointAt, text, [index]) > 65535 ? 2 : 1)]);
380
+ }
381
+ /** Serialized bytes contributed by one complete Unicode code point inside JSON quotes. */
382
+ function serializedCharacterBytes(character) {
383
+ if (character.length === 2) return 4;
384
+ if (character === "\"" || character === "\\") return 2;
385
+ const code = intrinsicReflectApply(intrinsicStringCharCodeAt, character, [0]);
386
+ if (code >= 55296 && code <= 57343) return 6;
387
+ if (code < 32) return code === 8 || code === 9 || code === 10 || code === 12 || code === 13 ? 2 : 6;
388
+ return byteLength(character);
389
+ }
390
+ /**
391
+ * Measure one JSON string without materializing its complete escaped form.
392
+ * @param text - the candidate string.
393
+ * @param maxBytes - largest serialized size the caller can admit.
394
+ * @returns Exact serialized bytes, or `undefined` as soon as the cap is crossed.
395
+ */
396
+ function jsonStringBytesUpTo(text, maxBytes) {
397
+ if (maxBytes < 2) return void 0;
398
+ let bytes = 2;
399
+ for (let index = 0; index < text.length;) {
400
+ const character = characterAt(text, index);
401
+ bytes += serializedCharacterBytes(character);
402
+ if (bytes > maxBytes) return void 0;
403
+ index += character.length;
404
+ }
405
+ return bytes;
406
+ }
407
+ /**
408
+ * Measure one lossless JSON value without allocating its serialized form.
409
+ * @param value - already validated lossless JSON.
410
+ * @param maxBytes - largest serialized size the caller can admit.
411
+ * @returns Exact serialized bytes, or `undefined` as soon as the cap is crossed.
412
+ */
413
+ function jsonValueBytesUpTo(value, maxBytes) {
414
+ let bytes = 0;
415
+ const add = (cost) => {
416
+ bytes += cost;
417
+ return bytes <= maxBytes;
418
+ };
419
+ const tasks = [{
420
+ kind: "value",
421
+ value
422
+ }];
423
+ for (let task = takeLast(tasks); task !== void 0; task = takeLast(tasks)) {
424
+ if (task.kind === "value") {
425
+ const current = task.value;
426
+ if (current === null) {
427
+ if (!add(4)) return void 0;
428
+ } else if (typeof current === "string") {
429
+ const stringBytes = jsonStringBytesUpTo(current, maxBytes - bytes);
430
+ if (stringBytes === void 0) return void 0;
431
+ bytes += stringBytes;
432
+ } else if (typeof current === "number") {
433
+ if (!add(byteLength(intrinsicString(current)))) return void 0;
434
+ } else if (typeof current === "boolean") {
435
+ if (!add(current ? 4 : 5)) return void 0;
436
+ } else if (intrinsicArrayIsArray(current)) {
437
+ if (!add(2)) return void 0;
438
+ if (current.length > 0) append(tasks, {
439
+ kind: "array",
440
+ value: current,
441
+ index: 0
442
+ });
443
+ } else {
444
+ if (!add(2)) return void 0;
445
+ const keys = intrinsicObjectKeys(current);
446
+ if (keys.length > 0) append(tasks, {
447
+ kind: "object",
448
+ value: current,
449
+ keys,
450
+ index: 0
451
+ });
452
+ }
453
+ continue;
454
+ }
455
+ if (task.index > 0 && !add(1)) return void 0;
456
+ if (task.kind === "array") {
457
+ const item = task.value[task.index];
458
+ if (item === void 0) return void 0;
459
+ if (task.index + 1 < task.value.length) append(tasks, {
460
+ ...task,
461
+ index: task.index + 1
462
+ });
463
+ append(tasks, {
464
+ kind: "value",
465
+ value: item
466
+ });
467
+ continue;
468
+ }
469
+ const key = task.keys[task.index];
470
+ /* v8 ignore next -- an object frame is created and advanced only for an existing Object.keys entry. */
471
+ if (key === void 0) return void 0;
472
+ const keyBytes = jsonStringBytesUpTo(key, maxBytes - bytes);
473
+ if (keyBytes === void 0) return void 0;
474
+ if (!add(keyBytes + 1)) return void 0;
475
+ const item = task.value[key];
476
+ if (item === void 0) return void 0;
477
+ if (task.index + 1 < task.keys.length) append(tasks, {
478
+ ...task,
479
+ index: task.index + 1
480
+ });
481
+ append(tasks, {
482
+ kind: "value",
483
+ value: item
484
+ });
485
+ }
486
+ return bytes;
487
+ }
488
+ /**
489
+ * Return the longest code-point-aligned prefix whose JSON string encoding,
490
+ * including its surrounding quotes, fits `maxBytes`.
491
+ *
492
+ * @param text - the candidate string.
493
+ * @param maxBytes - serialized JSON-string bytes available.
494
+ * @returns the fitting prefix, or an empty string when even useful content cannot fit.
201
495
  */
202
496
  function truncateJsonStringBytes(text, maxBytes) {
203
497
  if (maxBytes < 2) return "";
@@ -211,7 +505,7 @@ function truncateJsonStringBytes(text, maxBytes) {
211
505
  end += character.length;
212
506
  index += character.length;
213
507
  }
214
- return end === text.length ? text : intrinsicReflectApply$1(intrinsicStringSlice, text, [0, end]);
508
+ return end === text.length ? text : intrinsicReflectApply(intrinsicStringSlice, text, [0, end]);
215
509
  }
216
510
  //#endregion
217
511
  //#region lib/types/channel.js
@@ -340,39 +634,186 @@ var JsonChannel = class {
340
634
  //#region lib/types/launch.js
341
635
  /** Select source or built bootstrap assets in the mounted execution world. */
342
636
  /**
637
+ * Resolve the local carrier default or validate an explicit execution-world invocation.
638
+ * @param config - explicit remote or local worker configuration; omitted selects this carrier.
639
+ * @returns the worker invocation without guessing from the execution world's paths.
640
+ */
641
+ function resolveLaunch(config) {
642
+ const launch = config ?? {
643
+ kind: "pkg" in process ? "embedded" : "node-script",
644
+ executable: process.execPath
645
+ };
646
+ if (launch.executable.length === 0) throw new Error("ptc-runtime-node: launch.executable must be non-empty");
647
+ if (launch.kind === "node-script" && launch.bootstrapPath !== void 0 && !isAbsolute(launch.bootstrapPath)) throw new Error("ptc-runtime-node: launch.bootstrapPath must be absolute");
648
+ return launch;
649
+ }
650
+ /**
343
651
  * Select explicit arguments without inheriting host loader or inspector flags.
344
652
  * @param fs - Filesystem mapping host bootstrap assets into the process world.
345
- * @param config - Optional preinstalled built bootstrap.
653
+ * @param config - resolved worker invocation.
346
654
  * @param maxMessageBytes - Validated frame and queued-write limit.
655
+ * @param persistent - Select the in-memory REPL bootstrap instead of the one-shot evaluator.
347
656
  * @returns Arguments following the resolved Node executable.
348
657
  */
349
- function bootstrapArgs(fs, config, maxMessageBytes) {
350
- if (config.bootstrapPath !== void 0) return [config.bootstrapPath, String(maxMessageBytes)];
351
- if ("pkg" in process) return [String(maxMessageBytes)];
658
+ function bootstrapArgs(fs, config, maxMessageBytes, persistent = false) {
659
+ switch (config.kind) {
660
+ case "embedded":
661
+ if (persistent) throw new Error("Persistent PTC requires a node-script launch");
662
+ return [String(maxMessageBytes)];
663
+ case "node-script": return scriptBootstrapArgs(fs, config.bootstrapPath, maxMessageBytes, persistent);
664
+ /* v8 ignore next -- Callers supply the resolved, closed LaunchConfig union. */
665
+ default: return assertNever(config);
666
+ }
667
+ }
668
+ function scriptBootstrapArgs(fs, bootstrapPath, maxMessageBytes, persistent) {
669
+ const moduleName = persistent ? "persistent-process" : "process";
670
+ if (bootstrapPath !== void 0) return [bootstrapPath, String(maxMessageBytes)];
352
671
  const mapped = (path) => {
353
672
  const result = fs.processPathFromHostPath(path);
354
673
  if (result === void 0) throw new Error(`PTC runtime bootstrap is unavailable in the subprocess execution world: ${path}`);
355
674
  return result;
356
675
  };
357
676
  /* v8 ignore next 3 -- built-lib.e2e.ts executes the bundled provider and sibling process.js under plain Node. */
358
- if (!new URL(import.meta.url).pathname.endsWith(".ts")) return [mapped(fileURLToPath(new URL("./process.js", import.meta.url))), String(maxMessageBytes)];
359
- const entry = mapped(fileURLToPath(new URL("./process.ts", import.meta.url)));
677
+ if (!new URL(import.meta.url).pathname.endsWith(".ts")) return [mapped(fileURLToPath(new URL(`./${moduleName}.js`, import.meta.url))), String(maxMessageBytes)];
678
+ const entry = mapped(fileURLToPath(new URL(`./${moduleName}.ts`, import.meta.url)));
360
679
  const helper = mapped(resolve(dirname(fileURLToPath(import.meta.resolve("@deepseek-ai/dsh-subprocess/package.json"))), "src/control.ts"));
680
+ const main = persistent ? "runPersistentMain" : "runNodeMain";
361
681
  return [
362
682
  "--input-type=module",
363
683
  "--eval",
364
- `const {openInheritedControlChannel}=await import(${JSON.stringify(pathToFileURL(helper).href)});const {runNodeMain}=await import(${JSON.stringify(pathToFileURL(entry).href)});await runNodeMain(openInheritedControlChannel(),${maxMessageBytes},process);`
684
+ `const {openInheritedControlChannel}=await import(${JSON.stringify(pathToFileURL(helper).href)});const {${main}}=await import(${JSON.stringify(pathToFileURL(entry).href)});await ${main}(openInheritedControlChannel(),${maxMessageBytes},process);`
365
685
  ];
366
686
  }
367
687
  //#endregion
688
+ //#region lib/types/config.js
689
+ /** Execution mode and resource limits for the Node PTC provider. */
690
+ /** Loader defaults for the Node PTC provider. */
691
+ const NodeConfig = z.object({
692
+ executionMode: z.union([z.const("per-call"), z.const("persistent-in-memory")]).default("per-call"),
693
+ timeoutMs: z.number().default(12e4),
694
+ maxTimeoutMs: z.number().default(6e5),
695
+ maxOutputBytes: z.number().default(67108864),
696
+ maxOldGenerationSizeMb: z.number().default(512),
697
+ maxMessageBytes: z.number().default(134217728),
698
+ maxPendingCalls: z.number().default(128),
699
+ graceMs: z.number().default(3e3),
700
+ launch: z.union([z.object({
701
+ kind: z.const("node-script").required(),
702
+ executable: z.string().required(),
703
+ bootstrapPath: z.string()
704
+ }), z.object({
705
+ kind: z.const("embedded").required(),
706
+ executable: z.string().required()
707
+ })])
708
+ });
709
+ /**
710
+ * Validate resolved Node limits and paths.
711
+ * @param config - Loader-defaulted fields with the selected executable.
712
+ */
713
+ function validateConfig(config) {
714
+ for (const [key, value] of Object.entries(config)) if (typeof value === "number" && (!Number.isFinite(value) || value <= 0)) throw new Error(`ptc-runtime-node: ${key} must be positive and finite`);
715
+ for (const key of [
716
+ "timeoutMs",
717
+ "maxTimeoutMs",
718
+ "graceMs"
719
+ ]) if (config[key] > MAX_TIMER_DELAY_MS) throw new Error(`ptc-runtime-node: ${key} exceeds the supported timer range`);
720
+ if (!Number.isSafeInteger(config.maxOutputBytes) || config.maxOutputBytes < 4) throw new Error("ptc-runtime-node: maxOutputBytes must be an integer of at least 4");
721
+ if (!Number.isSafeInteger(config.maxMessageBytes) || config.maxMessageBytes > 4294967295) throw new Error("ptc-runtime-node: maxMessageBytes must fit an unsigned 32-bit frame length");
722
+ if (!Number.isSafeInteger(config.maxPendingCalls)) throw new Error("ptc-runtime-node: maxPendingCalls must be an integer");
723
+ if (!Number.isSafeInteger(config.maxOldGenerationSizeMb)) throw new Error("ptc-runtime-node: maxOldGenerationSizeMb must be an integer");
724
+ }
725
+ //#endregion
726
+ //#region lib/types/policy-processes.js
727
+ /** Owns asynchronous process cleanup without delaying permission setters. */
728
+ var PolicyProcesses = class {
729
+ ctx;
730
+ processes = /* @__PURE__ */ new Set();
731
+ /**
732
+ * Begin cleanup on policy changes, even when no subsequent program runs.
733
+ * @param ctx - Node provider lifecycle and Session services.
734
+ */
735
+ constructor(ctx) {
736
+ this.ctx = ctx;
737
+ ctx.on("session/event", (session, event) => {
738
+ if (event.type !== "sandbox/mode") return;
739
+ for (const process of this.processes) {
740
+ if (process.sessionId !== session.id || process.mode === event.data.mode) continue;
741
+ process.invalidated = true;
742
+ this.release(process).catch((error) => {
743
+ ctx.logger.warn(error);
744
+ });
745
+ }
746
+ }, { global: true });
747
+ ctx.effect(() => async () => {
748
+ await this.settle([...this.processes]);
749
+ }, "Node PTC policy cleanup");
750
+ }
751
+ /**
752
+ * Track an admitted call or retained process until its owner confirms cleanup.
753
+ * @param policy - Resolved process policy, including an optional Session identity.
754
+ * @param release - Idempotent cancellation and awaited cleanup.
755
+ * @returns Removal after successful process cleanup.
756
+ */
757
+ register(policy, release) {
758
+ if (policy.sessionId === void 0) return () => {};
759
+ const session = this.ctx.get("sessions")?.get(policy.sessionId);
760
+ const process = {
761
+ sessionId: policy.sessionId,
762
+ mode: session === void 0 ? policy.mode : this.ctx.sandboxPolicy.resolve({ session }).mode,
763
+ release,
764
+ invalidated: false
765
+ };
766
+ this.processes.add(process);
767
+ return () => {
768
+ this.processes.delete(process);
769
+ };
770
+ }
771
+ /**
772
+ * Wait for invalidated processes before admitting another program.
773
+ * @param sessionId - Session carried by the resolved policy.
774
+ * @returns Completion after prior cleanup; a failure rejects and remains retryable.
775
+ */
776
+ async wait(sessionId) {
777
+ await this.settle([...this.processes].filter((process) => process.sessionId === sessionId && process.invalidated));
778
+ }
779
+ release(process) {
780
+ if (process.cleanup !== void 0) return process.cleanup;
781
+ process.cleanup = process.release().then(() => {
782
+ this.processes.delete(process);
783
+ }, (error) => {
784
+ delete process.cleanup;
785
+ throw error;
786
+ });
787
+ return process.cleanup;
788
+ }
789
+ async settle(processes) {
790
+ const failed = (await Promise.allSettled(processes.map((process) => this.release(process)))).find((result) => result.status === "rejected");
791
+ if (failed?.status === "rejected") throw failed.reason;
792
+ }
793
+ };
794
+ //#endregion
795
+ //#region lib/types/environment.js
796
+ /** Startup variables required by native executables before model evaluation. */
797
+ /** Native executable search, Windows system paths, and sandbox temporary paths retained in the OS environment. */
798
+ const STARTUP_ENVIRONMENT_NAMES = new Set([
799
+ "PATH",
800
+ "PATHEXT",
801
+ "SYSTEMROOT",
802
+ "WINDIR",
803
+ "TEMP",
804
+ "TMP"
805
+ ]);
806
+ //#endregion
368
807
  //#region lib/types/output-ledger.js
369
808
  /** One run's combined outer-output ledger; binding values never enter it. */
370
809
  var OutputLedger = class {
371
810
  maxBytes;
811
+ overflowMessage;
372
812
  bytes = 2;
373
813
  entries = 0;
374
- constructor(maxBytes) {
814
+ constructor(maxBytes, overflowMessage) {
375
815
  this.maxBytes = maxBytes;
816
+ this.overflowMessage = overflowMessage;
376
817
  }
377
818
  /** Admit one exact log entry, or report that the hard cap was crossed. */
378
819
  admit(text, sink) {
@@ -402,7 +843,7 @@ var OutputLedger = class {
402
843
  }
403
844
  /** Build the explicit output-limit failure while retaining a fitting prefix of the final log. */
404
845
  limit(logs) {
405
- const fullMessage = `outer output exceeded ${this.maxBytes} bytes`;
846
+ const fullMessage = this.overflowMessage ?? `outer output exceeded ${this.maxBytes} bytes`;
406
847
  const messageBytes = fullMessage.length + 2;
407
848
  const retained = [];
408
849
  let retainedBytes = 2;
@@ -415,343 +856,545 @@ var OutputLedger = class {
415
856
  retained.push(text);
416
857
  retainedBytes += stringBytes + separatorBytes;
417
858
  continue;
418
- }
419
- const prefix = truncateJsonStringBytes(text, availableBytes);
420
- if (prefix.length > 0) {
421
- const prefixBytes = jsonStringBytesUpTo(prefix, availableBytes);
422
- /* v8 ignore next -- truncateJsonStringBytes guarantees its returned prefix fits the same budget. */
423
- if (prefixBytes === void 0) throw new Error("output ledger produced an oversized log prefix");
424
- retained.push(prefix);
425
- retainedBytes += prefixBytes + separatorBytes;
426
- }
427
- break;
428
- }
429
- return {
430
- logs: retained,
431
- error: {
432
- kind: "output-limit",
433
- message: truncateJsonStringBytes(fullMessage, this.maxBytes - retainedBytes)
434
- }
435
- };
436
- }
437
- };
438
- //#endregion
439
- //#region lib/types/output-stream.js
440
- /**
441
- * Wait for queued bytes without letting an inherited descriptor retain a run forever.
442
- * @param stream - Caller-owned raw process output, when provided.
443
- * @param graceMs - Maximum wait after managed process termination.
444
- * @returns Whether the complete stream ended without a transport error.
445
- */
446
- function drainOutput(stream, graceMs) {
447
- if (stream === void 0 || stream.readableEnded) return Promise.resolve(true);
448
- if (stream.destroyed) return Promise.resolve(false);
449
- return new Promise((resolve) => {
450
- const finish = (complete) => {
451
- clearTimeout(timer);
452
- stream.off("end", onEnd);
453
- stream.off("close", onClose);
454
- stream.off("error", onError);
455
- resolve(complete);
456
- };
457
- const onEnd = () => {
458
- finish(true);
459
- };
460
- const onClose = () => {
461
- finish(false);
462
- };
463
- const onError = () => {
464
- finish(false);
465
- };
466
- const timer = setTimeout(() => {
467
- finish(false);
468
- }, graceMs);
469
- stream.once("end", onEnd);
470
- stream.once("close", onClose);
471
- stream.once("error", onError);
472
- });
473
- }
474
- //#endregion
475
- //#region lib/types/environment.js
476
- /** Startup variables required by native executables before model evaluation. */
477
- /** Native executable search, Windows system paths, and sandbox temporary paths retained in the OS environment. */
478
- const STARTUP_ENVIRONMENT_NAMES = new Set([
479
- "PATH",
480
- "PATHEXT",
481
- "SYSTEMROOT",
482
- "WINDIR",
483
- "TEMP",
484
- "TMP"
485
- ]);
486
- //#endregion
487
- //#region lib/types/json-wire.js
488
- /**
489
- * Lossless-JSON snapshots for the dependency-free source bootstrap closure.
490
- * @module @deepseek-ai/dsh-ptc-runtime-node/json-wire
491
- */
492
- const intrinsicFunctionToString = Reflect.get(Function.prototype, "toString");
493
- const intrinsicReflectApply = Reflect.get(Reflect, "apply");
494
- const IntrinsicError = Error;
495
- const IntrinsicSet = Set;
496
- const intrinsicArrayIsArray = Array.isArray;
497
- const intrinsicArrayPrototype = Array.prototype;
498
- const intrinsicNumberIsFinite = Number.isFinite;
499
- const intrinsicNumberIsSafeInteger = Number.isSafeInteger;
500
- const intrinsicObjectCreate = Object.create;
501
- const intrinsicObjectDefineProperty = Object.defineProperty;
502
- const intrinsicObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
503
- const intrinsicObjectGetPrototypeOf = Object.getPrototypeOf;
504
- const intrinsicObjectHasOwn = Object.hasOwn;
505
- const intrinsicObjectIs = Object.is;
506
- const intrinsicObjectKeys = Object.keys;
507
- const intrinsicObjectPrototype = Object.prototype;
508
- const intrinsicObjectPropertyIsEnumerable = Reflect.get(intrinsicObjectPrototype, "propertyIsEnumerable");
509
- const intrinsicReflectOwnKeys = Reflect.ownKeys;
510
- const intrinsicSetAdd = Reflect.get(Set.prototype, "add");
511
- Reflect.get(Set.prototype, "delete");
512
- const intrinsicSetHas = Reflect.get(Set.prototype, "has");
513
- /** Build a data descriptor that cannot inherit model-defined accessor fields. */
514
- function dataDescriptor(value) {
515
- const descriptor = intrinsicObjectCreate(null);
516
- descriptor.value = value;
517
- return descriptor;
518
- }
519
- /** Define an ordinary enumerable data slot without a prototype-bearing descriptor. */
520
- function defineEnumerableDataProperty(target, key, value) {
521
- const descriptor = dataDescriptor(value);
522
- descriptor.enumerable = true;
523
- descriptor.configurable = true;
524
- descriptor.writable = true;
525
- intrinsicObjectDefineProperty(target, key, descriptor);
526
- }
527
- /** Append without consulting a model-mutated `Array.prototype`. */
528
- function append(target, value) {
529
- defineEnumerableDataProperty(target, target.length, value);
530
- }
531
- /** Pop without consulting a model-mutated `Array.prototype`. */
532
- function takeLast(target) {
533
- if (target.length === 0) return void 0;
534
- const index = target.length - 1;
535
- const value = target[index];
536
- intrinsicObjectDefineProperty(target, "length", dataDescriptor(index));
537
- return value;
538
- }
539
- /** Whether one captured-intrinsic Set contains a value. */
540
- function setHas(target, value) {
541
- return intrinsicReflectApply(intrinsicSetHas, target, [value]);
542
- }
543
- /** Add to one captured-intrinsic Set. */
544
- function setAdd(target, value) {
545
- intrinsicReflectApply(intrinsicSetAdd, target, [value]);
546
- }
547
- /** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
548
- function hasIntrinsicConstructor(prototype, name) {
549
- const constructor = intrinsicObjectGetOwnPropertyDescriptor(prototype, "constructor")?.value;
550
- if (typeof constructor !== "function") return false;
551
- try {
552
- return constructor.name === name && constructor.prototype === prototype && intrinsicReflectApply(intrinsicFunctionToString, constructor, []) === `function ${name}() { [native code] }`;
553
- } catch {
554
- return false;
555
- }
556
- }
557
- /** Whether a candidate is a foreign realm's intrinsic `Object.prototype`. */
558
- function isForeignIntrinsicObjectPrototype(value) {
559
- return intrinsicObjectGetPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
560
- }
561
- /** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
562
- function hasPlainArrayPrototype(value) {
563
- const prototype = intrinsicObjectGetPrototypeOf(value);
564
- if (prototype === intrinsicArrayPrototype) return true;
565
- if (!intrinsicArrayIsArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
566
- const objectPrototype = intrinsicObjectGetPrototypeOf(prototype);
567
- return typeof objectPrototype === "object" && objectPrototype !== null && isForeignIntrinsicObjectPrototype(objectPrototype);
568
- }
569
- /** Whether an object is a plain or null-prototype record from any JavaScript realm. */
570
- function hasPlainObjectPrototype(value) {
571
- const prototype = intrinsicObjectGetPrototypeOf(value);
572
- return prototype === null || prototype === intrinsicObjectPrototype || typeof prototype === "object" && isForeignIntrinsicObjectPrototype(prototype);
573
- }
574
- /** Return every JSON-visible object key, or reject own data JSON would discard. */
575
- function enumerableStringKeys(value) {
576
- const keys = intrinsicReflectOwnKeys(value);
577
- for (let index = 0; index < keys.length; index++) {
578
- const key = keys[index];
579
- if (typeof key !== "string" || !intrinsicReflectApply(intrinsicObjectPropertyIsEnumerable, value, [key])) return void 0;
580
- }
581
- return keys;
582
- }
583
- /**
584
- * Flatten one validated JSON value for the process control channel.
585
- * @param value - the lossless JSON value to transport.
586
- * @returns a pre-order token stream whose own nesting is bounded.
587
- */
588
- function encodePtcJsonWire(value) {
589
- const wire = [];
590
- const pending = [value];
591
- for (let current = takeLast(pending); current !== void 0; current = takeLast(pending)) {
592
- if (current === null || typeof current === "boolean" || typeof current === "number" || typeof current === "string") {
593
- append(wire, current);
594
- continue;
595
- }
596
- if (intrinsicArrayIsArray(current)) {
597
- append(wire, {
598
- kind: "array",
599
- length: current.length
600
- });
601
- for (let index = current.length - 1; index >= 0; index--) {
602
- const item = current[index];
603
- if (item === void 0) throw new IntrinsicError("cannot encode a sparse JSON array");
604
- append(pending, item);
605
- }
606
- continue;
607
- }
608
- const keys = intrinsicObjectKeys(current);
609
- append(wire, {
610
- kind: "object",
611
- keys
612
- });
613
- for (let index = keys.length - 1; index >= 0; index--) {
614
- const key = keys[index];
615
- /* v8 ignore next -- the loop is bounded by the captured key count. */
616
- if (key === void 0) throw new IntrinsicError("cannot encode a missing JSON object key");
617
- const item = current[key];
618
- if (item === void 0) throw new IntrinsicError("cannot encode an undefined JSON object property");
619
- append(pending, item);
620
- }
621
- }
622
- return wire;
623
- }
624
- /** Whether an array contains exactly its dense indexed slots and `length`. */
625
- function isDenseArray(value) {
626
- if (!hasPlainArrayPrototype(value) || intrinsicReflectOwnKeys(value).length !== value.length + 1) return false;
627
- for (let index = 0; index < value.length; index++) if (!intrinsicObjectHasOwn(value, index)) return false;
628
- return true;
629
- }
630
- /** Whether one exact string-key list contains a key, without consulting its prototype. */
631
- function keysContain(keys, expected) {
632
- for (let index = 0; index < keys.length; index++) if (keys[index] === expected) return true;
633
- return false;
634
- }
635
- /** Return one exact container marker, or reject any extra/missing fields. */
636
- function containerToken(value) {
637
- if (intrinsicArrayIsArray(value) || !hasPlainObjectPrototype(value)) return void 0;
638
- const keys = enumerableStringKeys(value);
639
- if (keys === void 0) return void 0;
640
- const token = value;
641
- if (token.kind === "array") {
642
- if (keys.length !== 2 || !keysContain(keys, "kind") || !keysContain(keys, "length")) return void 0;
643
- const length = token.length;
644
- return typeof length === "number" && intrinsicNumberIsSafeInteger(length) && length >= 0 ? {
645
- kind: "array",
646
- length
647
- } : void 0;
648
- }
649
- if (token.kind === "object") {
650
- if (keys.length !== 2 || !keysContain(keys, "kind") || !keysContain(keys, "keys")) return void 0;
651
- const objectKeys = token.keys;
652
- if (!intrinsicArrayIsArray(objectKeys) || !isDenseArray(objectKeys)) return void 0;
653
- const unique = new IntrinsicSet();
654
- const normalizedKeys = [];
655
- const objectKeyValues = objectKeys;
656
- for (let index = 0; index < objectKeyValues.length; index++) {
657
- const key = objectKeyValues[index];
658
- if (typeof key !== "string" || setHas(unique, key)) return void 0;
659
- setAdd(unique, key);
660
- append(normalizedKeys, key);
859
+ }
860
+ const prefix = truncateJsonStringBytes(text, availableBytes);
861
+ if (prefix.length > 0) {
862
+ const prefixBytes = jsonStringBytesUpTo(prefix, availableBytes);
863
+ /* v8 ignore next -- truncateJsonStringBytes guarantees its returned prefix fits the same budget. */
864
+ if (prefixBytes === void 0) throw new Error("output ledger produced an oversized log prefix");
865
+ retained.push(prefix);
866
+ retainedBytes += prefixBytes + separatorBytes;
867
+ }
868
+ break;
661
869
  }
662
870
  return {
663
- kind: "object",
664
- keys: normalizedKeys
871
+ logs: retained,
872
+ error: {
873
+ kind: "output-limit",
874
+ message: truncateJsonStringBytes(fullMessage, this.maxBytes - retainedBytes)
875
+ }
665
876
  };
666
877
  }
878
+ };
879
+ //#endregion
880
+ //#region lib/types/output-stream.js
881
+ /**
882
+ * Wait for queued bytes without letting an inherited descriptor retain a run forever.
883
+ * @param stream - Caller-owned raw process output, when provided.
884
+ * @param graceMs - Maximum wait after managed process termination.
885
+ * @returns Whether the complete stream ended without a transport error.
886
+ */
887
+ function drainOutput(stream, graceMs) {
888
+ if (stream === void 0 || stream.readableEnded) return Promise.resolve(true);
889
+ if (stream.destroyed) return Promise.resolve(false);
890
+ return new Promise((resolve) => {
891
+ const finish = (complete) => {
892
+ clearTimeout(timer);
893
+ stream.off("end", onEnd);
894
+ stream.off("close", onClose);
895
+ stream.off("error", onError);
896
+ resolve(complete);
897
+ };
898
+ const onEnd = () => {
899
+ finish(true);
900
+ };
901
+ const onClose = () => {
902
+ finish(false);
903
+ };
904
+ const onError = () => {
905
+ finish(false);
906
+ };
907
+ const timer = setTimeout(() => {
908
+ finish(false);
909
+ }, graceMs);
910
+ stream.once("end", onEnd);
911
+ stream.once("close", onClose);
912
+ stream.once("error", onError);
913
+ });
667
914
  }
668
915
  /**
669
- * Rebuild one lossless JSON value from the flat process wire format.
670
- * Malformed or incomplete traffic returns `undefined`; traversal is iterative
671
- * and therefore independent of the transported value's application depth.
672
- * @param input - untrusted message-port payload.
673
- * @returns the detached JSON value, or `undefined` when the wire is invalid.
916
+ * Terminate a managed process and drain its pipes, including failures before output listeners attach.
917
+ * @param handle - Process and streams owned by the runtime.
918
+ * @param graceMs - Maximum pipe-drain wait after managed exit.
919
+ * @returns Whether both output streams ended cleanly.
674
920
  */
675
- function decodePtcJsonWire(input) {
921
+ async function terminateProcess(handle, graceMs) {
676
922
  try {
677
- if (!intrinsicArrayIsArray(input) || !isDenseArray(input) || input.length === 0) return void 0;
678
- const wire = input;
679
- const frames = [];
680
- let root;
681
- let rootAssigned = false;
682
- const attach = (value) => {
683
- const parent = frames[frames.length - 1];
684
- if (!parent) {
685
- if (rootAssigned) return false;
686
- root = value;
687
- rootAssigned = true;
688
- return true;
923
+ handle.terminate();
924
+ handle.stdout?.resume();
925
+ handle.stderr?.resume();
926
+ await Promise.all([handle.done.catch(() => {}), handle.waitForExit()]);
927
+ return !(await Promise.all([drainOutput(handle.stdout, graceMs), drainOutput(handle.stderr, graceMs)])).includes(false);
928
+ } finally {
929
+ handle.stdout?.destroy();
930
+ handle.stderr?.destroy();
931
+ }
932
+ }
933
+ //#endregion
934
+ //#region lib/types/persistent.js
935
+ /** Node PTC in-memory execution with retained processes scoped to caller-owned objects. */
936
+ const messageOf$1 = (error) => error instanceof Error ? error.message : String(error);
937
+ const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
938
+ function persistentOutput(maxBytes) {
939
+ return new OutputLedger(maxBytes, `retained variables were cleared; outer output exceeded ${maxBytes} bytes`);
940
+ }
941
+ /** One managed process; all mutable program state belongs to this process alone. */
942
+ var Kernel = class {
943
+ ctx;
944
+ config;
945
+ policy;
946
+ unregister;
947
+ confined;
948
+ handle;
949
+ channel;
950
+ active;
951
+ /** Admission remains reserved until the result includes completed process cleanup. */
952
+ running = false;
953
+ cleanupPending = false;
954
+ ready = false;
955
+ sequence = 0;
956
+ nextCall = 1;
957
+ stderr = "";
958
+ stopped;
959
+ starting;
960
+ exitOutcome;
961
+ controller = new AbortController();
962
+ closed = false;
963
+ constructor(ctx, config, policy, processes) {
964
+ this.ctx = ctx;
965
+ this.config = config;
966
+ this.policy = policy;
967
+ this.unregister = processes.register(policy, () => this.dispose());
968
+ }
969
+ assertIdle() {
970
+ if (this.running) throw new Error("persistent PTC does not accept concurrent runs for one state owner");
971
+ if (this.cleanupPending) throw new Error("persistent PTC environment cleanup is incomplete; release the owner before running another program");
972
+ }
973
+ async run(spec, bindings, code) {
974
+ this.running = true;
975
+ const sandbox = {
976
+ mode: this.policy.mode,
977
+ denied: false
978
+ };
979
+ try {
980
+ const result = await this.evaluate(spec, bindings, code);
981
+ if (this.confined !== void 0) {
982
+ sandbox.enforcement = this.confined.enforcement;
983
+ sandbox.denied = result.error?.kind !== "sandbox-unavailable" && this.confined.denialSignatures.some((signature) => result.error?.message.toLowerCase().includes(signature.toLowerCase()));
689
984
  }
690
- /* v8 ignore next -- completed frames are popped before another token can attach. */
691
- if (parent.index >= (parent.kind === "array" ? parent.length : parent.keys.length)) return false;
692
- if (parent.kind === "array") append(parent.target, value);
693
- else {
694
- const key = parent.keys[parent.index];
695
- /* v8 ignore next -- object frames are built from validated keys and their exact length. */
696
- if (key === void 0) return false;
697
- defineEnumerableDataProperty(parent.target, key, value);
985
+ return {
986
+ ...result,
987
+ sandbox
988
+ };
989
+ } finally {
990
+ this.running = false;
991
+ }
992
+ }
993
+ async evaluate(spec, bindings, code) {
994
+ if (this.exitOutcome !== void 0) {
995
+ const output = persistentOutput(this.config.maxOutputBytes);
996
+ const failure = spec.signal?.aborted ? {
997
+ kind: "abort",
998
+ message: "execution canceled; retained variables were cleared"
999
+ } : {
1000
+ kind: "worker-exit",
1001
+ message: `${this.exitMessage(this.exitOutcome)}; retained variables were cleared`
1002
+ };
1003
+ try {
1004
+ await this.stop();
1005
+ } catch (error) {
1006
+ return output.failure([], {
1007
+ kind: "worker-exit",
1008
+ message: `managed process cleanup failed: ${messageOf$1(error)}`
1009
+ });
698
1010
  }
699
- parent.index += 1;
700
- return true;
1011
+ return output.failure([], failure);
1012
+ }
1013
+ this.stderr = "";
1014
+ const output = persistentOutput(this.config.maxOutputBytes);
1015
+ const logs = [];
1016
+ const result = Promise.withResolvers();
1017
+ const timer = spec.timeoutMs === null ? void 0 : setTimeout(() => {
1018
+ evaluation.finish({
1019
+ kind: "timeout",
1020
+ message: `execution deadline reached (${spec.timeoutMs}ms); retained variables were cleared`
1021
+ }, void 0, true);
1022
+ }, spec.timeoutMs);
1023
+ const onAbort = () => {
1024
+ evaluation.finish({
1025
+ kind: "abort",
1026
+ message: "execution canceled; retained variables were cleared"
1027
+ }, void 0, true);
701
1028
  };
702
- for (let tokenIndex = 0; tokenIndex < wire.length; tokenIndex++) {
703
- const token = wire[tokenIndex];
704
- let value;
705
- let frame;
706
- if (token === null || typeof token === "boolean" || typeof token === "string") value = token;
707
- else if (typeof token === "number") {
708
- if (!intrinsicNumberIsFinite(token) || intrinsicObjectIs(token, -0)) return void 0;
709
- value = token;
710
- } else {
711
- if (typeof token !== "object") return void 0;
712
- const marker = containerToken(token);
713
- if (!marker) return void 0;
714
- const remainingTokens = wire.length - tokenIndex - 1;
715
- if (marker.kind === "array") {
716
- if (marker.length > remainingTokens) return void 0;
717
- const target = [];
718
- value = target;
719
- if (marker.length > 0) frame = {
720
- kind: "array",
721
- target,
722
- length: marker.length,
723
- index: 0
1029
+ const evaluation = {
1030
+ id: ++this.sequence,
1031
+ data: {
1032
+ code,
1033
+ maxOutputBytes: this.config.maxOutputBytes,
1034
+ namespaces: [...bindings.values()].map((binding) => ({
1035
+ global: binding.global,
1036
+ names: Object.keys(binding.functions),
1037
+ ...binding.errorClass ? { errorClass: binding.errorClass } : {}
1038
+ }))
1039
+ },
1040
+ spec,
1041
+ bindings,
1042
+ reply: AsyncLocalStorage.bind(replyToBinding),
1043
+ pending: 0,
1044
+ pendingBytes: 0,
1045
+ admit: (text) => {
1046
+ if (!output.admit(text, logs)) {
1047
+ logs.push(text);
1048
+ evaluation.finish({
1049
+ kind: "output-limit",
1050
+ message: "output limit exceeded"
1051
+ }, void 0, true);
1052
+ }
1053
+ },
1054
+ finish: (error, value, discard = false) => {
1055
+ if (this.active !== evaluation) return;
1056
+ clearTimeout(timer);
1057
+ spec.signal?.removeEventListener("abort", onAbort);
1058
+ this.active = void 0;
1059
+ const outcome = error?.kind === "output-limit" ? output.limit(logs) : error ? output.failure(logs, error) : output.success(logs, value);
1060
+ (discard || outcome.error?.kind === "output-limit" || spec.stateOwner === void 0 ? this.stop() : Promise.resolve(true)).then((drained) => {
1061
+ result.resolve(drained ? outcome : output.failure(logs, {
1062
+ kind: "worker-exit",
1063
+ message: "managed process exited but its output did not drain cleanly"
1064
+ }));
1065
+ }, (failure) => {
1066
+ result.resolve(output.failure(logs, {
1067
+ kind: "worker-exit",
1068
+ message: `managed process cleanup failed: ${messageOf$1(failure)}`
1069
+ }));
1070
+ });
1071
+ }
1072
+ };
1073
+ this.active = evaluation;
1074
+ spec.signal?.addEventListener("abort", onAbort, { once: true });
1075
+ if (spec.signal?.aborted) onAbort();
1076
+ else if (this.ready) this.dispatch(evaluation);
1077
+ else {
1078
+ this.starting = this.start(spec.cwd);
1079
+ this.starting.catch((error) => {
1080
+ this.fail(error instanceof SandboxUnavailableError || this.confined !== void 0 && isRunnerSpawnFailure(error, this.confined.argv[0], spec.cwd) ? "sandbox-unavailable" : "worker-exit", messageOf$1(error));
1081
+ });
1082
+ }
1083
+ return await result.promise;
1084
+ }
1085
+ async dispose() {
1086
+ this.active?.finish({
1087
+ kind: "abort",
1088
+ message: "PTC environment disposed; retained variables were cleared"
1089
+ }, void 0, true);
1090
+ await this.stop();
1091
+ }
1092
+ stop() {
1093
+ if (this.stopped) return this.stopped;
1094
+ this.closed = true;
1095
+ this.cleanupPending = true;
1096
+ this.controller.abort();
1097
+ this.channel?.close();
1098
+ this.stopped = (async () => {
1099
+ await this.starting?.catch(() => {});
1100
+ const handle = this.handle;
1101
+ return handle === void 0 || await terminateProcess(handle, this.config.graceMs);
1102
+ })().then((drained) => {
1103
+ this.unregister?.();
1104
+ this.cleanupPending = false;
1105
+ if (!drained) this.ctx.logger.warn("persistent Node process exited but its output did not drain cleanly");
1106
+ return drained;
1107
+ }, (error) => {
1108
+ this.stopped = void 0;
1109
+ throw error;
1110
+ });
1111
+ return this.stopped;
1112
+ }
1113
+ fail(kind, message) {
1114
+ if (this.active) this.active.finish({
1115
+ kind,
1116
+ message: `${message}; retained variables were cleared`
1117
+ }, void 0, true);
1118
+ else {
1119
+ if (!this.closed) this.ctx.logger.warn(message);
1120
+ this.stop().catch((error) => {
1121
+ this.ctx.logger.warn(messageOf$1(error));
1122
+ });
1123
+ }
1124
+ }
1125
+ dispatch(evaluation) {
1126
+ this.channel?.send({
1127
+ type: "run",
1128
+ id: evaluation.id,
1129
+ cwd: evaluation.spec.cwd,
1130
+ data: evaluation.data
1131
+ }).catch((error) => {
1132
+ this.fail("protocol", messageOf$1(error));
1133
+ });
1134
+ }
1135
+ exitMessage(outcome) {
1136
+ return `Node process exited (${String(outcome.exitCode)}, signal ${String(outcome.signal)})${this.stderr ? `: ${this.stderr}` : ""}`;
1137
+ }
1138
+ async start(cwd) {
1139
+ const executable = await this.ctx.subprocess.resolveExecutable(this.config.launch.executable, void 0, this.controller.signal);
1140
+ if (this.closed) return;
1141
+ const argv = [
1142
+ executable,
1143
+ `--max-old-space-size=${this.config.maxOldGenerationSizeMb}`,
1144
+ ...bootstrapArgs(this.ctx.fs, this.config.launch, this.config.maxMessageBytes, true)
1145
+ ];
1146
+ const policy = this.policy;
1147
+ this.confined = policy.mode === "danger-full-access" ? void 0 : await this.ctx.sandbox.confine(argv, {
1148
+ ...policy,
1149
+ mode: policy.mode
1150
+ }, this.controller.signal);
1151
+ if (this.closed) return;
1152
+ const env = Object.fromEntries(Object.keys(process.env).filter((key) => !STARTUP_ENVIRONMENT_NAMES.has(key.toUpperCase()) && key.toUpperCase() !== "ELECTRON_RUN_AS_NODE").map((key) => [key, void 0]));
1153
+ const handle = this.ctx.subprocess.spawn({
1154
+ argv: this.confined?.argv ?? argv,
1155
+ cwd,
1156
+ env,
1157
+ stdio: {
1158
+ stdin: "ignore",
1159
+ stdout: "pipe",
1160
+ stderr: "pipe",
1161
+ control: "pipe"
1162
+ },
1163
+ graceMs: this.config.graceMs
1164
+ });
1165
+ this.handle = handle;
1166
+ if (!handle.control || !handle.stdout || !handle.stderr) throw new Error("subprocess provider did not supply persistent PTC pipes");
1167
+ for (const stream of [handle.stdout, handle.stderr]) {
1168
+ stream.setEncoding("utf8");
1169
+ stream.on("data", (text) => {
1170
+ if (stream === handle.stderr) this.stderr = (this.stderr + text).slice(-this.config.maxOutputBytes);
1171
+ this.active?.admit(text);
1172
+ });
1173
+ stream.on("error", (error) => {
1174
+ this.fail("worker-exit", error.message);
1175
+ });
1176
+ }
1177
+ this.channel = new JsonChannel(handle.control, this.config.maxMessageBytes, (raw, bytes) => {
1178
+ this.receive(raw, bytes);
1179
+ }, (error, kind) => {
1180
+ if (kind === "protocol") this.fail("protocol", error.message);
1181
+ else setImmediate(() => {
1182
+ if (!this.closed && this.exitOutcome === void 0) handle.terminate();
1183
+ });
1184
+ });
1185
+ handle.done.then(async (outcome) => {
1186
+ this.exitOutcome = outcome;
1187
+ await Promise.all([drainOutput(handle.stdout, this.config.graceMs), drainOutput(handle.stderr, this.config.graceMs)]);
1188
+ setImmediate(() => {
1189
+ if (!this.closed) this.fail(this.confined !== void 0 && classifyRunnerFailure(outcome.exitCode, this.stderr, this.confined.runnerFailureRules) !== void 0 ? "sandbox-unavailable" : "worker-exit", this.exitMessage(outcome));
1190
+ });
1191
+ }, (error) => {
1192
+ this.fail("worker-exit", messageOf$1(error));
1193
+ });
1194
+ }
1195
+ receive(raw, bytes) {
1196
+ if (!record$1(raw)) {
1197
+ this.fail("protocol", "invalid persistent PTC frame");
1198
+ return;
1199
+ }
1200
+ const evaluation = this.active;
1201
+ if (!evaluation) {
1202
+ this.fail("protocol", "frame arrived without an active PTC evaluation");
1203
+ return;
1204
+ }
1205
+ if (!this.ready) {
1206
+ if (raw.type !== "ready") {
1207
+ this.fail("protocol", "expected persistent PTC readiness");
1208
+ return;
1209
+ }
1210
+ this.ready = true;
1211
+ this.dispatch(evaluation);
1212
+ return;
1213
+ }
1214
+ if (raw.id !== evaluation.id || !record$1(raw.payload)) {
1215
+ this.fail("protocol", "frame does not belong to the active PTC evaluation");
1216
+ return;
1217
+ }
1218
+ const payload = raw.payload;
1219
+ switch (payload.type) {
1220
+ case "log":
1221
+ if (typeof payload.text === "string") evaluation.admit(payload.text);
1222
+ else this.fail("protocol", "invalid persistent PTC log");
1223
+ return;
1224
+ case "output-limit":
1225
+ evaluation.finish({
1226
+ kind: "output-limit",
1227
+ message: "output limit exceeded"
1228
+ }, void 0, true);
1229
+ return;
1230
+ case "done":
1231
+ if (payload.error !== void 0) {
1232
+ const error = payload.error;
1233
+ if (!record$1(error) || typeof error.message !== "string" || error.kind !== "exception" && error.kind !== "invalid-output" && error.kind !== "output-limit") {
1234
+ this.fail("protocol", "invalid persistent PTC failure");
1235
+ return;
1236
+ }
1237
+ const failure = {
1238
+ kind: error.kind,
1239
+ message: error.message
724
1240
  };
1241
+ setImmediate(() => {
1242
+ evaluation.finish(failure, void 0, failure.kind === "output-limit");
1243
+ });
725
1244
  } else {
726
- if (marker.keys.length > remainingTokens) return void 0;
727
- const target = {};
728
- value = target;
729
- if (marker.keys.length > 0) frame = {
730
- kind: "object",
731
- target,
732
- keys: marker.keys,
733
- index: 0
734
- };
1245
+ const value = payload.value === void 0 ? void 0 : decodePtcJsonWire(payload.value);
1246
+ const failure = payload.value !== void 0 && value === void 0 ? {
1247
+ kind: "invalid-output",
1248
+ message: "program completion must be lossless JSON"
1249
+ } : void 0;
1250
+ setImmediate(() => {
1251
+ evaluation.finish(failure, value);
1252
+ });
735
1253
  }
1254
+ return;
1255
+ case "call": {
1256
+ if (payload.id !== this.nextCall || typeof payload.global !== "string" || typeof payload.name !== "string") {
1257
+ this.fail("protocol", "invalid persistent PTC binding identity");
1258
+ return;
1259
+ }
1260
+ this.nextCall += 1;
1261
+ const args = decodePtcJsonWire(payload.args);
1262
+ if (args === void 0) {
1263
+ this.fail("protocol", "binding arguments must be lossless JSON");
1264
+ return;
1265
+ }
1266
+ if (++evaluation.pending > this.config.maxPendingCalls || (evaluation.pendingBytes += bytes) > this.config.maxMessageBytes) {
1267
+ this.fail("protocol", "pending binding calls exceed configured limits");
1268
+ return;
1269
+ }
1270
+ const functions = evaluation.bindings.get(payload.global)?.functions;
1271
+ const fn = functions && Object.hasOwn(functions, payload.name) ? functions[payload.name] : void 0;
1272
+ const id = payload.id;
1273
+ (async () => {
1274
+ const reply = await evaluation.reply(id, fn, args).finally(() => {
1275
+ evaluation.pending -= 1;
1276
+ evaluation.pendingBytes -= bytes;
1277
+ });
1278
+ if (this.active === evaluation) await this.channel?.send(reply);
1279
+ })().catch((error) => {
1280
+ this.fail("protocol", messageOf$1(error));
1281
+ });
1282
+ return;
736
1283
  }
737
- if (!attach(value)) return void 0;
738
- if (frame) append(frames, frame);
739
- while (frames.length > 0) {
740
- const current = frames[frames.length - 1];
741
- /* v8 ignore next -- the loop condition guarantees a final frame. */
742
- if (current === void 0) break;
743
- if (current.index < (current.kind === "array" ? current.length : current.keys.length)) break;
744
- takeLast(frames);
1284
+ default: this.fail("protocol", "unknown persistent PTC message");
1285
+ }
1286
+ }
1287
+ };
1288
+ /** Sandboxed, best-effort retained state; restart, disposal, and interrupted execution clear it. */
1289
+ var InMemoryExecution = class {
1290
+ ctx;
1291
+ config;
1292
+ processes;
1293
+ /**
1294
+ * Model-facing rules for retained variables, authority, output, and recovery.
1295
+ * @returns Usage guidance appended to the run_code schema.
1296
+ */
1297
+ get executionInstructions() {
1298
+ return "The program contains TypeScript REPL statements; only erasable syntax is accepted, with no enum or namespaces. Type annotations are advisory. Top-level declarations and await are supported; top-level return is not supported. Emit output with console.log(...) or a final lossless JSON expression. Only printed values and that final expression become program output. Variables, functions, and imported modules remain in memory for this Agent across calls. Use await import(...) for modules. Direct filesystem access follows this execution's sandbox policy; process.env starts empty. Changing the session sandbox mode clears retained variables. An approved sandbox escalation runs in a separate temporary environment: it cannot access retained variables, and its declarations are discarded afterward. Restart, Agent disposal, cancellation, timeout, output overflow, or process failure clears the environment. If a variable is undefined, recreate it and reacquire its resources. State is not restored from the conversation, and failed evaluations can leave earlier mutations in memory. Redeclaring an existing let, const, or class name fails; assign to a mutable binding or use a new name. Await all tool calls; background callbacks cannot call tools after their evaluation ends. Await child-process completion before ending an evaluation; raw subprocess output belongs to whichever evaluation is active when it arrives.";
1299
+ }
1300
+ owners = /* @__PURE__ */ new WeakMap();
1301
+ kernels = /* @__PURE__ */ new Set();
1302
+ disposed = false;
1303
+ /**
1304
+ * Own retained execution resources under the existing Node provider.
1305
+ * @param ctx - Provider lifecycle and execution services.
1306
+ * @param config - Validated limits and selected executable.
1307
+ * @param processes - Provider-owned policy invalidation and cleanup.
1308
+ */
1309
+ constructor(ctx, config, processes) {
1310
+ this.ctx = ctx;
1311
+ this.config = config;
1312
+ this.processes = processes;
1313
+ if (this.config.launch.kind === "embedded") throw new Error("Persistent PTC requires a node-script launch");
1314
+ ctx.effect(() => async () => {
1315
+ this.disposed = true;
1316
+ const results = await Promise.allSettled([...this.kernels].map((kernel) => kernel.dispose()));
1317
+ this.kernels.clear();
1318
+ const failed = results.find((result) => result.status === "rejected");
1319
+ if (failed?.status === "rejected") throw failed.reason;
1320
+ }, "Persistent PTC environments");
1321
+ }
1322
+ /**
1323
+ * Resolve the file policy, directory and deadline for a retained REPL.
1324
+ * @param request - Program, bindings, owner, directory and elapsed budget.
1325
+ * @returns Resolved inputs with the selected file policy.
1326
+ */
1327
+ resolve(request) {
1328
+ if (this.disposed) throw new Error("persistent PTC: resolve after disposal");
1329
+ const sandboxPolicy = request.sandboxPolicy ?? this.ctx.sandboxPolicy.resolve();
1330
+ const cwd = request.cwd ?? sandboxPolicy.workspaceRoot;
1331
+ if (!isAbsolute(cwd)) throw new Error("persistent PTC: cwd must be absolute");
1332
+ return {
1333
+ ...request,
1334
+ sandboxPolicy,
1335
+ cwd,
1336
+ timeoutMs: request.timeoutMs === null ? null : clampTimeout(request.timeoutMs, this.config.timeoutMs, this.config.maxTimeoutMs, "persistent PTC timeoutMs")
1337
+ };
1338
+ }
1339
+ /**
1340
+ * Evaluate statements using this owner's process, or a temporary process without an owner.
1341
+ * @param spec - Resolved request; concurrent calls for one owner are rejected.
1342
+ * @returns Logs, a final JSON expression, or a structured execution failure.
1343
+ */
1344
+ async run(spec) {
1345
+ if (this.disposed) throw new Error("persistent PTC: run after disposal");
1346
+ if (spec.sandboxPolicy === void 0 || !isAbsolute(spec.cwd) || spec.timeoutMs !== null && (!Number.isFinite(spec.timeoutMs) || spec.timeoutMs <= 0 || spec.timeoutMs > this.config.maxTimeoutMs)) throw new Error("persistent PTC: run requires resolved inputs with a sandbox policy");
1347
+ const bindings = validateBindings(spec);
1348
+ let kernel = spec.stateOwner === void 0 ? void 0 : this.owners.get(spec.stateOwner);
1349
+ kernel?.assertIdle();
1350
+ if (kernel && !kernel.closed && (kernel.policy.mode !== spec.sandboxPolicy.mode || kernel.policy.workspaceRoot !== spec.sandboxPolicy.workspaceRoot || kernel.policy.sessionId !== spec.sandboxPolicy.sessionId)) throw new Error("persistent PTC: release the retained environment before changing its sandbox policy");
1351
+ let code;
1352
+ try {
1353
+ code = stripTypeScriptTypes(spec.program);
1354
+ } catch (error) {
1355
+ const output = persistentOutput(this.config.maxOutputBytes);
1356
+ const outcome = output.failure([], {
1357
+ kind: "exception",
1358
+ message: messageOf$1(error)
1359
+ });
1360
+ if (outcome.error?.kind === "output-limit" && spec.stateOwner !== void 0) try {
1361
+ await this.release(spec.stateOwner);
1362
+ } catch (failure) {
1363
+ return output.failure([], {
1364
+ kind: "worker-exit",
1365
+ message: `managed process cleanup failed: ${messageOf$1(failure)}`
1366
+ });
745
1367
  }
1368
+ return outcome;
1369
+ }
1370
+ if (!kernel || kernel.closed) {
1371
+ if (kernel) this.kernels.delete(kernel);
1372
+ kernel = new Kernel(this.ctx, this.config, { ...spec.sandboxPolicy }, this.processes);
1373
+ this.kernels.add(kernel);
1374
+ if (spec.stateOwner !== void 0) this.owners.set(spec.stateOwner, kernel);
1375
+ }
1376
+ try {
1377
+ return await kernel.run(spec, bindings, code);
1378
+ } finally {
1379
+ if (spec.stateOwner === void 0) this.kernels.delete(kernel);
746
1380
  }
747
- return frames.length === 0 ? root : void 0;
748
- } catch {
749
- return;
750
1381
  }
751
- }
1382
+ /**
1383
+ * Release retained state and await its process cleanup.
1384
+ * @param owner - Exact object supplied to run.
1385
+ * @returns Completion after the owned process and output streams stop.
1386
+ */
1387
+ async release(owner) {
1388
+ const kernel = this.owners.get(owner);
1389
+ if (!kernel) return;
1390
+ await kernel.dispose();
1391
+ if (this.owners.get(owner) === kernel) this.owners.delete(owner);
1392
+ this.kernels.delete(kernel);
1393
+ }
1394
+ };
752
1395
  //#endregion
753
1396
  //#region lib/types/index.js
754
- /** Confined Node programs with host-owned bindings, output limits, and managed process cleanup. */
1397
+ /** Node PTC execution with configurable per-call processes or retained in-memory REPL state. */
755
1398
  const STRIP_PREFIX = "async function __dsh_program__() {\n";
756
1399
  const STRIP_SUFFIX = "\n}";
757
1400
  function messageOf(error) {
@@ -760,7 +1403,7 @@ function messageOf(error) {
760
1403
  function record(value) {
761
1404
  return typeof value === "object" && value !== null && !Array.isArray(value);
762
1405
  }
763
- /** Node provider; direct file effects use the same sandbox service as Bash. */
1406
+ /** Node execution under file policy, with optional owner-scoped retained memory. */
764
1407
  var NodePtcRuntime = class extends PtcRuntime {
765
1408
  static inject = [
766
1409
  "fs",
@@ -768,21 +1411,17 @@ var NodePtcRuntime = class extends PtcRuntime {
768
1411
  "sandbox",
769
1412
  "sandboxPolicy"
770
1413
  ];
771
- static Config = z.object({
772
- timeoutMs: z.number().default(12e4),
773
- maxTimeoutMs: z.number().default(6e5),
774
- maxOutputBytes: z.number().default(67108864),
775
- maxOldGenerationSizeMb: z.number().default(512),
776
- maxMessageBytes: z.number().default(134217728),
777
- maxPendingCalls: z.number().default(128),
778
- graceMs: z.number().default(3e3),
779
- nodeExecutable: z.string(),
780
- bootstrapPath: z.string()
781
- });
1414
+ static Config = NodeConfig;
782
1415
  language = "typescript";
783
1416
  isolation = "process";
1417
+ inMemory;
1418
+ processes;
1419
+ get programFormat() {
1420
+ return this.inMemory ? "repl" : "function-body";
1421
+ }
784
1422
  get executionInstructions() {
785
- return "Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution's sandbox policy.";
1423
+ if (this.inMemory) return this.inMemory.executionInstructions;
1424
+ return "The program is the body of an async TypeScript function; only erasable syntax is accepted, with no enum or namespaces. Type annotations are advisory. Top-level await and return are supported. Emit output with console.log(...) or return a lossless JSON value. Only printed values and the returned value become program output. Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution's sandbox policy.";
786
1425
  }
787
1426
  config;
788
1427
  live = /* @__PURE__ */ new Set();
@@ -791,20 +1430,11 @@ var NodePtcRuntime = class extends PtcRuntime {
791
1430
  super(ctx);
792
1431
  this.config = {
793
1432
  ...config,
794
- nodeExecutable: config.nodeExecutable ?? process.execPath
1433
+ launch: resolveLaunch(config.launch)
795
1434
  };
796
- for (const [key, value] of Object.entries(this.config)) if (typeof value === "number" && (!Number.isFinite(value) || value <= 0)) throw new Error(`ptc-runtime-node: ${key} must be positive and finite`);
797
- for (const key of [
798
- "timeoutMs",
799
- "maxTimeoutMs",
800
- "graceMs"
801
- ]) if (this.config[key] > MAX_TIMER_DELAY_MS) throw new Error(`ptc-runtime-node: ${key} exceeds the supported timer range`);
802
- if (!Number.isSafeInteger(this.config.maxOutputBytes) || this.config.maxOutputBytes < 4) throw new Error("ptc-runtime-node: maxOutputBytes must be an integer of at least 4");
803
- if (!Number.isSafeInteger(this.config.maxMessageBytes) || this.config.maxMessageBytes > 4294967295) throw new Error("ptc-runtime-node: maxMessageBytes must fit an unsigned 32-bit frame length");
804
- if (!Number.isSafeInteger(this.config.maxPendingCalls)) throw new Error("ptc-runtime-node: maxPendingCalls must be an integer");
805
- if (!Number.isSafeInteger(this.config.maxOldGenerationSizeMb)) throw new Error("ptc-runtime-node: maxOldGenerationSizeMb must be an integer");
806
- if (this.config.nodeExecutable.length === 0) throw new Error("ptc-runtime-node: nodeExecutable must be non-empty");
807
- if (this.config.bootstrapPath !== void 0 && !isAbsolute(this.config.bootstrapPath)) throw new Error("ptc-runtime-node: bootstrapPath must be absolute");
1435
+ validateConfig(this.config);
1436
+ this.processes = new PolicyProcesses(ctx);
1437
+ if (this.config.executionMode === "persistent-in-memory") this.inMemory = new InMemoryExecution(ctx, this.config, this.processes);
808
1438
  ctx.effect(() => async () => {
809
1439
  this.disposed = true;
810
1440
  const active = [...this.live];
@@ -815,6 +1445,9 @@ var NodePtcRuntime = class extends PtcRuntime {
815
1445
  get sandboxMode() {
816
1446
  return this.ctx.sandboxPolicy.defaultMode;
817
1447
  }
1448
+ release(owner) {
1449
+ return this.inMemory ? this.inMemory.release(owner) : super.release(owner);
1450
+ }
818
1451
  get timeout() {
819
1452
  return {
820
1453
  defaultMs: Math.min(this.config.timeoutMs, this.config.maxTimeoutMs),
@@ -822,11 +1455,12 @@ var NodePtcRuntime = class extends PtcRuntime {
822
1455
  };
823
1456
  }
824
1457
  /**
825
- * Resolve an execution under explicit or deployment policy.
1458
+ * Resolve directory, deadline and file policy for the selected execution mode.
826
1459
  * @param request - Program, bindings, optional cwd/deadline, and resolved authority.
827
1460
  * @returns Complete execution inputs with a capped numeric budget or an explicit null deadline.
828
1461
  */
829
1462
  resolve(request) {
1463
+ if (this.inMemory) return this.inMemory.resolve(request);
830
1464
  if (this.disposed) throw new Error("ptc-runtime-node: resolve after disposal");
831
1465
  const sandboxPolicy = request.sandboxPolicy ?? this.ctx.sandboxPolicy.resolve();
832
1466
  const cwd = request.cwd ?? sandboxPolicy.workspaceRoot;
@@ -839,30 +1473,45 @@ var NodePtcRuntime = class extends PtcRuntime {
839
1473
  };
840
1474
  }
841
1475
  /**
842
- * Run a resolved program in a fresh managed and confined Node process.
843
- * @param spec - Inputs returned by resolve; missing authority is caller misuse.
844
- * @returns Output and file-confinement facts after managed cleanup.
1476
+ * Run a resolved program under the configured execution mode.
1477
+ * Session mode changes cancel admitted calls before process launch as well as active programs.
1478
+ * @param spec - Inputs returned by resolve for the configured mode.
1479
+ * @returns Logs, JSON completion or failure; retained processes remain until release or invalidation.
845
1480
  */
846
1481
  async run(spec) {
847
1482
  if (this.disposed) throw new Error("ptc-runtime-node: run after disposal");
848
- if (spec.sandboxPolicy === void 0) throw new Error("ptc-runtime-node: run requires a resolved sandbox policy");
849
- if (!isAbsolute(spec.cwd) || spec.timeoutMs !== null && (!Number.isFinite(spec.timeoutMs) || spec.timeoutMs <= 0 || spec.timeoutMs > this.config.maxTimeoutMs)) throw new Error("ptc-runtime-node: run requires resolved cwd and timeout");
850
- const bindings = validateBindings(spec);
851
1483
  const controller = new AbortController();
852
1484
  const completion = Promise.withResolvers();
1485
+ const unregister = spec.sandboxPolicy === void 0 ? void 0 : this.processes.register(spec.sandboxPolicy, async () => {
1486
+ controller.abort(/* @__PURE__ */ new Error("sandbox mode changed"));
1487
+ await completion.promise;
1488
+ });
853
1489
  const live = {
854
1490
  controller,
855
1491
  finished: completion.promise
856
1492
  };
857
1493
  this.live.add(live);
858
1494
  try {
1495
+ await this.processes.wait(spec.sandboxPolicy?.sessionId);
1496
+ if (this.inMemory) {
1497
+ const signal = spec.signal === void 0 ? controller.signal : AbortSignal.any([spec.signal, controller.signal]);
1498
+ return await this.inMemory.run({
1499
+ ...spec,
1500
+ signal
1501
+ });
1502
+ }
1503
+ if (spec.sandboxPolicy === void 0) throw new Error("ptc-runtime-node: run requires a resolved sandbox policy");
1504
+ if (!isAbsolute(spec.cwd) || spec.timeoutMs !== null && (!Number.isFinite(spec.timeoutMs) || spec.timeoutMs <= 0 || spec.timeoutMs > this.config.maxTimeoutMs)) throw new Error("ptc-runtime-node: run requires resolved cwd and timeout");
1505
+ const bindings = validateBindings(spec);
859
1506
  return await this.execute(spec, spec.sandboxPolicy, bindings, controller);
860
1507
  } finally {
1508
+ unregister?.();
861
1509
  this.live.delete(live);
862
1510
  completion.resolve();
863
1511
  }
864
1512
  }
865
1513
  async execute(spec, policy, bindings, controller) {
1514
+ const replyBinding = AsyncLocalStorage.bind(replyToBinding);
866
1515
  const output = new OutputLedger(this.config.maxOutputBytes);
867
1516
  const logs = [];
868
1517
  const sandbox = {
@@ -884,6 +1533,18 @@ var NodePtcRuntime = class extends PtcRuntime {
884
1533
  timedOut = true;
885
1534
  controller.abort("execution deadline reached");
886
1535
  }, spec.timeoutMs);
1536
+ let stopping;
1537
+ const stop = () => {
1538
+ if (stopping !== void 0) return stopping;
1539
+ stopping = (handle === void 0 ? Promise.resolve(true) : terminateProcess(handle, this.config.graceMs)).then((drained) => {
1540
+ unregister();
1541
+ return drained;
1542
+ }, (error) => {
1543
+ stopping = void 0;
1544
+ throw error;
1545
+ });
1546
+ return stopping;
1547
+ };
887
1548
  const finish = (failure, value) => {
888
1549
  if (settled) return;
889
1550
  settled = true;
@@ -891,10 +1552,8 @@ var NodePtcRuntime = class extends PtcRuntime {
891
1552
  signal.removeEventListener("abort", onAbort);
892
1553
  channel?.close();
893
1554
  (async () => {
894
- if (handle !== void 0) try {
895
- handle.terminate();
896
- await Promise.all([handle.done.catch(() => {}), handle.waitForExit()]);
897
- if ((await Promise.all([drainOutput(handle.stdout, this.config.graceMs), drainOutput(handle.stderr, this.config.graceMs)])).includes(false) && failure === void 0) failure = {
1555
+ try {
1556
+ if (!await stop() && failure === void 0) failure = {
898
1557
  kind: "worker-exit",
899
1558
  message: "Node process output did not close cleanly"
900
1559
  };
@@ -903,9 +1562,6 @@ var NodePtcRuntime = class extends PtcRuntime {
903
1562
  kind: "worker-exit",
904
1563
  message: `managed process cleanup failed: ${messageOf(error)}`
905
1564
  };
906
- } finally {
907
- handle.stdout?.destroy();
908
- handle.stderr?.destroy();
909
1565
  }
910
1566
  const outcome = outputOverflow ? overflowResult ?? output.limit(logs) : failure === void 0 ? output.success(logs, value) : output.failure(logs, failure);
911
1567
  result.resolve({
@@ -923,6 +1579,10 @@ var NodePtcRuntime = class extends PtcRuntime {
923
1579
  message: messageOf(signal.reason)
924
1580
  });
925
1581
  };
1582
+ const unregister = this.processes.register(policy, async () => {
1583
+ controller.abort(/* @__PURE__ */ new Error("sandbox mode changed"));
1584
+ await stop();
1585
+ });
926
1586
  signal.addEventListener("abort", onAbort, { once: true });
927
1587
  if (signal.aborted) onAbort();
928
1588
  try {
@@ -938,14 +1598,14 @@ var NodePtcRuntime = class extends PtcRuntime {
938
1598
  })),
939
1599
  maxOutputBytes: this.config.maxOutputBytes
940
1600
  };
941
- const executable = await this.ctx.subprocess.resolveExecutable(this.config.nodeExecutable, void 0, signal);
1601
+ const executable = await this.ctx.subprocess.resolveExecutable(this.config.launch.executable, void 0, signal);
942
1602
  if (settled) return await result.promise;
943
- const packaged = "pkg" in process && this.config.bootstrapPath === void 0;
1603
+ const packaged = this.config.launch.kind === "embedded";
944
1604
  const heapFlag = `--max-old-space-size=${this.config.maxOldGenerationSizeMb}`;
945
1605
  const argv = [
946
1606
  executable,
947
1607
  ...packaged ? [] : [heapFlag],
948
- ...bootstrapArgs(this.ctx.fs, this.config, this.config.maxMessageBytes)
1608
+ ...bootstrapArgs(this.ctx.fs, this.config.launch, this.config.maxMessageBytes)
949
1609
  ];
950
1610
  confined = policy.mode === "danger-full-access" ? void 0 : await this.ctx.sandbox.confine(argv, {
951
1611
  ...policy,
@@ -1112,27 +1772,10 @@ var NodePtcRuntime = class extends PtcRuntime {
1112
1772
  }
1113
1773
  const id = raw.id;
1114
1774
  (async () => {
1115
- let reply;
1116
- try {
1117
- const value = snapshotJsonValue(await fn(args));
1118
- if (value === void 0) throw new Error("binding resolution must be lossless JSON");
1119
- reply = {
1120
- type: "reply",
1121
- id,
1122
- ok: true,
1123
- value: encodePtcJsonWire(value)
1124
- };
1125
- } catch (error) {
1126
- reply = {
1127
- type: "reply",
1128
- id,
1129
- ok: false,
1130
- message: messageOf(error)
1131
- };
1132
- } finally {
1775
+ const reply = await replyBinding(id, fn, args).finally(() => {
1133
1776
  pending -= 1;
1134
1777
  pendingBytes -= bytes;
1135
- }
1778
+ });
1136
1779
  if (!settled) await transport.send(reply);
1137
1780
  })().catch((error) => {
1138
1781
  protocolFailure(messageOf(error));