arisa 5.1.66 → 5.2.7
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 +7 -4
- package/package.json +1 -1
- package/src/core/agent/agent-manager.js +46 -6
- package/src/core/agent/agent-session-lifecycle.js +80 -3
- package/src/core/agent/core-tools.js +1 -1
- package/src/core/agent/pi-auth-login.js +1 -1
- package/src/core/agent/pi-runtime.js +1 -1
- package/src/core/agent/runtime-context.js +1 -1
- package/src/core/agent/worker-heap-circuit-breaker.js +122 -0
- package/src/core/artifacts/artifact-store.js +1 -1
- package/src/core/capabilities/capability-service.js +1 -1
- package/src/core/config/config-defaults.js +30 -1
- package/src/core/config/config-store.js +1 -1
- package/src/core/conversation/session-seed-store.js +1 -1
- package/src/core/tasks/task-store.js +1 -1
- package/src/core/tools/daemon-client.js +180 -0
- package/src/core/tools/daemon-health.js +5 -2
- package/src/core/tools/daemon-processes.js +19 -3
- package/src/core/tools/daemon-protocol.js +72 -0
- package/src/core/tools/daemon-runtime.js +13 -490
- package/src/core/tools/daemon-worker.js +310 -0
- package/src/core/tools/ipc-client.js +2 -2
- package/src/core/tools/memory-pressure.js +56 -0
- package/src/core/tools/official-tool-installer.js +1 -1
- package/src/core/tools/tool-config.js +1 -1
- package/src/core/tools/tool-process-output.js +100 -0
- package/src/core/tools/tool-process-runner.js +175 -0
- package/src/core/tools/tool-registry.js +130 -186
- package/src/core/tools/tool-resource-note-store.js +1 -1
- package/src/core/tools/tool-usage-store.js +1 -1
- package/src/core/tools/weighted-resource-governor.js +192 -38
- package/src/index.js +14 -2
- package/src/official-tools.lock.json +430 -60
- package/src/platform/paths.js +152 -0
- package/src/runtime/bootstrap-cli.js +121 -0
- package/src/runtime/bootstrap-config.js +97 -0
- package/src/runtime/bootstrap-telegram.js +325 -0
- package/src/runtime/bootstrap.js +6 -543
- package/src/runtime/doctor.js +6 -3
- package/src/runtime/flush.js +1 -1
- package/src/runtime/ipc/ipc-server.js +1 -1
- package/src/runtime/log-viewer.js +1 -1
- package/src/runtime/oom-protection.js +20 -0
- package/src/runtime/paths.js +3 -151
- package/src/runtime/restart-receipt.js +1 -1
- package/src/runtime/service-manager.js +1 -1
- package/src/runtime/service-supervisor.js +14 -0
- package/src/runtime/slave-cli.js +2 -1
- package/src/runtime/tool-process-supervisor.js +1 -1
- package/src/runtime/tui.js +200 -0
- package/src/runtime/update-manager.js +1 -1
- package/src/runtime/worker-recovery-report.js +142 -0
- package/src/transport/telegram/bot.js +42 -320
- package/src/transport/telegram/prompt-builders.js +8 -3
- package/src/transport/telegram/telegram-prompt-controller.js +346 -0
- package/src/transport/telegram/workspace-topic-store.js +1 -1
- package/test/agent-session-lifecycle.test.js +92 -0
- package/test/architecture-boundaries.test.js +29 -0
- package/test/bootstrap.test.js +65 -0
- package/test/daemon-process-invocation.test.js +27 -0
- package/test/daemon-runtime.test.js +56 -1
- package/test/doctor.test.js +22 -0
- package/test/memory-pressure.test.js +36 -0
- package/test/model-selection.test.js +11 -1
- package/test/official-tool-dependencies.test.js +1 -1
- package/test/official-tool-installer.test.js +18 -1
- package/test/oom-protection.test.js +32 -0
- package/test/paths.test.js +7 -0
- package/test/pi-compaction.test.js +9 -0
- package/test/service-manager.test.js +6 -1
- package/test/slave-cli.test.js +20 -1
- package/test/telegram-prompt-controller.test.js +81 -0
- package/test/telegram-text-artifact.test.js +30 -0
- package/test/tool-registry-run.test.js +147 -4
- package/test/tui.test.js +41 -0
- package/test/weighted-resource-governor.test.js +103 -5
- package/test/worker-heap-circuit-breaker.test.js +79 -0
- package/test/worker-recovery-report.test.js +69 -0
- package/test-fixtures/fake-daemon.js +5 -0
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
import { mkdir, readdir, readFile, rmdir, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { arisaIpcSocketFile, arisaPackageDir, getToolConfigPath, getToolStateDir, getToolTmpDir, getChatToolTmpDir, toolsDir as userToolsRoot } from "../../runtime/paths.js";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import { getToolConfigPath, getToolStateDir, getToolTmpDir, getChatToolTmpDir, toolsDir as userToolsRoot } from "../../platform/paths.js";
|
|
6
5
|
import { loadToolConfig, parseConfigModule, writeToolConfig } from "./tool-config.js";
|
|
7
6
|
import { normalizeToolResult } from "./tool-result.js";
|
|
8
7
|
import { readDaemonDiagnostic } from "./daemon-processes.js";
|
|
9
|
-
import { createDaemonRuntime
|
|
8
|
+
import { createDaemonRuntime } from "./daemon-runtime.js";
|
|
10
9
|
import { daemonConfigDefaults } from "../config/config-defaults.js";
|
|
11
10
|
import { SkillRegistry } from "../skills/skill-registry.js";
|
|
12
11
|
import { ToolUsageStore } from "./tool-usage-store.js";
|
|
13
12
|
import { inspectToolDependencies, normalizeToolDependencies } from "./tool-dependencies.js";
|
|
14
13
|
import { normalizeToolExecution, WeightedResourceGovernor } from "./weighted-resource-governor.js";
|
|
14
|
+
import {
|
|
15
|
+
isolatedToolProcessInvocation,
|
|
16
|
+
runToolHelpProcess,
|
|
17
|
+
runToolProcess,
|
|
18
|
+
toolProcessEnv
|
|
19
|
+
} from "./tool-process-runner.js";
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
21
|
+
export { createToolOutputParser } from "./tool-process-output.js";
|
|
22
|
+
export { isolatedToolProcessInvocation } from "./tool-process-runner.js";
|
|
19
23
|
|
|
20
24
|
const defaultToolHelpTimeoutMs = 10_000;
|
|
21
25
|
const defaultToolRunTimeoutMs = 30 * 60_000;
|
|
@@ -25,43 +29,28 @@ function positiveDuration(value, fallback) {
|
|
|
25
29
|
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
forceTimer = setTimeout(() => child.kill("SIGKILL"), killGraceMs);
|
|
36
|
-
}, timeoutMs);
|
|
37
|
-
|
|
38
|
-
const finish = (callback, value) => {
|
|
39
|
-
clearTimeout(timeout);
|
|
40
|
-
clearTimeout(forceTimer);
|
|
41
|
-
callback(value);
|
|
42
|
-
};
|
|
32
|
+
function canonicalRequestValue(value) {
|
|
33
|
+
if (Array.isArray(value)) return value.map(canonicalRequestValue);
|
|
34
|
+
if (value && typeof value === "object") {
|
|
35
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalRequestValue(value[key])]));
|
|
36
|
+
}
|
|
37
|
+
return value === undefined ? null : value;
|
|
38
|
+
}
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
finish(resolve, code);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const error = new Error(`${label} timed out after ${timeoutMs}ms`);
|
|
51
|
-
error.code = "TOOL_PROCESS_TIMEOUT";
|
|
52
|
-
finish(reject, error);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
40
|
+
function concurrentExecutionKey(name, chatId, request) {
|
|
41
|
+
const serialized = JSON.stringify(canonicalRequestValue({ name, chatId: chatId == null ? null : String(chatId), request }));
|
|
42
|
+
return createHash("sha256").update(serialized).digest("hex");
|
|
55
43
|
}
|
|
56
44
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
45
|
+
function executionForLease(execution, lease) {
|
|
46
|
+
if (!execution) return null;
|
|
47
|
+
return {
|
|
48
|
+
...execution,
|
|
49
|
+
maxHeapMb: lease.heapLimitMb || execution.maxHeapMb,
|
|
50
|
+
maxMemoryMb: lease.memoryLimitMb || execution.maxMemoryMb,
|
|
51
|
+
memoryHighPercent: lease.memoryHighPercent,
|
|
52
|
+
swapMaxMb: lease.swapMaxMb
|
|
53
|
+
};
|
|
65
54
|
}
|
|
66
55
|
|
|
67
56
|
function requirementNames(requirements) {
|
|
@@ -72,123 +61,6 @@ function requirementNames(requirements) {
|
|
|
72
61
|
return [];
|
|
73
62
|
}
|
|
74
63
|
|
|
75
|
-
export function createToolOutputParser(name, { onEvent, maxFrameBytes = 1_048_576 } = {}) {
|
|
76
|
-
let buffer = "";
|
|
77
|
-
let mode = "unknown";
|
|
78
|
-
let rawOutput = "";
|
|
79
|
-
let terminalResult = null;
|
|
80
|
-
let activeJobId = null;
|
|
81
|
-
let sequence = 0;
|
|
82
|
-
let terminalSeen = false;
|
|
83
|
-
|
|
84
|
-
async function parseEvent(line) {
|
|
85
|
-
let event;
|
|
86
|
-
try {
|
|
87
|
-
event = JSON.parse(line);
|
|
88
|
-
} catch {
|
|
89
|
-
throw new Error(`Invalid NDJSON from ${name}`);
|
|
90
|
-
}
|
|
91
|
-
if (event?.version !== DAEMON_PROTOCOL_VERSION || !DAEMON_EVENT_TYPES.includes(event?.type)) {
|
|
92
|
-
throw new Error(`Invalid versioned tool event from ${name}`);
|
|
93
|
-
}
|
|
94
|
-
if (typeof event.jobId !== "string" || !event.jobId) throw new Error(`Tool event from ${name} is missing jobId`);
|
|
95
|
-
if (activeJobId == null) activeJobId = event.jobId;
|
|
96
|
-
if (event.jobId !== activeJobId) throw new Error(`Tool ${name} multiplexed an unexpected jobId`);
|
|
97
|
-
if (!Number.isSafeInteger(event.sequence) || event.sequence !== sequence + 1) {
|
|
98
|
-
throw new Error(`Invalid tool event sequence from ${name}: ${event.sequence}`);
|
|
99
|
-
}
|
|
100
|
-
if (terminalSeen) throw new Error(`Tool ${name} emitted more than one terminal event`);
|
|
101
|
-
sequence = event.sequence;
|
|
102
|
-
terminalSeen = event.type === "completed" || event.type === "failed";
|
|
103
|
-
await onEvent?.(event);
|
|
104
|
-
if (terminalSeen) {
|
|
105
|
-
terminalResult = event.type === "completed"
|
|
106
|
-
? event.payload?.result ?? event.payload?.output ?? event.payload
|
|
107
|
-
: { ok: false, error: event.payload?.error || `Tool failed: ${name}`, ...(event.payload?.code ? { code: event.payload.code } : {}) };
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async function consumeLine(line) {
|
|
112
|
-
if (Buffer.byteLength(line, "utf8") > maxFrameBytes) throw new Error(`Tool event from ${name} exceeds ${maxFrameBytes} bytes`);
|
|
113
|
-
if (mode === "unknown") {
|
|
114
|
-
let candidate;
|
|
115
|
-
try {
|
|
116
|
-
candidate = JSON.parse(line);
|
|
117
|
-
} catch {
|
|
118
|
-
mode = "legacy";
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (candidate?.version === DAEMON_PROTOCOL_VERSION && DAEMON_EVENT_TYPES.includes(candidate?.type)) {
|
|
122
|
-
mode = "ndjson";
|
|
123
|
-
rawOutput = "";
|
|
124
|
-
return parseEvent(line);
|
|
125
|
-
}
|
|
126
|
-
mode = "legacy";
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (mode === "legacy") return;
|
|
130
|
-
return parseEvent(line);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return {
|
|
134
|
-
async push(chunk) {
|
|
135
|
-
const text = chunk.toString("utf8");
|
|
136
|
-
if (mode !== "ndjson") rawOutput += text;
|
|
137
|
-
if (mode === "legacy") return;
|
|
138
|
-
buffer += text;
|
|
139
|
-
if (Buffer.byteLength(buffer, "utf8") > maxFrameBytes && !buffer.includes("\n")) {
|
|
140
|
-
throw new Error(`Tool event from ${name} exceeds ${maxFrameBytes} bytes`);
|
|
141
|
-
}
|
|
142
|
-
let newlineIndex = buffer.indexOf("\n");
|
|
143
|
-
while (newlineIndex !== -1) {
|
|
144
|
-
const line = buffer.slice(0, newlineIndex).trim();
|
|
145
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
146
|
-
if (line) await consumeLine(line);
|
|
147
|
-
newlineIndex = buffer.indexOf("\n");
|
|
148
|
-
}
|
|
149
|
-
},
|
|
150
|
-
async finish() {
|
|
151
|
-
const tail = buffer.trim();
|
|
152
|
-
buffer = "";
|
|
153
|
-
if (tail) await consumeLine(tail);
|
|
154
|
-
if (mode !== "ndjson") return { mode: "legacy", output: rawOutput };
|
|
155
|
-
if (!terminalSeen) throw new Error(`Tool ${name} ended without a terminal event`);
|
|
156
|
-
return { mode: "ndjson", result: terminalResult };
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function runToolProcess(command, args, { onEvent, maxFrameBytes, timeoutMs, killGraceMs, label, ...options } = {}) {
|
|
162
|
-
const child = spawn(command, args, { ...options, stdio: ["ignore", "pipe", "pipe"] });
|
|
163
|
-
const parser = createToolOutputParser(path.basename(args[0] || command), { onEvent, maxFrameBytes });
|
|
164
|
-
const stderrChunks = [];
|
|
165
|
-
let stderrBytes = 0;
|
|
166
|
-
const stdoutTask = (async () => {
|
|
167
|
-
for await (const chunk of child.stdout) await parser.push(chunk);
|
|
168
|
-
return parser.finish();
|
|
169
|
-
})();
|
|
170
|
-
const stderrTask = (async () => {
|
|
171
|
-
for await (const chunk of child.stderr) {
|
|
172
|
-
if (stderrBytes >= maxFrameBytes) continue;
|
|
173
|
-
const accepted = chunk.subarray(0, maxFrameBytes - stderrBytes);
|
|
174
|
-
stderrChunks.push(accepted);
|
|
175
|
-
stderrBytes += accepted.length;
|
|
176
|
-
}
|
|
177
|
-
return Buffer.concat(stderrChunks).toString("utf8");
|
|
178
|
-
})();
|
|
179
|
-
child.stdout.resume();
|
|
180
|
-
child.stderr.resume();
|
|
181
|
-
let code;
|
|
182
|
-
try {
|
|
183
|
-
code = await waitForToolProcess(child, { timeoutMs, killGraceMs, label });
|
|
184
|
-
} catch (error) {
|
|
185
|
-
await Promise.allSettled([stdoutTask, stderrTask]);
|
|
186
|
-
throw error;
|
|
187
|
-
}
|
|
188
|
-
const [parsed, stderr] = await Promise.all([stdoutTask, stderrTask]);
|
|
189
|
-
return { code, parsed, stderr };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
64
|
function normalizeCategory(category) {
|
|
193
65
|
if (typeof category !== "string") return null;
|
|
194
66
|
const trimmed = category.trim();
|
|
@@ -289,6 +161,7 @@ export class ToolRegistry {
|
|
|
289
161
|
policy: executionPolicy,
|
|
290
162
|
logger
|
|
291
163
|
});
|
|
164
|
+
this.concurrentExecutions = new Map();
|
|
292
165
|
}
|
|
293
166
|
|
|
294
167
|
async buildSnapshot() {
|
|
@@ -393,29 +266,44 @@ export class ToolRegistry {
|
|
|
393
266
|
async help(name) {
|
|
394
267
|
const tool = this.get(name);
|
|
395
268
|
if (!tool) throw new Error(`Tool not found: ${name}`);
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
269
|
+
const lease = await this.executionGovernor.acquire(tool.execution, `${name}:help`);
|
|
270
|
+
try {
|
|
271
|
+
const execution = executionForLease(tool.execution, lease);
|
|
272
|
+
const nodeArgs = [
|
|
273
|
+
...(execution?.maxHeapMb ? [`--max-old-space-size=${execution.maxHeapMb}`] : []),
|
|
274
|
+
tool.entry,
|
|
275
|
+
"--help"
|
|
276
|
+
];
|
|
277
|
+
const invocation = isolatedToolProcessInvocation(nodeArgs, execution);
|
|
278
|
+
const result = await runToolHelpProcess(invocation.command, invocation.args, {
|
|
279
|
+
cwd: tool.dir,
|
|
280
|
+
env: toolProcessEnv(),
|
|
281
|
+
timeoutMs: this.helpTimeoutMs,
|
|
282
|
+
killGraceMs: this.killGraceMs,
|
|
283
|
+
maxOutputBytes: execution?.maxOutputBytes || daemonConfigDefaults.ipcFrameBytes,
|
|
284
|
+
label: `Tool help for ${name}`
|
|
285
|
+
});
|
|
286
|
+
const help = result.stdout || result.stderr;
|
|
287
|
+
const skills = await this.resolveSkills(name);
|
|
288
|
+
const sections = [
|
|
289
|
+
help.trimEnd(),
|
|
290
|
+
formatSemanticMetadata(tool),
|
|
291
|
+
formatToolDependencies(tool, this.tools)
|
|
292
|
+
];
|
|
293
|
+
if (skills.length) {
|
|
294
|
+
const skillHelp = skills.map((item) => [
|
|
295
|
+
`- ${item.name}${item.when ? ` (${item.when})` : ""}`,
|
|
296
|
+
item.description ? ` ${item.description}` : null,
|
|
297
|
+
item.found ? ` path: ${item.path}` : " warning: skill not found"
|
|
298
|
+
].filter(Boolean).join("\n")).join("\n");
|
|
299
|
+
sections.push(`Assigned skills:\n${skillHelp}`);
|
|
300
|
+
}
|
|
301
|
+
lease.release({ success: result.code === 0 });
|
|
302
|
+
return `${sections.filter(Boolean).join("\n\n")}\n`;
|
|
303
|
+
} catch (error) {
|
|
304
|
+
lease.release({ memoryLimited: error?.code === "TOOL_PROCESS_MEMORY_LIMIT" });
|
|
305
|
+
throw error;
|
|
417
306
|
}
|
|
418
|
-
return `${sections.filter(Boolean).join("\n\n")}\n`;
|
|
419
307
|
}
|
|
420
308
|
|
|
421
309
|
async resolveSkills(name) {
|
|
@@ -476,7 +364,25 @@ export class ToolRegistry {
|
|
|
476
364
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
477
365
|
}
|
|
478
366
|
|
|
479
|
-
async run(
|
|
367
|
+
async run(invocation) {
|
|
368
|
+
const tool = this.get(invocation.name);
|
|
369
|
+
if (!tool?.execution?.deduplicateConcurrent) return this.runOnce(invocation);
|
|
370
|
+
const key = concurrentExecutionKey(invocation.name, invocation.chatId, invocation.request);
|
|
371
|
+
const active = this.concurrentExecutions.get(key);
|
|
372
|
+
if (active) {
|
|
373
|
+
this.logger?.log("tools", `joined concurrent duplicate ${invocation.name}`);
|
|
374
|
+
return active;
|
|
375
|
+
}
|
|
376
|
+
const execution = this.runOnce(invocation);
|
|
377
|
+
this.concurrentExecutions.set(key, execution);
|
|
378
|
+
try {
|
|
379
|
+
return await execution;
|
|
380
|
+
} finally {
|
|
381
|
+
if (this.concurrentExecutions.get(key) === execution) this.concurrentExecutions.delete(key);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async runOnce({ name, request, chatId = null, onEvent = null }) {
|
|
480
386
|
const tool = this.get(name);
|
|
481
387
|
if (!tool) throw new Error(`Tool not found: ${name}`);
|
|
482
388
|
const dependencyIssue = this.dependencyIssues(name)[0];
|
|
@@ -490,6 +396,7 @@ export class ToolRegistry {
|
|
|
490
396
|
const tmpDir = chatId != null ? getChatToolTmpDir(chatId, name) : getToolTmpDir(name);
|
|
491
397
|
const requestFile = path.join(tmpDir, `.request-${Date.now()}-${randomUUID()}.json`);
|
|
492
398
|
let lease = null;
|
|
399
|
+
let leaseOutcome = {};
|
|
493
400
|
let result;
|
|
494
401
|
try {
|
|
495
402
|
lease = await this.executionGovernor.acquire(tool.execution, name);
|
|
@@ -511,11 +418,25 @@ export class ToolRegistry {
|
|
|
511
418
|
result = await runtime.submit(enrichedRequest, { onEvent });
|
|
512
419
|
} else {
|
|
513
420
|
await writeFile(requestFile, `${JSON.stringify(enrichedRequest, null, 2)}\n`, "utf8");
|
|
514
|
-
const
|
|
421
|
+
const execution = executionForLease(tool.execution, lease);
|
|
422
|
+
const nodeArgs = [
|
|
423
|
+
...(execution?.maxHeapMb ? [`--max-old-space-size=${execution.maxHeapMb}`] : []),
|
|
424
|
+
tool.entry,
|
|
425
|
+
"run",
|
|
426
|
+
"--request-file",
|
|
427
|
+
requestFile
|
|
428
|
+
];
|
|
429
|
+
const processInvocation = isolatedToolProcessInvocation(nodeArgs, execution);
|
|
430
|
+
if (processInvocation.isolated) {
|
|
431
|
+
this.logger?.log("tools", `${name} isolated at ${execution.maxMemoryMb} MiB total memory (${execution.maxHeapMb} MiB heap)`);
|
|
432
|
+
}
|
|
433
|
+
const processResult = await runToolProcess(processInvocation.command, processInvocation.args, {
|
|
515
434
|
cwd: tool.dir,
|
|
516
|
-
env:
|
|
435
|
+
env: toolProcessEnv(),
|
|
517
436
|
onEvent,
|
|
437
|
+
parserName: name,
|
|
518
438
|
maxFrameBytes: daemonConfigDefaults.ipcFrameBytes,
|
|
439
|
+
maxOutputBytes: tool.execution?.maxOutputBytes || daemonConfigDefaults.ipcFrameBytes,
|
|
519
440
|
timeoutMs: this.runTimeoutMs,
|
|
520
441
|
killGraceMs: this.killGraceMs,
|
|
521
442
|
label: `Tool run for ${name}`
|
|
@@ -523,11 +444,21 @@ export class ToolRegistry {
|
|
|
523
444
|
if (processResult.stderr.trim()) {
|
|
524
445
|
this.logger?.log("tools", `${name} stderr: ${processResult.stderr.trim()}`);
|
|
525
446
|
}
|
|
447
|
+
if (processResult.code !== 0) {
|
|
448
|
+
const memoryLimited = /heap limit|heap out of memory|allocation failed.*memory|memory cgroup out of memory|\bkilled\b/i.test(processResult.stderr)
|
|
449
|
+
|| (processInvocation.isolated && [9, 134, 137].includes(processResult.code));
|
|
450
|
+
const error = new Error(memoryLimited
|
|
451
|
+
? `Tool ${name} exceeded its isolated memory limit`
|
|
452
|
+
: `Tool ${name} exited with code ${processResult.code}`);
|
|
453
|
+
error.code = memoryLimited ? "TOOL_PROCESS_MEMORY_LIMIT" : "TOOL_PROCESS_EXIT";
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
526
456
|
result = processResult.parsed.mode === "ndjson"
|
|
527
457
|
? processResult.parsed.result
|
|
528
458
|
: JSON.parse(processResult.parsed.output);
|
|
529
459
|
}
|
|
530
460
|
const normalized = normalizeToolResult(name, result);
|
|
461
|
+
leaseOutcome = { success: normalized.ok !== false };
|
|
531
462
|
if (normalized.ok === false) {
|
|
532
463
|
this.logger?.log("tools", `${name} -> ${normalized.status || "error"}: ${normalized.error || "unknown error"}`);
|
|
533
464
|
} else {
|
|
@@ -535,7 +466,20 @@ export class ToolRegistry {
|
|
|
535
466
|
}
|
|
536
467
|
return normalized;
|
|
537
468
|
} catch (error) {
|
|
538
|
-
|
|
469
|
+
leaseOutcome = { memoryLimited: error?.code === "TOOL_PROCESS_MEMORY_LIMIT" };
|
|
470
|
+
if (error?.code === "TOOL_RESOURCE_PRESSURE") {
|
|
471
|
+
return normalizeToolResult(name, {
|
|
472
|
+
ok: false,
|
|
473
|
+
status: "retryable",
|
|
474
|
+
error: error.message,
|
|
475
|
+
resolution: {
|
|
476
|
+
type: "retry_later",
|
|
477
|
+
retry: true,
|
|
478
|
+
message: "The tool was not started. Retry after host memory pressure falls."
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
if (["TOOL_PROCESS_TIMEOUT", "TOOL_OUTPUT_LIMIT", "TOOL_PROCESS_MEMORY_LIMIT", "TOOL_PROCESS_EXIT"].includes(error?.code)) {
|
|
539
483
|
return normalizeToolResult(name, {
|
|
540
484
|
ok: false,
|
|
541
485
|
status: "outcome_uncertain",
|
|
@@ -543,7 +487,7 @@ export class ToolRegistry {
|
|
|
543
487
|
resolution: {
|
|
544
488
|
type: "status_check_required",
|
|
545
489
|
retry: false,
|
|
546
|
-
message: "The tool process
|
|
490
|
+
message: "The isolated tool process ended without a confirmed result. Check external state before retrying."
|
|
547
491
|
}
|
|
548
492
|
});
|
|
549
493
|
}
|
|
@@ -552,7 +496,7 @@ export class ToolRegistry {
|
|
|
552
496
|
error: error?.message || `Invalid tool response for ${name}`
|
|
553
497
|
});
|
|
554
498
|
} finally {
|
|
555
|
-
lease?.release();
|
|
499
|
+
lease?.release(leaseOutcome);
|
|
556
500
|
await unlink(requestFile).catch(() => {});
|
|
557
501
|
await rmdir(tmpDir).catch(() => {});
|
|
558
502
|
if (chatId != null) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { getChatToolResourceNotesFile } from "../../
|
|
3
|
+
import { getChatToolResourceNotesFile } from "../../platform/paths.js";
|
|
4
4
|
|
|
5
5
|
export const maxToolResourceNoteCharacters = 200;
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { getChatToolUsageFile } from "../../
|
|
3
|
+
import { getChatToolUsageFile } from "../../platform/paths.js";
|
|
4
4
|
|
|
5
5
|
function emptyUsage() {
|
|
6
6
|
return { version: 1, tools: {} };
|