poe-code 8.0.0 → 9.0.0

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.
Files changed (45) hide show
  1. package/dist/cli/commands/harness.js +12 -3
  2. package/dist/cli/commands/harness.js.map +1 -1
  3. package/dist/index.js +1515 -712
  4. package/dist/index.js.map +4 -4
  5. package/dist/metafile.json +1 -1
  6. package/package.json +15 -2
  7. package/packages/safejs/dist/chunks/chunk-4YIDX44G.js +40 -0
  8. package/packages/safejs/dist/chunks/chunk-4YIDX44G.js.map +7 -0
  9. package/packages/safejs/dist/chunks/chunk-55ZK3CR7.js +7802 -0
  10. package/packages/safejs/dist/chunks/chunk-55ZK3CR7.js.map +7 -0
  11. package/packages/safejs/dist/chunks/chunk-GQBEEFVQ.js +28798 -0
  12. package/packages/safejs/dist/chunks/chunk-GQBEEFVQ.js.map +7 -0
  13. package/packages/safejs/dist/chunks/chunk-NI4WFFTL.js +5701 -0
  14. package/packages/safejs/dist/chunks/chunk-NI4WFFTL.js.map +7 -0
  15. package/packages/safejs/dist/chunks/inMemory-RLZ4QGDZ.js +48 -0
  16. package/packages/safejs/dist/chunks/inMemory-RLZ4QGDZ.js.map +7 -0
  17. package/packages/safejs/dist/chunks/server-3SNT3RE2.js +12518 -0
  18. package/packages/safejs/dist/chunks/server-3SNT3RE2.js.map +7 -0
  19. package/packages/safejs/dist/chunks/types-BTF37PZ6.js +348 -0
  20. package/packages/safejs/dist/chunks/types-BTF37PZ6.js.map +7 -0
  21. package/packages/safejs/dist/cli.d.ts +2 -0
  22. package/packages/safejs/dist/cli.js +550 -491
  23. package/packages/safejs/dist/cli.js.map +7 -0
  24. package/packages/safejs/dist/core.js +14 -4
  25. package/packages/safejs/dist/core.js.map +7 -0
  26. package/packages/safejs/dist/index.d.ts +3 -0
  27. package/packages/safejs/dist/index.js +791 -30
  28. package/packages/safejs/dist/index.js.map +7 -0
  29. package/packages/safejs/dist/interp/host-bridge.d.ts +2 -0
  30. package/packages/safejs/dist/interp/host-bridge.js +20 -9
  31. package/packages/safejs/dist/interp/host-call.d.ts +5 -0
  32. package/packages/safejs/dist/interp/host-call.js +48 -3
  33. package/packages/safejs/dist/interp/resources.d.ts +7 -0
  34. package/packages/safejs/dist/interp/resources.js +36 -0
  35. package/packages/safejs/dist/modules/mcp-transport.d.ts +26 -0
  36. package/packages/safejs/dist/modules/mcp-transport.js +259 -0
  37. package/packages/safejs/dist/modules/mcp.d.ts +15 -1
  38. package/packages/safejs/dist/modules/mcp.js +107 -0
  39. package/packages/safejs/dist/run.js +6 -3
  40. package/packages/safejs/dist/snapshot/dump-format.d.ts +1 -1
  41. package/packages/safejs/dist/snapshot/dump-format.js +1 -1
  42. package/packages/safejs/dist/snapshot/replay-data.d.ts +1 -0
  43. package/packages/safejs/dist/snapshot/replay-data.js +3 -1
  44. package/packages/safejs/dist/snapshot/replay-inputs.d.ts +2 -2
  45. package/packages/safejs/dist/snapshot/replay-inputs.js +2 -1
