pi-ptc-subagents 0.1.2 → 0.1.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/CHANGELOG.md +28 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +30 -3128
- package/dist/protocol-CfOgxz3u.js +2 -0
- package/dist/worker.js +3 -758
- package/package.json +3 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/protocol-DUnAP1Ro.js +0 -117
- package/dist/protocol-DUnAP1Ro.js.map +0 -1
- package/dist/worker.js.map +0 -1
package/dist/worker.js
CHANGED
|
@@ -1,759 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { inspect } from "node:util";
|
|
5
|
-
//#region src/runtime/worker-main.ts
|
|
6
|
-
/**
|
|
7
|
-
* Worker-side PTC runtime: installs one surface, runs one program, routes `tools.*`
|
|
8
|
-
* calls back to the host.
|
|
9
|
-
*
|
|
10
|
-
* State machine (ADR-0017 §5):
|
|
11
|
-
*
|
|
12
|
-
* CREATED ── function entry ──────────────────────────► BOOTING
|
|
13
|
-
* │ │
|
|
14
|
-
* │ control port installed
|
|
15
|
-
* │ │
|
|
16
|
-
* │ ┌───────┴───────┐
|
|
17
|
-
* │ │ │
|
|
18
|
-
* │ ▼ ▼
|
|
19
|
-
* │ (host sends init) READY ◄─── ready sent
|
|
20
|
-
* │ │ │
|
|
21
|
-
* │ ▼ │ init
|
|
22
|
-
* │ RUNNING │
|
|
23
|
-
* │ │ │
|
|
24
|
-
* │ (program completes / throws / cancel) │
|
|
25
|
-
* │ │ │
|
|
26
|
-
* │ ▼ │
|
|
27
|
-
* │ reset() + ready sent ───────────────► READY
|
|
28
|
-
* │
|
|
29
|
-
* └── (host closes control port) ─► worker terminates, no further transitions
|
|
30
|
-
*
|
|
31
|
-
* A later `connect` re-enters `READY` from wherever the worker is: a handover, not a wait — which is
|
|
32
|
-
* how a worker the host abandoned is picked up by the next run (see the connect handler, ADR-0017
|
|
33
|
-
* §10(h)).
|
|
34
|
-
*
|
|
35
|
-
* The `ready` frame has two meanings under ADR-0017 §5:
|
|
36
|
-
* - cold start: "booted, send init".
|
|
37
|
-
* - warm reuse: "cleared per-run state, ready for next init".
|
|
38
|
-
*
|
|
39
|
-
* Run identity: every accepted `init` starts a new run and claims the worker for it
|
|
40
|
-
* (a run ordinal, captured by that run). A run only owns the worker until the next
|
|
41
|
-
* `init` supersedes it — so the reset that ends a run must prove it still owns the
|
|
42
|
-
* worker before it clears anything (see `reset`).
|
|
43
|
-
*
|
|
44
|
-
* Frame ownership: a run's outbound frames (`log`, `narration`, `phase`, `call`, `result`,
|
|
45
|
-
* `error`) are posted through the control port that run captured when its `init` arrived —
|
|
46
|
-
* never through "whichever port the worker is connected to now". Warm reuse replaces the
|
|
47
|
-
* connection while a superseded run may still be unwinding, so routing is what keeps one run's
|
|
48
|
-
* terminal frame from settling the next one (see `post` and `startRun`).
|
|
49
|
-
*
|
|
50
|
-
* The worker is loaded by file URL (`dist/worker.js`, see `worker-entry.ts`); V8's
|
|
51
|
-
* code cache and Node's module cache survive across warm-reuse spawns. The
|
|
52
|
-
* SELF-CONTAINMENT RULE that previously gated this module — `Function.prototype.toString()`
|
|
53
|
-
* of `workerMain` into a `data:` URL — is retired.
|
|
54
|
-
*/
|
|
55
|
-
function workerMain(deps) {
|
|
56
|
-
const hostFrame = deps.protocol.hostFrame;
|
|
57
|
-
const workerFrame = deps.protocol.workerFrame;
|
|
58
|
-
const logLevel = deps.protocol.logLevel;
|
|
59
|
-
const errorKind = deps.protocol.errorKind;
|
|
60
|
-
/** Wrapper name used to compile a program body (`return …` is legal inside it). */
|
|
61
|
-
const programName = "__ptcProgram";
|
|
62
|
-
/** Active lifecycle state — see the doc comment at the top of the module. */
|
|
63
|
-
let state = "CREATED";
|
|
64
|
-
/**
|
|
65
|
-
* Run ordinal: incremented once per accepted `init` frame, never decremented. The value
|
|
66
|
-
* a run was born with is its identity for life — `reset()` checks it against this
|
|
67
|
-
* counter before touching anything.
|
|
68
|
-
*/
|
|
69
|
-
let runGeneration = 0;
|
|
70
|
-
let control;
|
|
71
|
-
let cancelled;
|
|
72
|
-
let nextCallId = 1;
|
|
73
|
-
/** Call frames posted and not yet answered; capped by `maxPendingCalls` (ADR-0004). */
|
|
74
|
-
let inFlightCalls = 0;
|
|
75
|
-
let maxPendingCalls = Number.POSITIVE_INFINITY;
|
|
76
|
-
const admissionWaiters = [];
|
|
77
|
-
const pending = /* @__PURE__ */ new Map();
|
|
78
|
-
/**
|
|
79
|
-
* Snapshot of `globalThis` taken once, at boot (end of `installWarningCapture`), i.e. the
|
|
80
|
-
* "warm baseline" a reset restores to.
|
|
81
|
-
*
|
|
82
|
-
* What it covers: `globalThis`'s own enumerable string keys — the way a PTC program normally
|
|
83
|
-
* touches the realm. A `globalThis.x = 1` left by run N is deleted before run N+1 starts, and any
|
|
84
|
-
* `console` / `tools` / `log` / `phase` / `parallel` / `pipeline` installed by run N is put back
|
|
85
|
-
* to its boot value for run N+1 to replace with its own install.
|
|
86
|
-
*
|
|
87
|
-
* What it does **not** cover — a superseded run's leftovers that survive into the next one, so
|
|
88
|
-
* "warm reuse is hermetic" would be the wrong reading of this snapshot:
|
|
89
|
-
* - a property added to `globalThis` that is non-enumerable or symbol-keyed (`Object.keys` is
|
|
90
|
-
* what both the snapshot and the restore walk, so such a property is neither deleted nor put
|
|
91
|
-
* back);
|
|
92
|
-
* - anything reachable through another object: `Array.prototype.x = 1`, a mutated built-in, the
|
|
93
|
-
* module-level state of a module the program imported (Node caches modules per worker, and a
|
|
94
|
-
* program can `import()` one);
|
|
95
|
-
* - `process` state the program wrote — `process.title`, `process.exitCode`, listeners it added;
|
|
96
|
-
* - timers it started, which keep firing (see `startRun`).
|
|
97
|
-
*
|
|
98
|
-
* Those are known, accepted limits of warm reuse rather than oversights of this snapshot.
|
|
99
|
-
*
|
|
100
|
-
* The *boot* snapshot is the only clean starting point there is: a later `connect` must not
|
|
101
|
-
* take a new one, or whatever the run it superseded left behind would be part of the baseline
|
|
102
|
-
* and would be restored into every run after it.
|
|
103
|
-
*/
|
|
104
|
-
let warmBaseline;
|
|
105
|
-
const globals = () => globalThis;
|
|
106
|
-
class ToolCallError extends Error {
|
|
107
|
-
toolName;
|
|
108
|
-
constructor(toolName, message) {
|
|
109
|
-
super(message);
|
|
110
|
-
this.name = "ToolCallError";
|
|
111
|
-
this.toolName = toolName;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
const messageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
115
|
-
/**
|
|
116
|
-
* Trim a stack down to what a reader can act on.
|
|
117
|
-
*
|
|
118
|
-
* Capped at a small depth so the model-facing stack stays inside the output budget.
|
|
119
|
-
* Now that the worker is loaded by file URL, no `data:text/javascript,…` frames
|
|
120
|
-
* appear at all — the rule kept the previous bootstrap's encoded payload from
|
|
121
|
-
* filling the stack.
|
|
122
|
-
*/
|
|
123
|
-
const stackOf = (error) => {
|
|
124
|
-
if (!(error instanceof Error) || typeof error.stack !== "string") return void 0;
|
|
125
|
-
const lines = error.stack.split("\n");
|
|
126
|
-
const kept = [lines[0] ?? ""];
|
|
127
|
-
for (const line of lines.slice(1)) {
|
|
128
|
-
kept.push(line);
|
|
129
|
-
if (kept.length >= 6) break;
|
|
130
|
-
}
|
|
131
|
-
return kept.join("\n");
|
|
132
|
-
};
|
|
133
|
-
/**
|
|
134
|
-
* Post a frame through `port` — the control port the caller's run belongs to — defaulting to
|
|
135
|
-
* the port the worker is currently connected to.
|
|
136
|
-
*
|
|
137
|
-
* Invariant (ADR-0017 §10(b)): *a run's outbound frames travel only through the port that run
|
|
138
|
-
* captured at `init` time*. Warm reuse installs a new port while a superseded run may still be
|
|
139
|
-
* unwinding, and that run's terminal `result` must not land on the *next* run's port — the host
|
|
140
|
-
* reads any `result` there as "the current run settled" (`handleFrame` → `finish()`), which
|
|
141
|
-
* would end a run whose program never finished. The port of a run the host has abandoned has
|
|
142
|
-
* been closed on the host's side, so its late frames fail to send and are dropped here; that is
|
|
143
|
-
* the right outcome for a frame nobody owns any more.
|
|
144
|
-
*
|
|
145
|
-
* Only realm-level frames — the `connect` handshake's `ready`, warning `log`s, which belong to
|
|
146
|
-
* the worker rather than to a program — use the default.
|
|
147
|
-
*/
|
|
148
|
-
const post = (frame, port = control) => {
|
|
149
|
-
if (!port) return false;
|
|
150
|
-
try {
|
|
151
|
-
port.postMessage(frame);
|
|
152
|
-
return true;
|
|
153
|
-
} catch {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
const postLog = (level, text, port) => {
|
|
158
|
-
post({
|
|
159
|
-
kind: workerFrame.log,
|
|
160
|
-
level,
|
|
161
|
-
text
|
|
162
|
-
}, port);
|
|
163
|
-
};
|
|
164
|
-
const postError = (kind, message, stack, port) => {
|
|
165
|
-
post(stack === void 0 ? {
|
|
166
|
-
kind: workerFrame.error,
|
|
167
|
-
error: {
|
|
168
|
-
kind,
|
|
169
|
-
message
|
|
170
|
-
}
|
|
171
|
-
} : {
|
|
172
|
-
kind: workerFrame.error,
|
|
173
|
-
error: {
|
|
174
|
-
kind,
|
|
175
|
-
message,
|
|
176
|
-
stack
|
|
177
|
-
}
|
|
178
|
-
}, port);
|
|
179
|
-
};
|
|
180
|
-
const abortError = (reason) => {
|
|
181
|
-
const error = /* @__PURE__ */ new Error(reason === "timeout" ? "PTC run timed out" : "PTC run was cancelled");
|
|
182
|
-
error.name = "AbortError";
|
|
183
|
-
return error;
|
|
184
|
-
};
|
|
185
|
-
const closedError = () => {
|
|
186
|
-
const error = /* @__PURE__ */ new Error("the PTC control channel closed before the call completed");
|
|
187
|
-
error.name = "AbortError";
|
|
188
|
-
return error;
|
|
189
|
-
};
|
|
190
|
-
/**
|
|
191
|
-
* Admission control for host binding calls.
|
|
192
|
-
*
|
|
193
|
-
* "Maximum simultaneous host binding calls" is the worker's invariant to keep: a call
|
|
194
|
-
* frame is only posted once fewer than `maxPendingCalls` are outstanding, so a wide
|
|
195
|
-
* `Promise.all` / `parallel()` burst queues here rather than arriving at the host in one
|
|
196
|
-
* tick. The host still counts arrivals as a backstop, but it must never see this budget
|
|
197
|
-
* exceeded by a well-behaved program.
|
|
198
|
-
*
|
|
199
|
-
* Waiting for a slot is cancel-aware: `cancel` and a closed control port reject waiters
|
|
200
|
-
* immediately, so an oversized burst unwinds at once instead of waiting out the grace
|
|
201
|
-
* window before the host terminates the worker.
|
|
202
|
-
*/
|
|
203
|
-
const acquireCallSlot = async () => {
|
|
204
|
-
if (inFlightCalls < maxPendingCalls) {
|
|
205
|
-
inFlightCalls += 1;
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
await new Promise((resolve, reject) => {
|
|
209
|
-
admissionWaiters.push({
|
|
210
|
-
resolve,
|
|
211
|
-
reject
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
};
|
|
215
|
-
const releaseCallSlot = () => {
|
|
216
|
-
const next = admissionWaiters.shift();
|
|
217
|
-
if (next) {
|
|
218
|
-
next.resolve();
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
inFlightCalls -= 1;
|
|
222
|
-
};
|
|
223
|
-
const rejectAdmissionWaiters = (error) => {
|
|
224
|
-
while (admissionWaiters.length > 0) {
|
|
225
|
-
const waiter = admissionWaiters.shift();
|
|
226
|
-
if (waiter) waiter.reject(error);
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
/** Settle every outstanding call and every admission wait, so nothing can hang. */
|
|
230
|
-
const flushPendingCalls = (error) => {
|
|
231
|
-
rejectAdmissionWaiters(error);
|
|
232
|
-
for (const [callId, entry] of pending) {
|
|
233
|
-
pending.delete(callId);
|
|
234
|
-
entry.reject(error);
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
/**
|
|
238
|
-
* F3 — reinstall the frozen per-run environment carried in `workerData`.
|
|
239
|
-
*
|
|
240
|
-
* The spawn already passes the same snapshot as the worker's `env` option; rebuilding
|
|
241
|
-
* `process.env` from `workerData` here is what makes the snapshot the authority: the
|
|
242
|
-
* program's environment is the frozen record, never whatever `process.env` happened
|
|
243
|
-
* to hold when a binding call crossed the wire.
|
|
244
|
-
*/
|
|
245
|
-
const installFrozenEnv = () => {
|
|
246
|
-
const data = deps.workerData;
|
|
247
|
-
if (typeof data !== "object" || data === null) return;
|
|
248
|
-
const snapshot = data.env;
|
|
249
|
-
if (typeof snapshot !== "object" || snapshot === null || Array.isArray(snapshot)) return;
|
|
250
|
-
const env = snapshot;
|
|
251
|
-
for (const key of Object.keys(process.env)) if (!(key in env)) delete process.env[key];
|
|
252
|
-
for (const key of Object.keys(env)) {
|
|
253
|
-
const value = env[key];
|
|
254
|
-
if (typeof value === "string") process.env[key] = value;
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
/**
|
|
258
|
-
* Lossless-JSON materialization of the program's return value (R1 §3 `invalid-output`).
|
|
259
|
-
*
|
|
260
|
-
* Plain JSON only: class instances, `Date`/`Map`/`Set`, functions, symbols, bigints,
|
|
261
|
-
* non-finite numbers and cycles are rejected with a path-qualified reason instead of
|
|
262
|
-
* being silently lossy through the transport. `undefined` follows `JSON.stringify`:
|
|
263
|
-
* object properties are dropped, array slots become `null`.
|
|
264
|
-
*/
|
|
265
|
-
const toJsonValue = (value) => {
|
|
266
|
-
const seen = /* @__PURE__ */ new Set();
|
|
267
|
-
const walk = (candidate, path) => {
|
|
268
|
-
if (candidate === null) return {
|
|
269
|
-
ok: true,
|
|
270
|
-
value: null
|
|
271
|
-
};
|
|
272
|
-
if (typeof candidate === "string" || typeof candidate === "boolean") return {
|
|
273
|
-
ok: true,
|
|
274
|
-
value: candidate
|
|
275
|
-
};
|
|
276
|
-
if (typeof candidate === "undefined") return {
|
|
277
|
-
ok: true,
|
|
278
|
-
value: void 0
|
|
279
|
-
};
|
|
280
|
-
if (typeof candidate === "number") {
|
|
281
|
-
if (!Number.isFinite(candidate)) return {
|
|
282
|
-
ok: false,
|
|
283
|
-
reason: `${path} is ${String(candidate)}, which is not representable as JSON`
|
|
284
|
-
};
|
|
285
|
-
return {
|
|
286
|
-
ok: true,
|
|
287
|
-
value: candidate
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
if (typeof candidate !== "object") return {
|
|
291
|
-
ok: false,
|
|
292
|
-
reason: `${path} is ${describeValue(candidate)}; results must be lossless JSON`
|
|
293
|
-
};
|
|
294
|
-
if (Array.isArray(candidate)) {
|
|
295
|
-
if (seen.has(candidate)) return {
|
|
296
|
-
ok: false,
|
|
297
|
-
reason: `${path} is a circular reference`
|
|
298
|
-
};
|
|
299
|
-
seen.add(candidate);
|
|
300
|
-
const items = [];
|
|
301
|
-
for (let index = 0; index < candidate.length; index += 1) {
|
|
302
|
-
const item = walk(candidate[index], `${path}[${index}]`);
|
|
303
|
-
if (!item.ok) return item;
|
|
304
|
-
items.push(item.value === void 0 ? null : item.value);
|
|
305
|
-
}
|
|
306
|
-
seen.delete(candidate);
|
|
307
|
-
return {
|
|
308
|
-
ok: true,
|
|
309
|
-
value: items
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
const prototype = Object.getPrototypeOf(candidate);
|
|
313
|
-
if (prototype !== Object.prototype && prototype !== null) {
|
|
314
|
-
const ctor = candidate.constructor;
|
|
315
|
-
return {
|
|
316
|
-
ok: false,
|
|
317
|
-
reason: `${path} is a ${ctor && typeof ctor.name === "string" && ctor.name.length > 0 ? ctor.name : "an object"}; results must be plain JSON objects`
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
if (seen.has(candidate)) return {
|
|
321
|
-
ok: false,
|
|
322
|
-
reason: `${path} is a circular reference`
|
|
323
|
-
};
|
|
324
|
-
seen.add(candidate);
|
|
325
|
-
const entries = {};
|
|
326
|
-
for (const key of Object.keys(candidate)) {
|
|
327
|
-
const item = walk(candidate[key], `${path}.${key}`);
|
|
328
|
-
if (!item.ok) return item;
|
|
329
|
-
if (item.value !== void 0) entries[key] = item.value;
|
|
330
|
-
}
|
|
331
|
-
seen.delete(candidate);
|
|
332
|
-
return {
|
|
333
|
-
ok: true,
|
|
334
|
-
value: entries
|
|
335
|
-
};
|
|
336
|
-
};
|
|
337
|
-
return walk(value, "result");
|
|
338
|
-
};
|
|
339
|
-
const formatArgs = (args) => args.map((arg) => typeof arg === "string" ? arg : deps.inspect(arg, {
|
|
340
|
-
depth: 4,
|
|
341
|
-
breakLength: Infinity,
|
|
342
|
-
colors: false
|
|
343
|
-
})).join(" ");
|
|
344
|
-
/**
|
|
345
|
-
* Capture `console` instead of letting worker stdio reach the host terminal.
|
|
346
|
-
*
|
|
347
|
-
* Only the printing methods exist on this surface; everything routes to `log` frames
|
|
348
|
-
* so the model (not the terminal) sees the program's output. Log frames belong to the run
|
|
349
|
-
* whose console this is, hence the captured `port`.
|
|
350
|
-
*/
|
|
351
|
-
const installConsole = (port) => {
|
|
352
|
-
const write = (level) => {
|
|
353
|
-
return (...args) => {
|
|
354
|
-
postLog(level, formatArgs(args), port);
|
|
355
|
-
};
|
|
356
|
-
};
|
|
357
|
-
globals().console = {
|
|
358
|
-
log: write(logLevel.log),
|
|
359
|
-
info: write(logLevel.info),
|
|
360
|
-
warn: write(logLevel.warn),
|
|
361
|
-
error: write(logLevel.error),
|
|
362
|
-
debug: write(logLevel.debug)
|
|
363
|
-
};
|
|
364
|
-
};
|
|
365
|
-
/**
|
|
366
|
-
* Route `process.emitWarning` output through `log` frames.
|
|
367
|
-
*
|
|
368
|
-
* Node's default warning printer writes to stderr, which is piped to the host terminal.
|
|
369
|
-
* `ExperimentalWarning`s are dropped rather than forwarded: the PTC runtime itself uses
|
|
370
|
-
* experimental Node APIs (type stripping, for one) and those warnings are about the
|
|
371
|
-
* harness, not about the program — forwarding them would put Node-internals noise into
|
|
372
|
-
* every run's logs.
|
|
373
|
-
*/
|
|
374
|
-
const installWarningCapture = () => {
|
|
375
|
-
if (typeof process.removeAllListeners !== "function" || typeof process.on !== "function") return;
|
|
376
|
-
process.removeAllListeners("warning");
|
|
377
|
-
process.on("warning", (warning) => {
|
|
378
|
-
const record = typeof warning === "object" && warning !== null ? warning : void 0;
|
|
379
|
-
const name = record && typeof record.name === "string" ? record.name : "Warning";
|
|
380
|
-
if (name === "ExperimentalWarning") return;
|
|
381
|
-
const detail = record && typeof record.message === "string" ? record.message : String(warning);
|
|
382
|
-
postLog(logLevel.warn, `${name}: ${detail}`);
|
|
383
|
-
});
|
|
384
|
-
};
|
|
385
|
-
/**
|
|
386
|
-
* Compile a program body into a callable.
|
|
387
|
-
*
|
|
388
|
-
* The body is wrapped in an async function so `return` and `await` work at the top
|
|
389
|
-
* level, then run through `stripTypeScriptTypes` so the same body may carry type
|
|
390
|
-
* annotations (DSH's "type annotations are advisory, the code runs type-stripped").
|
|
391
|
-
* JavaScript the TypeScript parser rejects falls back to the unstripped source; a
|
|
392
|
-
* genuine syntax error then surfaces from the `Function` constructor instead.
|
|
393
|
-
*/
|
|
394
|
-
const compileProgram = (code) => {
|
|
395
|
-
const wrapped = `async function ${programName}() {\n${code}\n}`;
|
|
396
|
-
let source = wrapped;
|
|
397
|
-
try {
|
|
398
|
-
source = deps.stripTypes(wrapped, { mode: "strip" });
|
|
399
|
-
} catch {
|
|
400
|
-
source = wrapped;
|
|
401
|
-
}
|
|
402
|
-
return new Function(`${source}\nreturn ${programName}();`);
|
|
403
|
-
};
|
|
404
|
-
const runProgram = async (code, generation, port) => {
|
|
405
|
-
try {
|
|
406
|
-
let program;
|
|
407
|
-
try {
|
|
408
|
-
program = compileProgram(code);
|
|
409
|
-
} catch (error) {
|
|
410
|
-
postError(errorKind.exception, `program failed to compile: ${messageOf(error)}`, stackOf(error), port);
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
try {
|
|
414
|
-
const value = await program();
|
|
415
|
-
const normalized = toJsonValue(value);
|
|
416
|
-
if (!normalized.ok) {
|
|
417
|
-
postError(errorKind.invalidOutput, normalized.reason, void 0, port);
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
post(normalized.value === void 0 ? { kind: workerFrame.result } : {
|
|
421
|
-
kind: workerFrame.result,
|
|
422
|
-
value: normalized.value
|
|
423
|
-
}, port);
|
|
424
|
-
} catch (error) {
|
|
425
|
-
postError(cancelled ?? errorKind.exception, messageOf(error), stackOf(error), port);
|
|
426
|
-
}
|
|
427
|
-
} finally {
|
|
428
|
-
await reset(generation, port);
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
|
-
/**
|
|
432
|
-
* `tools.<name>(args)` — acquire an admission slot, post a call frame, and await the
|
|
433
|
-
* matching `call-result`.
|
|
434
|
-
*
|
|
435
|
-
* The slot is released on every exit path (settled, admission refused by `post`, cancel),
|
|
436
|
-
* which is what keeps a burst draining instead of deadlocking. Call frames carry the
|
|
437
|
-
* binding's own run port: a superseded run's late call is dropped at its closed port instead
|
|
438
|
-
* of arriving at the host as a call for a run it never made.
|
|
439
|
-
*/
|
|
440
|
-
const makeBinding = (name, port) => {
|
|
441
|
-
return async (args) => {
|
|
442
|
-
if (cancelled) throw abortError(cancelled);
|
|
443
|
-
await acquireCallSlot();
|
|
444
|
-
try {
|
|
445
|
-
if (cancelled) throw abortError(cancelled);
|
|
446
|
-
const callId = nextCallId;
|
|
447
|
-
nextCallId += 1;
|
|
448
|
-
const promise = new Promise((resolve, reject) => {
|
|
449
|
-
pending.set(callId, {
|
|
450
|
-
resolve,
|
|
451
|
-
reject
|
|
452
|
-
});
|
|
453
|
-
});
|
|
454
|
-
if (!post({
|
|
455
|
-
kind: workerFrame.call,
|
|
456
|
-
callId,
|
|
457
|
-
tool: name,
|
|
458
|
-
args
|
|
459
|
-
}, port)) {
|
|
460
|
-
pending.delete(callId);
|
|
461
|
-
throw new ToolCallError(name, `${name}() could not be called: the arguments are not transferable or the run has ended`);
|
|
462
|
-
}
|
|
463
|
-
return await promise;
|
|
464
|
-
} finally {
|
|
465
|
-
releaseCallSlot();
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
|
-
};
|
|
469
|
-
const settleCall = (frame) => {
|
|
470
|
-
const callId = frame.callId;
|
|
471
|
-
if (typeof callId !== "number") return;
|
|
472
|
-
const entry = pending.get(callId);
|
|
473
|
-
if (!entry) return;
|
|
474
|
-
pending.delete(callId);
|
|
475
|
-
if (frame.ok === true) {
|
|
476
|
-
entry.resolve(frame.value);
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
entry.reject(new ToolCallError(typeof frame.tool === "string" ? frame.tool : "unknown", typeof frame.message === "string" ? frame.message : "binding call failed"));
|
|
480
|
-
};
|
|
481
|
-
const cancelPending = (reason) => {
|
|
482
|
-
cancelled = reason;
|
|
483
|
-
flushPendingCalls(abortError(reason));
|
|
484
|
-
};
|
|
485
|
-
/**
|
|
486
|
-
* Confirm a cancel that has nothing to flush, and forget it.
|
|
487
|
-
*
|
|
488
|
-
* A cancel can arrive while the worker is idle — booting, or freshly reset and waiting for the
|
|
489
|
-
* next `init`. There is no program to interrupt and no pending call to reject, so the honest
|
|
490
|
-
* answer is to report the cancel: the host asked to stop, and it should settle now rather than
|
|
491
|
-
* wait out its grace window for a run that will never start (and the `init` it might send next
|
|
492
|
-
* would clear the verdict and run the program anyway).
|
|
493
|
-
*/
|
|
494
|
-
const reportIdleCancel = (reason, port) => {
|
|
495
|
-
cancelled = void 0;
|
|
496
|
-
const error = abortError(reason);
|
|
497
|
-
postError(reason, error.message, void 0, port);
|
|
498
|
-
};
|
|
499
|
-
const assertItemCount = (helper, count, maxItemsPerCall) => {
|
|
500
|
-
if (count > maxItemsPerCall) throw new RangeError(`${helper}() received ${count} items, exceeding maxItemsPerCall (${maxItemsPerCall})`);
|
|
501
|
-
};
|
|
502
|
-
const installWorkflowHelpers = (args, maxItemsPerCall, port) => {
|
|
503
|
-
const target = globals();
|
|
504
|
-
target.args = args === void 0 ? null : args;
|
|
505
|
-
target.log = (message) => {
|
|
506
|
-
if (typeof message !== "string") throw new TypeError(`log(message) expects a string, received ${describeValue(message)}`);
|
|
507
|
-
post({
|
|
508
|
-
kind: workerFrame.narration,
|
|
509
|
-
message
|
|
510
|
-
}, port);
|
|
511
|
-
};
|
|
512
|
-
target.phase = (title) => {
|
|
513
|
-
if (typeof title !== "string") throw new TypeError(`phase(title) expects a string, received ${describeValue(title)}`);
|
|
514
|
-
post({
|
|
515
|
-
kind: workerFrame.phase,
|
|
516
|
-
title
|
|
517
|
-
}, port);
|
|
518
|
-
};
|
|
519
|
-
target.parallel = async (thunks) => {
|
|
520
|
-
if (!Array.isArray(thunks)) throw new TypeError(`parallel(thunks) expects an array of functions, received ${describeValue(thunks)}`);
|
|
521
|
-
assertItemCount("parallel", thunks.length, maxItemsPerCall);
|
|
522
|
-
for (let index = 0; index < thunks.length; index += 1) if (typeof thunks[index] !== "function") throw new TypeError(`parallel(thunks) expects functions; item ${index} is ${describeValue(thunks[index])}`);
|
|
523
|
-
return await Promise.all(thunks.map(async (thunk) => {
|
|
524
|
-
try {
|
|
525
|
-
return await thunk();
|
|
526
|
-
} catch {
|
|
527
|
-
return null;
|
|
528
|
-
}
|
|
529
|
-
}));
|
|
530
|
-
};
|
|
531
|
-
target.pipeline = async (items, ...stages) => {
|
|
532
|
-
if (!Array.isArray(items)) throw new TypeError(`pipeline(items, ...stages) expects an array of items, received ${describeValue(items)}`);
|
|
533
|
-
assertItemCount("pipeline", items.length, maxItemsPerCall);
|
|
534
|
-
if (stages.length === 0) throw new TypeError("pipeline(items, ...stages) requires at least one stage function");
|
|
535
|
-
for (let index = 0; index < stages.length; index += 1) if (typeof stages[index] !== "function") throw new TypeError(`pipeline(items, ...stages) expects stage functions; stage ${index} is ${describeValue(stages[index])}`);
|
|
536
|
-
const stageFunctions = stages;
|
|
537
|
-
return await Promise.all(items.map(async (item, index) => {
|
|
538
|
-
try {
|
|
539
|
-
let current = item;
|
|
540
|
-
for (const stage of stageFunctions) current = await stage(current, item, index);
|
|
541
|
-
return current;
|
|
542
|
-
} catch {
|
|
543
|
-
return null;
|
|
544
|
-
}
|
|
545
|
-
}));
|
|
546
|
-
};
|
|
547
|
-
};
|
|
548
|
-
/**
|
|
549
|
-
* Restore `globalThis` to the warm baseline taken at the end of the worker's
|
|
550
|
-
* boot-time setup (`installWarningCapture`).
|
|
551
|
-
*
|
|
552
|
-
* This is stronger than deleting the known `RUN_GLOBAL_KEYS`: it also wipes any
|
|
553
|
-
* stray property the previous run's program left behind (`globalThis.x = 1` and
|
|
554
|
-
* similar). Without it, warm reuse would leak state from one run to the next.
|
|
555
|
-
* Enumerating own *enumerable string* keys is also its limit — see `warmBaseline`
|
|
556
|
-
* for the leftovers that walk past it.
|
|
557
|
-
*
|
|
558
|
-
* Properties in the baseline are restored even if the program overwrote them —
|
|
559
|
-
* that is what keeps `console` honest for the next run, since the program's
|
|
560
|
-
* console override does not survive the reset.
|
|
561
|
-
*/
|
|
562
|
-
const restoreWarmBaseline = () => {
|
|
563
|
-
if (!warmBaseline) return;
|
|
564
|
-
const target = globals();
|
|
565
|
-
for (const key of Object.keys(target)) if (!warmBaseline.has(key)) delete target[key];
|
|
566
|
-
for (const [key, value] of warmBaseline) {
|
|
567
|
-
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
568
|
-
if (descriptor !== void 0 && descriptor.writable === false) continue;
|
|
569
|
-
try {
|
|
570
|
-
target[key] = value;
|
|
571
|
-
} catch {}
|
|
572
|
-
}
|
|
573
|
-
};
|
|
574
|
-
/**
|
|
575
|
-
* RUNNING → READY: clear the per-run state this worker owns so it is safe to accept
|
|
576
|
-
* another `init` frame on warm reuse (ADR-0017 §5).
|
|
577
|
-
*
|
|
578
|
-
* Order matters: flush first so no settled call leaks into the next run, then
|
|
579
|
-
* restore the warm baseline (the program may have left values behind that would
|
|
580
|
-
* shadow the next install), then rehydrate the frozen env so the next run starts
|
|
581
|
-
* from the same `process.env` snapshot the host captured. State transitions to
|
|
582
|
-
* `READY` last — only when the worker is actually eligible for the next `init`.
|
|
583
|
-
*
|
|
584
|
-
* What it clears is exactly: the outstanding calls and admission waiters, the run
|
|
585
|
-
* bookkeeping (`inFlightCalls`, `cancelled`, `nextCallId`, `pending`), `globalThis`'s own
|
|
586
|
-
* enumerable keys (via the warm baseline — see `warmBaseline` for what that misses) and
|
|
587
|
-
* `process.env`. It does **not** stop timers the program left running; nothing can, see
|
|
588
|
-
* `startRun`. And when the run that produced this reset was superseded, none of it happens at
|
|
589
|
-
* all: the clearing then falls to the *next* run's `startRun` (see the invariant below).
|
|
590
|
-
*
|
|
591
|
-
* Invariant — a reset is scoped to the run that produced it: `generation` is the run
|
|
592
|
-
* ordinal the finishing run was born with. If the worker has since been handed to a
|
|
593
|
-
* newer run (`generation !== runGeneration`), this reset is a no-op: it must not clear
|
|
594
|
-
* the newer run's calls, globals or bookkeeping, and it must not post `ready` (the
|
|
595
|
-
* host reads `ready` as "the current run settled, send the next init" — posting a
|
|
596
|
-
* stranger's `ready` is what makes a program execute twice). Ownership is captured at
|
|
597
|
-
* `init` time rather than read from the shared `state`, because `state` belongs to
|
|
598
|
-
* whoever owns the worker now. `port` is the run's own control port for the same reason:
|
|
599
|
-
* a `ready` that survives the ownership check still has no business on another run's port.
|
|
600
|
-
*/
|
|
601
|
-
const reset = async (generation, port) => {
|
|
602
|
-
if (state !== "RUNNING" || generation !== runGeneration) return;
|
|
603
|
-
flushPendingCalls(closedError());
|
|
604
|
-
inFlightCalls = 0;
|
|
605
|
-
cancelled = void 0;
|
|
606
|
-
nextCallId = 1;
|
|
607
|
-
pending.clear();
|
|
608
|
-
restoreWarmBaseline();
|
|
609
|
-
installFrozenEnv();
|
|
610
|
-
state = "READY";
|
|
611
|
-
post({ kind: workerFrame.ready }, port);
|
|
612
|
-
};
|
|
613
|
-
/**
|
|
614
|
-
* Claim the worker for one run and install that run's surface.
|
|
615
|
-
*
|
|
616
|
-
* `port` is the channel the run's `init` arrived on; it *is* the run's control port for life.
|
|
617
|
-
* Every frame the run produces afterwards — `log`, `narration`, `phase`, `call`, `result`,
|
|
618
|
-
* `error`, its closing `ready` — is posted through it, even if the worker has since been handed
|
|
619
|
-
* to another run (see `post`).
|
|
620
|
-
*
|
|
621
|
-
* The run starts from a clean realm. That cannot be left to the previous run's `reset()`: a run
|
|
622
|
-
* the host abandoned is superseded, and its reset is a generation-guarded no-op — so its strays on
|
|
623
|
-
* `globalThis` (`tools`, the workflow helpers, whatever its program assigned) and its bookkeeping
|
|
624
|
-
* would otherwise live on into this run and break the isolation warm reuse promises (ADR-0017
|
|
625
|
-
* §10(d) — and note §5 is explicit that the reset handshake is *not* where isolation comes
|
|
626
|
-
* from). Clearing here is unconditional: one run starting is the end of everything the previous
|
|
627
|
-
* round was doing, whatever the previous round did or did not do.
|
|
628
|
-
*
|
|
629
|
-
* "Clean" is what `restoreWarmBaseline` can restore — `globalThis`'s own enumerable keys, then the
|
|
630
|
-
* frozen env and this run's fresh installs — plus this worker's run bookkeeping. It is honestly
|
|
631
|
-
* not every trace a program can leave: a timer it started keeps firing, a prototype it patched
|
|
632
|
-
* stays patched, and `process` state it wrote stays written (see `warmBaseline` for the list).
|
|
633
|
-
* Timers are the leftover this function used to claim to clear, and the claim was false: Node
|
|
634
|
-
* offers no way to enumerate a worker's pending timers — inside a worker thread
|
|
635
|
-
* `process._getActiveHandles()` returns `[]` and `process.getActiveResourcesInfo()` reports names
|
|
636
|
-
* (`"Timeout"`), not handles (both checked on Node 24.21.0). A leftover interval is therefore a
|
|
637
|
-
* known, accepted limitation of warm reuse, not something this function clears.
|
|
638
|
-
*/
|
|
639
|
-
const startRun = (frame, generation, port) => {
|
|
640
|
-
restoreWarmBaseline();
|
|
641
|
-
cancelled = void 0;
|
|
642
|
-
inFlightCalls = 0;
|
|
643
|
-
pending.clear();
|
|
644
|
-
installFrozenEnv();
|
|
645
|
-
installConsole(port);
|
|
646
|
-
maxPendingCalls = typeof frame.maxPendingCalls === "number" && Number.isFinite(frame.maxPendingCalls) && frame.maxPendingCalls > 0 ? frame.maxPendingCalls : Number.POSITIVE_INFINITY;
|
|
647
|
-
const tools = {};
|
|
648
|
-
const bindingNames = Array.isArray(frame.bindings) ? frame.bindings.filter((name) => typeof name === "string") : [];
|
|
649
|
-
for (const name of bindingNames) tools[name] = makeBinding(name, port);
|
|
650
|
-
const available = bindingNames.join(", ") || "(none)";
|
|
651
|
-
const candidates = Array.isArray(frame.bindingCandidates) ? frame.bindingCandidates : [];
|
|
652
|
-
for (const name of candidates) {
|
|
653
|
-
if (typeof name !== "string" || tools[name]) continue;
|
|
654
|
-
tools[name] = async () => {
|
|
655
|
-
throw new ToolCallError(name, `no binding named "${name}" in this run; available bindings: ${available}`);
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
globals().tools = tools;
|
|
659
|
-
if (frame.surface === "workflow") {
|
|
660
|
-
const maxItemsPerCall = typeof frame.maxItemsPerCall === "number" ? frame.maxItemsPerCall : 0;
|
|
661
|
-
installWorkflowHelpers(frame.args, maxItemsPerCall, port);
|
|
662
|
-
}
|
|
663
|
-
runProgram(typeof frame.code === "string" ? frame.code : "", generation, port);
|
|
664
|
-
};
|
|
665
|
-
/**
|
|
666
|
-
* Handle one host frame, tagging it with the port it arrived on: that port *is* the run's
|
|
667
|
-
* control port if the frame is an `init` (see `startRun`), so the run's frames stay on the
|
|
668
|
-
* channel its own host is listening to.
|
|
669
|
-
*/
|
|
670
|
-
const handleHostFrame = (raw, port) => {
|
|
671
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return;
|
|
672
|
-
const frame = raw;
|
|
673
|
-
if (frame.kind === hostFrame.init) {
|
|
674
|
-
if (state !== "READY") return;
|
|
675
|
-
runGeneration += 1;
|
|
676
|
-
const generation = runGeneration;
|
|
677
|
-
state = "RUNNING";
|
|
678
|
-
startRun(frame, generation, port);
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
if (frame.kind === hostFrame.callResult) {
|
|
682
|
-
settleCall(frame);
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
if (frame.kind === hostFrame.cancel) {
|
|
686
|
-
const reason = frame.reason === "timeout" ? "timeout" : "abort";
|
|
687
|
-
if (state === "READY") {
|
|
688
|
-
reportIdleCancel(reason, port);
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
cancelPending(reason);
|
|
692
|
-
}
|
|
693
|
-
};
|
|
694
|
-
state = "BOOTING";
|
|
695
|
-
deps.parentPort.on("message", (value) => {
|
|
696
|
-
if (typeof value !== "object" || value === null) return;
|
|
697
|
-
const frame = value;
|
|
698
|
-
if (frame.kind !== hostFrame.connect) return;
|
|
699
|
-
const port = frame.port;
|
|
700
|
-
if (typeof port !== "object" || port === null || typeof port.on !== "function") return;
|
|
701
|
-
const connected = port;
|
|
702
|
-
control = connected;
|
|
703
|
-
connected.on("message", (raw) => {
|
|
704
|
-
handleHostFrame(raw, connected);
|
|
705
|
-
});
|
|
706
|
-
connected.on("close", () => {
|
|
707
|
-
if (control === connected) flushPendingCalls(closedError());
|
|
708
|
-
});
|
|
709
|
-
installWarningCapture();
|
|
710
|
-
const isFirstBoot = warmBaseline === void 0;
|
|
711
|
-
if (isFirstBoot) warmBaseline = new Map(Object.entries(globals()));
|
|
712
|
-
state = "READY";
|
|
713
|
-
if (cancelled !== void 0) {
|
|
714
|
-
if (isFirstBoot) {
|
|
715
|
-
reportIdleCancel(cancelled, connected);
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
cancelled = void 0;
|
|
719
|
-
}
|
|
720
|
-
post({ kind: workerFrame.ready }, connected);
|
|
721
|
-
});
|
|
722
|
-
}
|
|
723
|
-
//#endregion
|
|
724
|
-
//#region src/runtime/worker-entry.ts
|
|
725
|
-
/**
|
|
726
|
-
* Worker entry — loaded by file URL (ADR-0017 §7).
|
|
727
|
-
*
|
|
728
|
-
* The previous bootstrap composed a `data:text/javascript,…` URL from
|
|
729
|
-
* `Function.prototype.toString()` of `workerMain`, which kept the package as a
|
|
730
|
-
* single self-contained file but paid a fresh V8 spin-up + module-graph parse
|
|
731
|
-
* for every worker spawn (no code-cache reuse). After the rolldown dual-entry
|
|
732
|
-
* build emits `dist/worker.js` as a sibling of `dist/index.js`, this file is
|
|
733
|
-
* the target the host loads; V8 and Node's module cache survive across
|
|
734
|
-
* warm-reuse spawns.
|
|
735
|
-
*
|
|
736
|
-
* The protocol tables travel as static imports in both bundles (host imports
|
|
737
|
-
* from `dist/index.js`, worker imports from `dist/worker.js`) — both files
|
|
738
|
-
* bundle `./protocol.ts`, so each side constructs `workerProtocolSpec()`
|
|
739
|
-
* independently and the wire strings stay aligned. This removes the build-time
|
|
740
|
-
* protocol literal the data: URL used to carry.
|
|
741
|
-
*
|
|
742
|
-
* `workerData` is the spawn-time payload `{ env }` — the frozen per-run env
|
|
743
|
-
* snapshot (F3, ADR-0005) — and nothing else. Run identity is *not* spawn-time:
|
|
744
|
-
* a warm worker outlives the run that spawned it, so the host sends each run's
|
|
745
|
-
* `runId` in its own `init` frame instead. `workerMain` reads neither (see its
|
|
746
|
-
* `WorkerMainDeps.workerData` note); `runId` is host-side bookkeeping for
|
|
747
|
-
* attributing frames to a run.
|
|
748
|
-
*/
|
|
749
|
-
workerMain({
|
|
750
|
-
parentPort,
|
|
751
|
-
workerData,
|
|
752
|
-
inspect,
|
|
753
|
-
stripTypes: stripTypeScriptTypes,
|
|
754
|
-
protocol: workerProtocolSpec()
|
|
755
|
-
});
|
|
756
|
-
//#endregion
|
|
757
|
-
export {};
|
|
758
|
-
|
|
1
|
+
import{a as e,c as t}from"./protocol-CfOgxz3u.js";import{stripTypeScriptTypes as n}from"node:module";import{parentPort as r,workerData as i}from"node:worker_threads";import{inspect as a}from"node:util";function o(t){let n=t.protocol.hostFrame,r=t.protocol.workerFrame,i=t.protocol.logLevel,a=t.protocol.errorKind,o=`__ptcProgram`,s=`CREATED`,c=0,l,u,d=1,f=0,p=1/0,m=[],h=/* @__PURE__ */ new Map,g,_=()=>globalThis;class v extends Error{toolName;constructor(e,t){super(t),this.name=`ToolCallError`,this.toolName=e}}let y=e=>e instanceof Error?e.message:String(e),b=e=>{if(!(e instanceof Error)||typeof e.stack!=`string`)return;let t=e.stack.split(`
|
|
2
|
+
`),n=[t[0]??``];for(let e of t.slice(1))if(n.push(e),n.length>=6)break;return n.join(`
|
|
3
|
+
`)},x=(e,t=l)=>{if(!t)return!1;try{return t.postMessage(e),!0}catch{return!1}},S=(e,t,n)=>{x({kind:r.log,level:e,text:t},n)},C=(e,t,n,i)=>{x(n===void 0?{kind:r.error,error:{kind:e,message:t}}:{kind:r.error,error:{kind:e,message:t,stack:n}},i)},w=e=>{let t=/* @__PURE__ */ Error(e===`timeout`?`PTC run timed out`:`PTC run was cancelled`);return t.name=`AbortError`,t},T=()=>{let e=/* @__PURE__ */ Error(`the PTC control channel closed before the call completed`);return e.name=`AbortError`,e},E=async()=>{if(f<p){f+=1;return}await new Promise((e,t)=>{m.push({resolve:e,reject:t})})},D=()=>{let e=m.shift();if(e){e.resolve();return}--f},O=e=>{for(;m.length>0;){let t=m.shift();t&&t.reject(e)}},k=e=>{O(e);for(let[t,n]of h)h.delete(t),n.reject(e)},A=()=>{let e=t.workerData;if(typeof e!=`object`||!e)return;let n=e.env;if(typeof n!=`object`||!n||Array.isArray(n))return;let r=n;for(let e of Object.keys(process.env))e in r||delete process.env[e];for(let e of Object.keys(r)){let t=r[e];typeof t==`string`&&(process.env[e]=t)}},j=t=>{let n=/* @__PURE__ */ new Set,r=(t,i)=>{if(t===null)return{ok:!0,value:null};if(typeof t==`string`||typeof t==`boolean`)return{ok:!0,value:t};if(t===void 0)return{ok:!0,value:void 0};if(typeof t==`number`)return Number.isFinite(t)?{ok:!0,value:t}:{ok:!1,reason:`${i} is ${String(t)}, which is not representable as JSON`};if(typeof t!=`object`)return{ok:!1,reason:`${i} is ${e(t)}; results must be lossless JSON`};if(Array.isArray(t)){if(n.has(t))return{ok:!1,reason:`${i} is a circular reference`};n.add(t);let e=[];for(let n=0;n<t.length;n+=1){let a=r(t[n],`${i}[${n}]`);if(!a.ok)return a;e.push(a.value===void 0?null:a.value)}return n.delete(t),{ok:!0,value:e}}let a=Object.getPrototypeOf(t);if(a!==Object.prototype&&a!==null){let e=t.constructor;return{ok:!1,reason:`${i} is a ${e&&typeof e.name==`string`&&e.name.length>0?e.name:`an object`}; results must be plain JSON objects`}}if(n.has(t))return{ok:!1,reason:`${i} is a circular reference`};n.add(t);let o={};for(let e of Object.keys(t)){let n=r(t[e],`${i}.${e}`);if(!n.ok)return n;n.value!==void 0&&(o[e]=n.value)}return n.delete(t),{ok:!0,value:o}};return r(t,`result`)},M=e=>e.map(e=>typeof e==`string`?e:t.inspect(e,{depth:4,breakLength:1/0,colors:!1})).join(` `),N=e=>{let t=t=>(...n)=>{S(t,M(n),e)};_().console={log:t(i.log),info:t(i.info),warn:t(i.warn),error:t(i.error),debug:t(i.debug)}},P=()=>{typeof process.removeAllListeners==`function`&&typeof process.on==`function`&&(process.removeAllListeners(`warning`),process.on(`warning`,e=>{let t=typeof e==`object`&&e?e:void 0,n=t&&typeof t.name==`string`?t.name:`Warning`;if(n===`ExperimentalWarning`)return;let r=t&&typeof t.message==`string`?t.message:String(e);S(i.warn,`${n}: ${r}`)}))},F=e=>{let n=`async function ${o}() {\n${e}\n}`,r=n;try{r=t.stripTypes(n,{mode:`strip`})}catch{r=n}return Function(`${r}\nreturn ${o}();`)},I=async(e,t,n)=>{try{let t;try{t=F(e)}catch(e){C(a.exception,`program failed to compile: ${y(e)}`,b(e),n);return}try{let e=await t(),i=j(e);if(!i.ok){C(a.invalidOutput,i.reason,void 0,n);return}x(i.value===void 0?{kind:r.result}:{kind:r.result,value:i.value},n)}catch(e){C(u??a.exception,y(e),b(e),n)}}finally{await W(t,n)}},L=(e,t)=>async n=>{if(u)throw w(u);await E();try{if(u)throw w(u);let i=d;d+=1;let a=new Promise((e,t)=>{h.set(i,{resolve:e,reject:t})});if(!x({kind:r.call,callId:i,tool:e,args:n},t))throw h.delete(i),new v(e,`${e}() could not be called: the arguments are not transferable or the run has ended`);return await a}finally{D()}},R=e=>{let t=e.callId;if(typeof t!=`number`)return;let n=h.get(t);if(n){if(h.delete(t),e.ok===!0){n.resolve(e.value);return}n.reject(new v(typeof e.tool==`string`?e.tool:`unknown`,typeof e.message==`string`?e.message:`binding call failed`))}},z=e=>{u=e,k(w(e))},B=(e,t)=>{u=void 0;let n=w(e);C(e,n.message,void 0,t)},V=(e,t,n)=>{if(t>n)throw RangeError(`${e}() received ${t} items, exceeding maxItemsPerCall (${n})`)},H=(t,n,i)=>{let a=_();a.args=t===void 0?null:t,a.log=t=>{if(typeof t!=`string`)throw TypeError(`log(message) expects a string, received ${e(t)}`);x({kind:r.narration,message:t},i)},a.phase=t=>{if(typeof t!=`string`)throw TypeError(`phase(title) expects a string, received ${e(t)}`);x({kind:r.phase,title:t},i)},a.parallel=async t=>{if(!Array.isArray(t))throw TypeError(`parallel(thunks) expects an array of functions, received ${e(t)}`);V(`parallel`,t.length,n);for(let n=0;n<t.length;n+=1)if(typeof t[n]!=`function`)throw TypeError(`parallel(thunks) expects functions; item ${n} is ${e(t[n])}`);return await Promise.all(t.map(async e=>{try{return await e()}catch{return null}}))},a.pipeline=async(t,...r)=>{if(!Array.isArray(t))throw TypeError(`pipeline(items, ...stages) expects an array of items, received ${e(t)}`);if(V(`pipeline`,t.length,n),r.length===0)throw TypeError(`pipeline(items, ...stages) requires at least one stage function`);for(let t=0;t<r.length;t+=1)if(typeof r[t]!=`function`)throw TypeError(`pipeline(items, ...stages) expects stage functions; stage ${t} is ${e(r[t])}`);let i=r;return await Promise.all(t.map(async(e,t)=>{try{let n=e;for(let r of i)n=await r(n,e,t);return n}catch{return null}}))}},U=()=>{if(!g)return;let e=_();for(let t of Object.keys(e))g.has(t)||delete e[t];for(let[t,n]of g){let r=Object.getOwnPropertyDescriptor(e,t);if(r===void 0||r.writable!==!1)try{e[t]=n}catch{}}},W=async(e,t)=>{s===`RUNNING`&&e===c&&(k(T()),f=0,u=void 0,d=1,h.clear(),U(),A(),s=`READY`,x({kind:r.ready},t))},G=(e,t,n)=>{U(),u=void 0,f=0,h.clear(),A(),N(n),p=typeof e.maxPendingCalls==`number`&&Number.isFinite(e.maxPendingCalls)&&e.maxPendingCalls>0?e.maxPendingCalls:1/0;let r={},i=Array.isArray(e.bindings)?e.bindings.filter(e=>typeof e==`string`):[];for(let e of i)r[e]=L(e,n);let a=i.join(`, `)||`(none)`,o=Array.isArray(e.bindingCandidates)?e.bindingCandidates:[];for(let e of o)typeof e!=`string`||r[e]||(r[e]=async()=>{throw new v(e,`no binding named "${e}" in this run; available bindings: ${a}`)});if(_().tools=r,e.surface===`workflow`){let t=typeof e.maxItemsPerCall==`number`?e.maxItemsPerCall:0;H(e.args,t,n)}I(typeof e.code==`string`?e.code:``,t,n)},K=(e,t)=>{if(typeof e!=`object`||!e||Array.isArray(e))return;let r=e;if(r.kind===n.init){if(s!==`READY`)return;c+=1;let e=c;s=`RUNNING`,G(r,e,t);return}if(r.kind===n.callResult){R(r);return}if(r.kind===n.cancel){let e=r.reason===`timeout`?`timeout`:`abort`;if(s===`READY`){B(e,t);return}z(e)}};s=`BOOTING`,t.parentPort.on(`message`,e=>{if(typeof e!=`object`||!e)return;let t=e;if(t.kind!==n.connect)return;let i=t.port;if(typeof i!=`object`||!i||typeof i.on!=`function`)return;let a=i;l=a,a.on(`message`,e=>{K(e,a)}),a.on(`close`,()=>{l===a&&k(T())}),P();let o=g===void 0;if(o&&(g=new Map(Object.entries(_()))),s=`READY`,u!==void 0){if(o){B(u,a);return}u=void 0}x({kind:r.ready},a)})}o({parentPort:r,workerData:i,inspect:a,stripTypes:n,protocol:t()});export{};
|
|
759
4
|
//# sourceMappingURL=worker.js.map
|