@mono-agent/agent-runtime 0.15.3 → 0.15.4
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/README.md +43 -6
- package/package.json +5 -1
- package/src/agent/tools/agent-tool.js +859 -0
- package/src/agent/tools/bash.js +241 -123
- package/src/agent/tools/exec.js +238 -0
- package/src/agent/tools/index.js +10 -3
- package/src/agent/tools/node-repl.js +231 -95
- package/src/agent/tools/pi-bridge.js +115 -24
- package/src/agent/tools/shared/process-runner.js +162 -0
- package/src/agent/tools/shared/semaphore.js +73 -0
- package/src/agent/tools/web-browser-render.js +221 -0
- package/src/agent/tools/web-controller.js +160 -0
- package/src/agent/tools/web-fetch.js +653 -68
- package/src/agent/tools/web-search.js +568 -16
- package/src/ai/providers/pi-native/stream-subscriber.js +37 -0
- package/src/ai/providers/pi-native/turn-runner.js +60 -5
- package/src/ai/providers/pi-native.js +49 -5
- package/src/ai/runtime/router.js +302 -166
- package/src/ai/types.js +52 -1
- package/src/runtime.js +51 -1
- package/types/agent/tools/agent-tool.d.ts +60 -0
- package/types/agent/tools/bash.d.ts +55 -7
- package/types/agent/tools/exec.d.ts +53 -0
- package/types/agent/tools/index.d.ts +5 -3
- package/types/agent/tools/node-repl.d.ts +28 -3
- package/types/agent/tools/pi-bridge.d.ts +6 -2
- package/types/agent/tools/shared/process-runner.d.ts +33 -0
- package/types/agent/tools/shared/semaphore.d.ts +29 -0
- package/types/agent/tools/web-browser-render.d.ts +16 -0
- package/types/agent/tools/web-controller.d.ts +20 -0
- package/types/agent/tools/web-fetch.d.ts +74 -5
- package/types/agent/tools/web-search.d.ts +81 -5
- package/types/ai/providers/pi-native/turn-runner.d.ts +34 -2
- package/types/ai/providers/pi-native.d.ts +12 -0
- package/types/ai/runtime/router.d.ts +23 -3
- package/types/ai/types.d.ts +163 -1
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
4
5
|
import { resolve } from "node:path";
|
|
5
6
|
import { passthroughSandbox } from "../sandbox-seam.js";
|
|
6
7
|
import { capChars } from "./shared/output-truncation.js";
|
|
8
|
+
import { killProcessGroup } from "./shared/process-runner.js";
|
|
7
9
|
import { readToolRuntime } from "./shared/runtime-context.js";
|
|
8
10
|
import { resolveSandboxPolicy } from "./shared/tool-context.js";
|
|
9
11
|
|
|
10
12
|
const DEFAULT_NODE_REPL_TIMEOUT_MS = 120_000;
|
|
11
13
|
const NODE_REPL_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
14
|
+
const NODE_REPL_MAX_FRAME_BYTES = NODE_REPL_MAX_BUFFER_BYTES * 3;
|
|
12
15
|
const KILL_GRACE_MS = 1_000;
|
|
13
16
|
|
|
14
17
|
// Kept self-contained so the sandboxed child can start from `node --eval`
|
|
15
18
|
// without needing read access to agent-runtime's installed package directory.
|
|
16
|
-
function nodeReplWorkerMain() {
|
|
19
|
+
function nodeReplWorkerMain(frameToken) {
|
|
17
20
|
const repl = require("node:repl");
|
|
18
21
|
const { PassThrough } = require("node:stream");
|
|
19
22
|
const MAX_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
23
|
+
const MAX_FRAME_BYTES = MAX_BUFFER_BYTES * 3;
|
|
20
24
|
const input = new PassThrough();
|
|
21
25
|
const output = new PassThrough();
|
|
26
|
+
const protocolWrite = process.stdout.write.bind(process.stdout);
|
|
27
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
22
28
|
const server = repl.start({
|
|
23
29
|
input,
|
|
24
30
|
output,
|
|
@@ -28,10 +34,19 @@ function nodeReplWorkerMain() {
|
|
|
28
34
|
});
|
|
29
35
|
const replServer = /** @type {any} */ (server);
|
|
30
36
|
let active = null;
|
|
37
|
+
let protocolBuffer = Buffer.alloc(0);
|
|
38
|
+
|
|
39
|
+
function encodeFrame(message) {
|
|
40
|
+
const payload = Buffer.from(JSON.stringify(message), "utf8");
|
|
41
|
+
return Buffer.concat([
|
|
42
|
+
Buffer.from(`${frameToken}:${payload.length}\n`, "utf8"),
|
|
43
|
+
payload,
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
31
46
|
|
|
32
47
|
function send(message) {
|
|
33
48
|
try {
|
|
34
|
-
|
|
49
|
+
protocolWrite(encodeFrame(message));
|
|
35
50
|
} catch {
|
|
36
51
|
process.exit(1);
|
|
37
52
|
}
|
|
@@ -57,7 +72,7 @@ function nodeReplWorkerMain() {
|
|
|
57
72
|
ok: false,
|
|
58
73
|
reset: true,
|
|
59
74
|
text: `Node REPL output exceeded ${MAX_BUFFER_BYTES} bytes.`,
|
|
60
|
-
stdout: request.stdout,
|
|
75
|
+
stdout: request.stdout.slice(0, MAX_BUFFER_BYTES),
|
|
61
76
|
stderr: request.stderr,
|
|
62
77
|
});
|
|
63
78
|
setImmediate(() => process.exit(1));
|
|
@@ -94,8 +109,8 @@ function nodeReplWorkerMain() {
|
|
|
94
109
|
};
|
|
95
110
|
}
|
|
96
111
|
|
|
97
|
-
process.stdout.write = captureProcessWrite("stdout",
|
|
98
|
-
process.stderr.write = captureProcessWrite("stderr",
|
|
112
|
+
process.stdout.write = captureProcessWrite("stdout", protocolWrite);
|
|
113
|
+
process.stderr.write = captureProcessWrite("stderr", originalStderrWrite);
|
|
99
114
|
|
|
100
115
|
output.on("data", (chunk) => {
|
|
101
116
|
if (!active) return;
|
|
@@ -117,8 +132,7 @@ function nodeReplWorkerMain() {
|
|
|
117
132
|
finish(false, active.output.trimEnd() || "Node REPL evaluation failed.");
|
|
118
133
|
};
|
|
119
134
|
|
|
120
|
-
|
|
121
|
-
const requestMessage = /** @type {any} */ (message);
|
|
135
|
+
function evaluate(requestMessage) {
|
|
122
136
|
if (!requestMessage || requestMessage.type !== "evaluate") return;
|
|
123
137
|
if (active) {
|
|
124
138
|
send({ type: "result", id: requestMessage.id, ok: false, text: "Node REPL is already evaluating code." });
|
|
@@ -150,42 +164,69 @@ function nodeReplWorkerMain() {
|
|
|
150
164
|
} catch (error) {
|
|
151
165
|
finish(false, [active.output.trimEnd(), errorText(error)].filter(Boolean).join("\n"));
|
|
152
166
|
}
|
|
153
|
-
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function consumeFrames(chunk) {
|
|
170
|
+
protocolBuffer = Buffer.concat([protocolBuffer, chunk]);
|
|
171
|
+
if (protocolBuffer.length > MAX_FRAME_BYTES) process.exit(1);
|
|
172
|
+
while (protocolBuffer.length > 0) {
|
|
173
|
+
const newline = protocolBuffer.indexOf(0x0a);
|
|
174
|
+
if (newline < 0) return;
|
|
175
|
+
const header = protocolBuffer.subarray(0, newline).toString("utf8");
|
|
176
|
+
const prefix = `${frameToken}:`;
|
|
177
|
+
if (!header.startsWith(prefix)) process.exit(1);
|
|
178
|
+
const lengthText = header.slice(prefix.length);
|
|
179
|
+
if (!/^\d+$/.test(lengthText)) process.exit(1);
|
|
180
|
+
const length = Number(lengthText);
|
|
181
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > MAX_FRAME_BYTES) process.exit(1);
|
|
182
|
+
const frameEnd = newline + 1 + length;
|
|
183
|
+
if (protocolBuffer.length < frameEnd) return;
|
|
184
|
+
const payload = protocolBuffer.subarray(newline + 1, frameEnd);
|
|
185
|
+
protocolBuffer = protocolBuffer.subarray(frameEnd);
|
|
186
|
+
try {
|
|
187
|
+
evaluate(JSON.parse(payload.toString("utf8")));
|
|
188
|
+
} catch {
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
154
193
|
|
|
155
|
-
process.on("
|
|
194
|
+
process.stdin.on("data", consumeFrames);
|
|
195
|
+
process.stdin.on("end", () => {
|
|
156
196
|
server.close();
|
|
157
197
|
process.exit(0);
|
|
158
198
|
});
|
|
159
199
|
}
|
|
160
200
|
|
|
161
|
-
|
|
201
|
+
function workerSource(frameToken) {
|
|
202
|
+
return `(${nodeReplWorkerMain.toString()})(${JSON.stringify(frameToken)});`;
|
|
203
|
+
}
|
|
162
204
|
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
205
|
+
function encodeFrame(frameToken, message) {
|
|
206
|
+
const payload = Buffer.from(JSON.stringify(message), "utf8");
|
|
207
|
+
return Buffer.concat([
|
|
208
|
+
Buffer.from(`${frameToken}:${payload.length}\n`, "utf8"),
|
|
209
|
+
payload,
|
|
210
|
+
]);
|
|
170
211
|
}
|
|
171
212
|
|
|
172
213
|
function errorMessage(error) {
|
|
173
214
|
return error instanceof Error ? error.message : String(error);
|
|
174
215
|
}
|
|
175
216
|
|
|
176
|
-
function
|
|
217
|
+
function appendStderr(record, chunk) {
|
|
177
218
|
record.directOutputBytes += chunk.length;
|
|
178
219
|
if (record.directOutputBytes > NODE_REPL_MAX_BUFFER_BYTES) {
|
|
179
220
|
record.failureReason = `Node REPL output exceeded ${NODE_REPL_MAX_BUFFER_BYTES} bytes.`;
|
|
180
221
|
void terminateRecord(record);
|
|
181
222
|
return;
|
|
182
223
|
}
|
|
183
|
-
|
|
224
|
+
record.stderr.push(chunk);
|
|
184
225
|
}
|
|
185
226
|
|
|
186
227
|
function directOutput(record, capturedStdout = "", capturedStderr = "") {
|
|
187
228
|
const sections = [];
|
|
188
|
-
const stdout =
|
|
229
|
+
const stdout = String(capturedStdout || "").trimEnd();
|
|
189
230
|
const stderr = `${capturedStderr}${Buffer.concat(record.stderr).toString("utf8")}`.trimEnd();
|
|
190
231
|
if (stdout) sections.push(`STDOUT:\n${stdout}`);
|
|
191
232
|
if (stderr) sections.push(`STDERR:\n${stderr}`);
|
|
@@ -193,7 +234,6 @@ function directOutput(record, capturedStdout = "", capturedStderr = "") {
|
|
|
193
234
|
}
|
|
194
235
|
|
|
195
236
|
function clearRequestOutput(record) {
|
|
196
|
-
record.stdout = [];
|
|
197
237
|
record.stderr = [];
|
|
198
238
|
record.directOutputBytes = 0;
|
|
199
239
|
record.failureReason = null;
|
|
@@ -222,6 +262,55 @@ async function terminateRecord(record) {
|
|
|
222
262
|
await record.done;
|
|
223
263
|
}
|
|
224
264
|
|
|
265
|
+
function consumeWorkerFrames(record, chunk, onMessage) {
|
|
266
|
+
record.protocolBuffer = Buffer.concat([record.protocolBuffer, chunk]);
|
|
267
|
+
if (record.protocolBuffer.length > NODE_REPL_MAX_FRAME_BYTES) {
|
|
268
|
+
record.failureReason = "Node REPL protocol frame exceeded its byte limit.";
|
|
269
|
+
void terminateRecord(record);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
while (record.protocolBuffer.length > 0) {
|
|
273
|
+
const newline = record.protocolBuffer.indexOf(0x0a);
|
|
274
|
+
if (newline < 0) return;
|
|
275
|
+
const header = record.protocolBuffer.subarray(0, newline).toString("utf8");
|
|
276
|
+
const prefix = `${record.frameToken}:`;
|
|
277
|
+
if (!header.startsWith(prefix)) {
|
|
278
|
+
record.failureReason = "Node REPL protocol framing was corrupted.";
|
|
279
|
+
void terminateRecord(record);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const lengthText = header.slice(prefix.length);
|
|
283
|
+
if (!/^\d+$/.test(lengthText)) {
|
|
284
|
+
record.failureReason = "Node REPL protocol frame length was invalid.";
|
|
285
|
+
void terminateRecord(record);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const length = Number(lengthText);
|
|
289
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > NODE_REPL_MAX_FRAME_BYTES) {
|
|
290
|
+
record.failureReason = "Node REPL protocol frame length was out of range.";
|
|
291
|
+
void terminateRecord(record);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const frameEnd = newline + 1 + length;
|
|
295
|
+
if (record.protocolBuffer.length < frameEnd) return;
|
|
296
|
+
const payload = record.protocolBuffer.subarray(newline + 1, frameEnd);
|
|
297
|
+
record.protocolBuffer = record.protocolBuffer.subarray(frameEnd);
|
|
298
|
+
try {
|
|
299
|
+
onMessage(JSON.parse(payload.toString("utf8")));
|
|
300
|
+
} catch {
|
|
301
|
+
record.failureReason = "Node REPL protocol payload was invalid JSON.";
|
|
302
|
+
void terminateRecord(record);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function codedError(message, code) {
|
|
309
|
+
const error = /** @type {Error & {code?: string}} */ (new Error(message));
|
|
310
|
+
error.code = code;
|
|
311
|
+
return error;
|
|
312
|
+
}
|
|
313
|
+
|
|
225
314
|
/**
|
|
226
315
|
* One lazy Node REPL process owned by a single Pi run.
|
|
227
316
|
* @param {{cwd?: string, maxOutputChars?: number, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any}} [options]
|
|
@@ -243,27 +332,28 @@ export function createNodeReplController({
|
|
|
243
332
|
let nextRequestId = 0;
|
|
244
333
|
|
|
245
334
|
async function startChild() {
|
|
335
|
+
const frameToken = randomBytes(24).toString("hex");
|
|
246
336
|
const prepared = await sandbox.prepareCommand({
|
|
247
337
|
policy,
|
|
248
338
|
engine: sandboxEngine ?? resolvedCtx.sandboxEngine ?? undefined,
|
|
249
339
|
command: {
|
|
250
340
|
command: process.execPath,
|
|
251
|
-
args: ["--eval",
|
|
341
|
+
args: ["--eval", workerSource(frameToken)],
|
|
252
342
|
cwd: workdir,
|
|
253
343
|
},
|
|
254
344
|
});
|
|
255
345
|
if (permanentlyClosed) {
|
|
256
346
|
await prepared.cleanup?.();
|
|
257
|
-
throw
|
|
347
|
+
throw codedError("Node REPL run has already ended.", "closed");
|
|
258
348
|
}
|
|
259
349
|
|
|
260
350
|
let child;
|
|
261
351
|
try {
|
|
262
352
|
child = spawn(prepared.command, prepared.args || [], {
|
|
263
353
|
cwd: prepared.cwd,
|
|
264
|
-
detached:
|
|
354
|
+
detached: process.platform !== "win32",
|
|
265
355
|
env: prepared.env ? { ...process.env, ...prepared.env } : process.env,
|
|
266
|
-
stdio: ["
|
|
356
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
267
357
|
});
|
|
268
358
|
} catch (error) {
|
|
269
359
|
await prepared.cleanup?.();
|
|
@@ -277,43 +367,24 @@ export function createNodeReplController({
|
|
|
277
367
|
prepared,
|
|
278
368
|
done,
|
|
279
369
|
resolveDone,
|
|
370
|
+
frameToken,
|
|
371
|
+
protocolBuffer: Buffer.alloc(0),
|
|
280
372
|
closed: false,
|
|
281
373
|
cleaned: false,
|
|
282
374
|
killTimer: null,
|
|
283
375
|
spawnError: null,
|
|
284
376
|
failureReason: null,
|
|
285
377
|
pending: null,
|
|
286
|
-
stdout: [],
|
|
287
378
|
stderr: [],
|
|
288
379
|
directOutputBytes: 0,
|
|
289
380
|
};
|
|
290
381
|
current = record;
|
|
291
382
|
|
|
292
|
-
child.stdout?.on("data", (chunk) =>
|
|
293
|
-
|
|
294
|
-
child.once("error", (error) => { record.spawnError = error; });
|
|
295
|
-
child.on("message", (message) => {
|
|
296
|
-
const result = /** @type {any} */ (message);
|
|
297
|
-
const pending = record.pending;
|
|
298
|
-
if (!pending || !result || result.type !== "result" || result.id !== pending.id) return;
|
|
299
|
-
record.pending = null;
|
|
300
|
-
clearTimeout(pending.timeoutTimer);
|
|
301
|
-
pending.signal?.removeEventListener?.("abort", pending.onAbort);
|
|
302
|
-
setImmediate(async () => {
|
|
303
|
-
const text = resultText(record, result.text, result.stdout, result.stderr);
|
|
304
|
-
if (result.reset) await terminateRecord(record);
|
|
305
|
-
if (result.ok) {
|
|
306
|
-
pending.resolve(capChars(text || "(no output)", {
|
|
307
|
-
label: "NodeRepl",
|
|
308
|
-
maxChars: maxOutputChars,
|
|
309
|
-
strategy: "head_tail",
|
|
310
|
-
ctx: resolvedCtx,
|
|
311
|
-
}));
|
|
312
|
-
} else {
|
|
313
|
-
pending.reject(new Error(text || "Node REPL evaluation failed."));
|
|
314
|
-
}
|
|
315
|
-
});
|
|
383
|
+
child.stdout?.on("data", (chunk) => {
|
|
384
|
+
consumeWorkerFrames(record, chunk, (message) => handleWorkerMessage(record, message));
|
|
316
385
|
});
|
|
386
|
+
child.stderr?.on("data", (chunk) => appendStderr(record, chunk));
|
|
387
|
+
child.once("error", (error) => { record.spawnError = error; });
|
|
317
388
|
child.once("close", (code, closeSignal) => {
|
|
318
389
|
record.closed = true;
|
|
319
390
|
if (record.killTimer) clearTimeout(record.killTimer);
|
|
@@ -326,72 +397,137 @@ export function createNodeReplController({
|
|
|
326
397
|
const reason = record.failureReason
|
|
327
398
|
|| (record.spawnError ? errorMessage(record.spawnError) : null)
|
|
328
399
|
|| `Node REPL process exited before evaluation completed${closeSignal ? ` (${closeSignal})` : ` (code ${code ?? "unknown"})`}.`;
|
|
329
|
-
pending.reject(
|
|
400
|
+
pending.reject(codedError(`${reason} Session state was reset.`, "process_exit"));
|
|
330
401
|
}
|
|
331
402
|
void cleanupPrepared(record).finally(() => record.resolveDone());
|
|
332
403
|
});
|
|
333
404
|
return record;
|
|
334
405
|
}
|
|
335
406
|
|
|
407
|
+
function handleWorkerMessage(record, result) {
|
|
408
|
+
const pending = record.pending;
|
|
409
|
+
if (!pending || !result || result.type !== "result" || result.id !== pending.id) return;
|
|
410
|
+
record.pending = null;
|
|
411
|
+
clearTimeout(pending.timeoutTimer);
|
|
412
|
+
pending.signal?.removeEventListener?.("abort", pending.onAbort);
|
|
413
|
+
setImmediate(async () => {
|
|
414
|
+
const text = resultText(record, result.text, result.stdout, result.stderr);
|
|
415
|
+
if (result.reset) await terminateRecord(record);
|
|
416
|
+
if (result.ok) {
|
|
417
|
+
pending.resolve(capChars(text || "(no output)", {
|
|
418
|
+
label: "NodeRepl",
|
|
419
|
+
maxChars: maxOutputChars,
|
|
420
|
+
strategy: "head_tail",
|
|
421
|
+
ctx: resolvedCtx,
|
|
422
|
+
}));
|
|
423
|
+
} else {
|
|
424
|
+
pending.reject(codedError(text || "Node REPL evaluation failed.", result.reset ? "output_limit" : "evaluation_error"));
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
336
429
|
async function ensureChild() {
|
|
337
|
-
if (permanentlyClosed) throw
|
|
430
|
+
if (permanentlyClosed) throw codedError("Node REPL run has already ended.", "closed");
|
|
338
431
|
if (current && !current.closed) return current;
|
|
339
432
|
starting ??= startChild().finally(() => { starting = null; });
|
|
340
433
|
return await starting;
|
|
341
434
|
}
|
|
342
435
|
|
|
343
|
-
async function resetForFailure(record, pending, message) {
|
|
436
|
+
async function resetForFailure(record, pending, message, code) {
|
|
344
437
|
if (record.pending === pending) record.pending = null;
|
|
345
438
|
clearTimeout(pending.timeoutTimer);
|
|
346
439
|
pending.signal?.removeEventListener?.("abort", pending.onAbort);
|
|
347
440
|
await terminateRecord(record);
|
|
348
|
-
pending.reject(
|
|
441
|
+
pending.reject(codedError(`${message} Session state was reset.`, code));
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* @param {{code: string}} params
|
|
446
|
+
* @param {{signal?: AbortSignal}} [execution]
|
|
447
|
+
*/
|
|
448
|
+
async function execute({ code }, { signal } = {}) {
|
|
449
|
+
if (typeof code !== "string" || code.trim().length === 0) {
|
|
450
|
+
throw codedError("Node REPL code must not be empty.", "invalid_code");
|
|
451
|
+
}
|
|
452
|
+
if (signal?.aborted) throw codedError("Node REPL execution aborted.", "aborted");
|
|
453
|
+
const record = await ensureChild();
|
|
454
|
+
if (signal?.aborted) {
|
|
455
|
+
await terminateRecord(record);
|
|
456
|
+
throw codedError("Node REPL execution aborted. Session state was reset.", "aborted");
|
|
457
|
+
}
|
|
458
|
+
if (record.pending) throw codedError("Node REPL is already evaluating code.", "busy");
|
|
459
|
+
clearRequestOutput(record);
|
|
460
|
+
const id = `node-repl-${++nextRequestId}`;
|
|
461
|
+
|
|
462
|
+
return await new Promise((resolveResult, rejectResult) => {
|
|
463
|
+
const pending = {
|
|
464
|
+
id,
|
|
465
|
+
resolve: resolveResult,
|
|
466
|
+
reject: rejectResult,
|
|
467
|
+
signal,
|
|
468
|
+
onAbort: null,
|
|
469
|
+
timeoutTimer: null,
|
|
470
|
+
};
|
|
471
|
+
pending.onAbort = () => {
|
|
472
|
+
void resetForFailure(record, pending, "Node REPL execution aborted.", "aborted");
|
|
473
|
+
};
|
|
474
|
+
pending.timeoutTimer = setTimeout(() => {
|
|
475
|
+
void resetForFailure(
|
|
476
|
+
record,
|
|
477
|
+
pending,
|
|
478
|
+
`Node REPL execution timed out after ${DEFAULT_NODE_REPL_TIMEOUT_MS}ms.`,
|
|
479
|
+
"timeout",
|
|
480
|
+
);
|
|
481
|
+
}, DEFAULT_NODE_REPL_TIMEOUT_MS);
|
|
482
|
+
pending.timeoutTimer.unref?.();
|
|
483
|
+
record.pending = pending;
|
|
484
|
+
signal?.addEventListener?.("abort", pending.onAbort, { once: true });
|
|
485
|
+
const frame = encodeFrame(record.frameToken, { type: "evaluate", id, code });
|
|
486
|
+
record.child.stdin?.write(frame, (error) => {
|
|
487
|
+
if (error && record.pending === pending) {
|
|
488
|
+
void resetForFailure(record, pending, `Node REPL stream protocol failed: ${errorMessage(error)}.`, "protocol_error");
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
});
|
|
349
492
|
}
|
|
350
493
|
|
|
351
494
|
return {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
reject: rejectResult,
|
|
372
|
-
signal,
|
|
373
|
-
onAbort: null,
|
|
374
|
-
timeoutTimer: null,
|
|
495
|
+
execute,
|
|
496
|
+
|
|
497
|
+
/** Structured result used by the Pi bridge so telemetry does not depend on text prefixes. */
|
|
498
|
+
async executeDetailed(params, execution = {}) {
|
|
499
|
+
const startedAt = Date.now();
|
|
500
|
+
try {
|
|
501
|
+
const text = await execute(params, execution);
|
|
502
|
+
return {
|
|
503
|
+
text,
|
|
504
|
+
outcome: {
|
|
505
|
+
status: "ok",
|
|
506
|
+
code: "ok",
|
|
507
|
+
retryable: false,
|
|
508
|
+
attempts: 1,
|
|
509
|
+
durationMs: Date.now() - startedAt,
|
|
510
|
+
bytes: Buffer.byteLength(text, "utf8"),
|
|
511
|
+
truncated: String(text).includes("[truncated NodeRepl output"),
|
|
512
|
+
},
|
|
513
|
+
error: false,
|
|
375
514
|
};
|
|
376
|
-
|
|
377
|
-
|
|
515
|
+
} catch (error) {
|
|
516
|
+
const text = errorMessage(error);
|
|
517
|
+
return {
|
|
518
|
+
text,
|
|
519
|
+
outcome: {
|
|
520
|
+
status: "error",
|
|
521
|
+
code: typeof error?.code === "string" ? error.code : "evaluation_error",
|
|
522
|
+
retryable: false,
|
|
523
|
+
attempts: 1,
|
|
524
|
+
durationMs: Date.now() - startedAt,
|
|
525
|
+
bytes: Buffer.byteLength(text, "utf8"),
|
|
526
|
+
truncated: false,
|
|
527
|
+
},
|
|
528
|
+
error: true,
|
|
378
529
|
};
|
|
379
|
-
|
|
380
|
-
void resetForFailure(
|
|
381
|
-
record,
|
|
382
|
-
pending,
|
|
383
|
-
`Node REPL execution timed out after ${DEFAULT_NODE_REPL_TIMEOUT_MS}ms.`,
|
|
384
|
-
);
|
|
385
|
-
}, DEFAULT_NODE_REPL_TIMEOUT_MS);
|
|
386
|
-
pending.timeoutTimer.unref?.();
|
|
387
|
-
record.pending = pending;
|
|
388
|
-
signal?.addEventListener?.("abort", pending.onAbort, { once: true });
|
|
389
|
-
record.child.send({ type: "evaluate", id, code }, (error) => {
|
|
390
|
-
if (error && record.pending === pending) {
|
|
391
|
-
void resetForFailure(record, pending, `Node REPL IPC failed: ${errorMessage(error)}.`);
|
|
392
|
-
}
|
|
393
|
-
});
|
|
394
|
-
});
|
|
530
|
+
}
|
|
395
531
|
},
|
|
396
532
|
|
|
397
533
|
async close() {
|