@@ -0,0 +1,259 @@
1
+ import { spawn } from "node:child_process";
2
+ import path from "node:path";
3
+ import { HttpTransport, McpClient, StdioTransport } from "tiny-mcp-client";
4
+ export function parseMcpConfig(source, directory) {
5
+ const parsed = JSON.parse(source);
6
+ const options = normalizeMcpOptions(parsed, [
7
+ "servers",
8
+ "requestTimeoutMs",
9
+ "closeTimeoutMs",
10
+ "maxToolPages"
11
+ ]);
12
+ for (const server of Object.values(options.servers)) {
13
+ if ("command" in server) {
14
+ server.cwd = path.resolve(directory, server.cwd ?? ".");
15
+ if (server.command.includes("/") || server.command.includes("\\")) {
16
+ server.command = path.resolve(directory, server.command);
17
+ }
18
+ }
19
+ }
20
+ return options;
21
+ }
22
+ export function normalizeMcpOptions(value, keys = [
23
+ "servers",
24
+ "requestTimeoutMs",
25
+ "closeTimeoutMs",
26
+ "maxToolPages",
27
+ "signal",
28
+ "fetch",
29
+ "spawn"
30
+ ]) {
31
+ const options = readRecord(value, "MCP options", keys);
32
+ const servers = readRecord(options.servers, "MCP servers");
33
+ const normalized = Object.create(null);
34
+ for (const [name, input] of Object.entries(servers)) {
35
+ if (name.length === 0 || name.trim() !== name)
36
+ throw new TypeError("MCP server names must be non-empty without surrounding whitespace.");
37
+ const server = readRecord(input, `MCP server ${name}`);
38
+ if (Object.hasOwn(server, "command")) {
39
+ readRecord(server, `MCP server ${name}`, ["command", "args", "cwd", "env"]);
40
+ const command = readText(server.command, "MCP command");
41
+ if (server.args !== undefined &&
42
+ (!Array.isArray(server.args) ||
43
+ server.args.some((argument) => typeof argument !== "string")))
44
+ throw new TypeError("MCP args must be an array of strings.");
45
+ normalized[name] = {
46
+ command,
47
+ ...(server.args === undefined ? {} : { args: [...server.args] }),
48
+ ...(server.cwd === undefined ? {} : { cwd: readText(server.cwd, "MCP cwd") }),
49
+ env: readStrings(server.env ?? {}, "MCP environment")
50
+ };
51
+ }
52
+ else {
53
+ readRecord(server, `MCP server ${name}`, ["url", "headers"]);
54
+ const url = new URL(readText(server.url, "MCP URL"));
55
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash)
56
+ throw new TypeError("MCP URL must use HTTP or HTTPS without credentials or a fragment.");
57
+ const headers = readStrings(server.headers ?? {}, "MCP headers");
58
+ new Headers(headers);
59
+ normalized[name] = { url: url.href, headers };
60
+ }
61
+ }
62
+ for (const name of ["fetch", "spawn"])
63
+ if (options[name] !== undefined && typeof options[name] !== "function")
64
+ throw new TypeError(`MCP ${name} must be a function.`);
65
+ if (options.signal !== undefined && !(options.signal instanceof AbortSignal))
66
+ throw new TypeError("MCP signal must be an AbortSignal.");
67
+ return {
68
+ servers: normalized,
69
+ requestTimeoutMs: readTimeout(options.requestTimeoutMs, 30_000),
70
+ closeTimeoutMs: readTimeout(options.closeTimeoutMs, 1_000),
71
+ maxToolPages: readTimeout(options.maxToolPages, 100),
72
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
73
+ ...(options.fetch === undefined ? {} : { fetch: options.fetch }),
74
+ ...(options.spawn === undefined ? {} : { spawn: options.spawn })
75
+ };
76
+ }
77
+ function readRecord(value, label, keys) {
78
+ if (typeof value !== "object" ||
79
+ value === null ||
80
+ Array.isArray(value) ||
81
+ ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
82
+ throw new TypeError(`${label} must be a plain object.`);
83
+ const result = Object.create(null);
84
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) {
85
+ if (!("value" in descriptor) || (keys !== undefined && !keys.includes(key)))
86
+ throw new TypeError(`${label} contains unsupported field ${key}.`);
87
+ result[key] = descriptor.value;
88
+ }
89
+ return result;
90
+ }
91
+ function readText(value, label) {
92
+ if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0"))
93
+ throw new TypeError(`${label} must be a non-empty string without NUL characters.`);
94
+ return value;
95
+ }
96
+ function readStrings(value, label) {
97
+ const entries = readRecord(value, label);
98
+ for (const [name, entry] of Object.entries(entries))
99
+ if (typeof entry !== "string" || name.includes("\0") || entry.includes("\0"))
100
+ throw new TypeError(`${label} must contain strings without NUL characters.`);
101
+ return entries;
102
+ }
103
+ function readTimeout(value, fallback) {
104
+ if (value === undefined)
105
+ return fallback;
106
+ if (typeof value !== "number" ||
107
+ !Number.isSafeInteger(value) ||
108
+ value <= 0 ||
109
+ value > 2_147_483_647)
110
+ throw new TypeError("MCP timeouts must be positive 32-bit integers.");
111
+ return value;
112
+ }
113
+ export function connectMcpTransport(server, options, signal) {
114
+ signal?.throwIfAborted();
115
+ options.signal?.throwIfAborted();
116
+ const client = new McpClient({
117
+ clientInfo: { name: "safejs", version: "1" },
118
+ requestTimeoutMs: options.requestTimeoutMs
119
+ });
120
+ let child;
121
+ const activeRequests = new Set();
122
+ const transport = "command" in server
123
+ ? new StdioTransport({
124
+ ...server,
125
+ spawn(command, args, spawnOptions) {
126
+ child =
127
+ options.spawn === undefined
128
+ ? spawn(command, args, spawnOptions)
129
+ : options.spawn(command, args, spawnOptions);
130
+ return child;
131
+ }
132
+ })
133
+ : new HttpTransport({
134
+ ...server,
135
+ async fetch(input, init) {
136
+ const controller = new AbortController();
137
+ activeRequests.add(controller);
138
+ const cancel = () => controller.abort(init?.signal?.reason);
139
+ init?.signal?.addEventListener("abort", cancel, { once: true });
140
+ if (init?.signal?.aborted)
141
+ cancel();
142
+ const timeout = setTimeout(() => controller.abort(new Error("MCP HTTP request timed out.")), init?.method === "DELETE" ? options.closeTimeoutMs : options.requestTimeoutMs);
143
+ const finish = () => {
144
+ clearTimeout(timeout);
145
+ activeRequests.delete(controller);
146
+ init?.signal?.removeEventListener("abort", cancel);
147
+ };
148
+ try {
149
+ const response = await (options.fetch ?? fetch)(input, {
150
+ ...init,
151
+ signal: controller.signal,
152
+ redirect: "error"
153
+ });
154
+ if (response.body === null) {
155
+ finish();
156
+ return response;
157
+ }
158
+ const reader = response.body.getReader();
159
+ let stopReading;
160
+ const release = () => {
161
+ finish();
162
+ if (stopReading !== undefined)
163
+ controller.signal.removeEventListener("abort", stopReading);
164
+ };
165
+ const body = new ReadableStream({
166
+ start(stream) {
167
+ stopReading = () => {
168
+ void reader.cancel(controller.signal.reason).catch(() => undefined);
169
+ stream.error(controller.signal.reason);
170
+ release();
171
+ };
172
+ controller.signal.addEventListener("abort", stopReading, { once: true });
173
+ if (controller.signal.aborted)
174
+ stopReading();
175
+ },
176
+ async pull(stream) {
177
+ try {
178
+ const result = await reader.read();
179
+ if (controller.signal.aborted)
180
+ return;
181
+ if (result.done) {
182
+ release();
183
+ stream.close();
184
+ }
185
+ else
186
+ stream.enqueue(result.value);
187
+ }
188
+ catch (error) {
189
+ release();
190
+ stream.error(error);
191
+ }
192
+ },
193
+ async cancel(reason) {
194
+ release();
195
+ await reader.cancel(reason);
196
+ }
197
+ });
198
+ return new Response(body, {
199
+ status: response.status,
200
+ statusText: response.statusText,
201
+ headers: response.headers
202
+ });
203
+ }
204
+ catch (error) {
205
+ finish();
206
+ throw error;
207
+ }
208
+ }
209
+ });
210
+ let closing;
211
+ const cancel = () => {
212
+ void close().catch(() => undefined);
213
+ };
214
+ function close() {
215
+ closing ??= (async () => {
216
+ signal?.removeEventListener("abort", cancel);
217
+ options.signal?.removeEventListener("abort", cancel);
218
+ for (const request of activeRequests)
219
+ request.abort(new Error("MCP connection closed."));
220
+ await client.close();
221
+ transport.dispose();
222
+ await new Promise((resolve, reject) => {
223
+ let forced;
224
+ const timeout = setTimeout(() => {
225
+ if (child !== undefined && child.exitCode === null && child.signalCode === null)
226
+ child.kill("SIGKILL");
227
+ forced = setTimeout(() => reject(new Error("MCP transport did not close.")), options.closeTimeoutMs);
228
+ }, options.closeTimeoutMs);
229
+ void transport.closed.then(() => {
230
+ clearTimeout(timeout);
231
+ clearTimeout(forced);
232
+ resolve();
233
+ }, (error) => {
234
+ clearTimeout(timeout);
235
+ clearTimeout(forced);
236
+ reject(error);
237
+ });
238
+ });
239
+ })();
240
+ return closing;
241
+ }
242
+ signal?.addEventListener("abort", cancel, { once: true });
243
+ options.signal?.addEventListener("abort", cancel, { once: true });
244
+ const aborted = signal?.aborted || options.signal?.aborted;
245
+ if (aborted)
246
+ cancel();
247
+ const connecting = aborted
248
+ ? Promise.reject(signal?.aborted ? signal.reason : options.signal?.reason)
249
+ : client.connect(transport);
250
+ const ready = connecting.then(() => client, async (error) => {
251
+ await close().catch(() => undefined);
252
+ throw signal?.aborted
253
+ ? signal.reason
254
+ : options.signal?.aborted
255
+ ? options.signal.reason
256
+ : error;
257
+ });
258
+ return { ready, close };
259
+ }
@@ -1,3 +1,4 @@
1
+ import { type McpModuleOptions } from "./mcp-transport.js";
1
2
  export type McpModuleServerHandle = {
2
3
  command: string;
3
4
  args?: string[];
@@ -42,8 +43,21 @@ type McpConnection = {
42
43
  }>): Promise<McpModuleToolBatchResult[]>;
43
44
  };
44
45
  export type ConnectMcp = (server: McpModuleServerHandle) => Promise<McpConnection> | McpConnection;
45
- export declare function makeMcpModule(connectMcp: ConnectMcp): {
46
+ export type McpNamedServerHandle = {
47
+ name: string;
48
+ };
49
+ export type ManagedMcpClient = McpModuleClient & {
50
+ close(): Promise<void>;
51
+ };
52
+ export type ManagedMcpModule = {
53
+ servers: Record<string, ManagedMcpClient>;
54
+ server(name: string): McpNamedServerHandle;
55
+ client(handle: McpNamedServerHandle | string): Promise<ManagedMcpClient>;
56
+ };
57
+ type CustomMcpModule = {
46
58
  server(server: McpModuleServerHandle): McpModuleServerHandle;
47
59
  client(handle: McpModuleServerHandle): Promise<McpModuleClient>;
48
60
  };
61
+ export declare function makeMcpModule(options: McpModuleOptions): ManagedMcpModule;
62
+ export declare function makeMcpModule(connectMcp: ConnectMcp): CustomMcpModule;
49
63
  export {};
@@ -1,5 +1,112 @@
1
+ import { McpError } from "tiny-mcp-client";
2
+ import { hostErrorData } from "../error/shape.js";
3
+ import { declareHostOperation } from "../interp/host-bridge.js";
4
+ import { runResources } from "../interp/resources.js";
5
+ import { connectMcpTransport, normalizeMcpOptions } from "./mcp-transport.js";
1
6
  const DEFAULT_TOOL_BATCH_CONCURRENCY = 4;
2
7
  export function makeMcpModule(connectMcp) {
8
+ if (typeof connectMcp !== "function") {
9
+ const options = normalizeMcpOptions(connectMcp);
10
+ const servers = Object.create(null);
11
+ for (const [name, config] of Object.entries(options.servers)) {
12
+ const connections = new WeakMap();
13
+ const directScope = {};
14
+ const connection = () => {
15
+ const scope = runResources.getStore();
16
+ scope?.signal.throwIfAborted();
17
+ options.signal?.throwIfAborted();
18
+ const owner = scope ?? directScope;
19
+ let current = connections.get(owner);
20
+ if (current === undefined) {
21
+ current = connectMcpTransport(config, options, scope?.signal);
22
+ connections.set(owner, current);
23
+ scope?.add(current.close);
24
+ }
25
+ return current.ready;
26
+ };
27
+ const invoke = async (operation) => {
28
+ try {
29
+ const result = await operation(await connection());
30
+ runResources.getStore()?.signal.throwIfAborted();
31
+ options.signal?.throwIfAborted();
32
+ return result;
33
+ }
34
+ catch (error) {
35
+ runResources.getStore()?.signal.throwIfAborted();
36
+ options.signal?.throwIfAborted();
37
+ if (error instanceof McpError) {
38
+ hostErrorData.set(error, {
39
+ code: error.code,
40
+ ...(error.data === undefined ? {} : { data: error.data })
41
+ });
42
+ }
43
+ throw error;
44
+ }
45
+ };
46
+ servers[name] = {
47
+ tools: declareHostOperation(async () => invoke(async (client) => {
48
+ const tools = [];
49
+ const cursors = new Set();
50
+ let pages = 0;
51
+ let cursor;
52
+ do {
53
+ const result = await client.listTools(cursor === undefined ? undefined : { cursor });
54
+ pages += 1;
55
+ tools.push(...normalizeToolsResult(result));
56
+ cursor = result.nextCursor;
57
+ if (cursor !== undefined) {
58
+ if (typeof cursor !== "string" || cursors.has(cursor))
59
+ throw new Error("MCP tools pagination did not advance.");
60
+ cursors.add(cursor);
61
+ if (pages >= options.maxToolPages)
62
+ throw new Error("MCP tools page limit exceeded.");
63
+ }
64
+ } while (cursor !== undefined);
65
+ return tools;
66
+ }), "re-issue"),
67
+ tool: declareHostOperation(async (toolName, args) => {
68
+ const request = createNullRecord({
69
+ name: readNonEmptyTrimmedString(toolName, "MCP tool name"),
70
+ ...(args === undefined ? {} : { arguments: readToolArguments(args) })
71
+ });
72
+ return invoke((client) => client.callTool(request));
73
+ }, "read-side-effect"),
74
+ toolBatch: declareHostOperation(async (calls) => {
75
+ const normalized = readToolBatchCalls(calls);
76
+ if (normalized.length === 0)
77
+ return [];
78
+ return invoke((client) => executeToolBatch(client, normalized));
79
+ }, "read-side-effect"),
80
+ close: declareHostOperation(async () => {
81
+ const owner = runResources.getStore() ?? directScope;
82
+ const current = connections.get(owner);
83
+ if (current !== undefined) {
84
+ await current.close();
85
+ connections.delete(owner);
86
+ }
87
+ }, "re-issue")
88
+ };
89
+ }
90
+ const readName = (value) => {
91
+ const name = readNonEmptyString(value, "MCP server name");
92
+ if (!Object.hasOwn(servers, name))
93
+ throw new Error(`MCP server '${name}' is not configured.`);
94
+ return name;
95
+ };
96
+ return {
97
+ servers,
98
+ server(name) {
99
+ return createNullRecord({ name: readName(name) });
100
+ },
101
+ async client(handle) {
102
+ if (typeof handle === "string")
103
+ return servers[readName(handle)];
104
+ if (!isRecord(handle) || Object.keys(handle).length !== 1 || !Object.hasOwn(handle, "name"))
105
+ throw new TypeError("MCP client requires a named server handle.");
106
+ return servers[readName(getOwnProperty(handle, "name"))];
107
+ }
108
+ };
109
+ }
3
110
  return {
4
111
  server(server) {
5
112
  return normalizeServerHandle(server);
@@ -1,4 +1,5 @@
1
1
  import { hashSource } from "./parse/hash.js";
2
+ import { withRunResources } from "./interp/resources.js";
2
3
  import { createReplayableRandom } from "./random.js";
3
4
  import { attachErrorSpan, describeThrownValue, materializeWrappedErrorCause, replaceErrorStack } from "./error/shape.js";
4
5
  import { parseExecutableModule } from "./parse/parser.js";
@@ -43,7 +44,7 @@ export function run(source, options = {}) {
43
44
  const lifecycle = { hostCallbackDepth: 0 };
44
45
  const dumpController = createDumpController(lifecycle);
45
46
  const promiseTracker = createSandboxPromiseRejectionTracker();
46
- const result = withSandboxPromiseRejectionTracker(promiseTracker, async () => {
47
+ const execute = async () => {
47
48
  const promiseReplay = new PromiseReplay(options.snapshot?.promiseReplay);
48
49
  return promiseReplayContext.run(promiseReplay, async () => {
49
50
  const deactivateOtelSink = activateOtelSink(options.otelSink);
@@ -140,7 +141,8 @@ export function run(source, options = {}) {
140
141
  if (!isSandboxClosure(binding))
141
142
  throw new TypeError("Invalid initial promise operation.");
142
143
  return binding.call([]);
143
- });
144
+ }, hostCalls.rebindHostCapability.bind(hostCalls));
145
+ hostCalls.validateHostCapabilities();
144
146
  budget.setRetainedDataUsage(initialInputs, measureSandboxData([
145
147
  ...Object.values(initialInputs.values.bindings),
146
148
  ...Object.values(initialInputs.values.imports),
@@ -310,7 +312,8 @@ export function run(source, options = {}) {
310
312
  deactivateOtelSink();
311
313
  }
312
314
  });
313
- });
315
+ };
316
+ const result = withRunResources(options.signal, () => withSandboxPromiseRejectionTracker(promiseTracker, execute));
314
317
  return attachDumpController(result, dumpController);
315
318
  }
316
319
  async function throwIfReturnedPromiseRejected(result) {
@@ -1,5 +1,5 @@
1
1
  export declare const DUMP_FORMAT_VERSION = 1;
2
- export declare const EXECUTION_SEMANTICS = "jobs-v4";
2
+ export declare const EXECUTION_SEMANTICS = "jobs-v5";
3
3
  export type DumpableSnapshot = {
4
4
  sourceHash: string;
5
5
  [key: string]: unknown;
@@ -1,5 +1,5 @@
1
1
  export const DUMP_FORMAT_VERSION = 1;
2
- export const EXECUTION_SEMANTICS = "jobs-v4";
2
+ export const EXECUTION_SEMANTICS = "jobs-v5";
3
3
  import { assertSnapshotGraphDepth } from "../graph-depth.js";
4
4
  import { sandboxErrorTypes } from "../error/shape.js";
5
5
  import { getSandboxArgumentEntries, isSandboxArguments } from "../interp/arguments.js";
@@ -61,5 +61,6 @@ export declare function encodeReplayData(value: SandboxValue, options?: {
61
61
  export declare function decodeReplayData(input: unknown, options?: {
62
62
  resolveCapability?: (id: string) => SandboxClosure | undefined;
63
63
  resolvePromise?: (id: string) => SandboxPromise | undefined;
64
+ onCapabilityRestored?: (original: SandboxClosure, restored: SandboxClosure) => void;
64
65
  }): SandboxValue;
65
66
  export {};
@@ -185,7 +185,7 @@ export function decodeReplayData(input, options = {}) {
185
185
  restored.set(id, capability);
186
186
  return capability;
187
187
  }
188
- return createSandboxClosure({
188
+ const copy = createSandboxClosure({
189
189
  ...capability,
190
190
  boundTarget: capability.boundTarget,
191
191
  cancellationSignal: capability.cancellationSignal,
@@ -202,6 +202,8 @@ export function decodeReplayData(input, options = {}) {
202
202
  return properties;
203
203
  }
204
204
  });
205
+ options.onCapabilityRestored?.(capability, copy);
206
+ return copy;
205
207
  }
206
208
  if (kind === "map") {
207
209
  const result = createSandboxMap();
@@ -1,4 +1,4 @@
1
- import { type SandboxPromise, type SandboxValue } from "../interp/values.js";
1
+ import { type SandboxClosure, type SandboxPromise, type SandboxValue } from "../interp/values.js";
2
2
  import { type ReplayData } from "./replay-data.js";
3
3
  export type ReplayInputs = {
4
4
  bindings: Record<string, SandboxValue>;
@@ -6,7 +6,7 @@ export type ReplayInputs = {
6
6
  entryPointArgs: SandboxValue[] | undefined;
7
7
  importMeta: SandboxValue;
8
8
  };
9
- export declare function prepareReplayInputs(current: ReplayInputs, saved?: unknown, preparePromise?: (promise: SandboxPromise | undefined, id: string) => SandboxPromise): {
9
+ export declare function prepareReplayInputs(current: ReplayInputs, saved?: unknown, preparePromise?: (promise: SandboxPromise | undefined, id: string) => SandboxPromise, onCapabilityRestored?: (original: SandboxClosure, restored: SandboxClosure) => void): {
10
10
  values: ReplayInputs;
11
11
  snapshot: ReplayData;
12
12
  };
@@ -1,7 +1,7 @@
1
1
  import { createSandboxPromise, isSandboxClosure, isSandboxMap, isSandboxSet, isSandboxPromise } from "../interp/values.js";
2
2
  import { decodeReplayData, encodeReplayData } from "./replay-data.js";
3
3
  const validationPromise = createSandboxPromise(Promise.resolve(undefined));
4
- export function prepareReplayInputs(current, saved, preparePromise) {
4
+ export function prepareReplayInputs(current, saved, preparePromise, onCapabilityRestored) {
5
5
  const identities = new WeakMap();
6
6
  const capabilities = new Map();
7
7
  const promises = new Map();
@@ -81,6 +81,7 @@ export function prepareReplayInputs(current, saved, preparePromise) {
81
81
  assertReplayInputShape(validated);
82
82
  const restored = decodeReplayData(saved, {
83
83
  resolveCapability,
84
+ onCapabilityRestored,
84
85
  resolvePromise: (id) => {
85
86
  const value = readCapability(id);
86
87
  if (!promises.has(id) && preparePromise !== undefined)