arisa 5.1.68 → 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-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 +99 -187
- 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 +188 -38
- package/src/index.js +14 -2
- package/src/official-tools.lock.json +424 -50
- 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 +1 -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 +36 -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/telegram-prompt-controller.test.js +81 -0
- package/test/telegram-text-artifact.test.js +30 -0
- package/test/tool-registry-run.test.js +108 -4
- package/test/tui.test.js +41 -0
- package/test/weighted-resource-governor.test.js +97 -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
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
readWorkerHeapPressure,
|
|
5
|
+
WorkerHeapCircuitBreaker,
|
|
6
|
+
WorkerHeapPressureError
|
|
7
|
+
} from "../src/core/agent/worker-heap-circuit-breaker.js";
|
|
8
|
+
|
|
9
|
+
function pressure(percent) {
|
|
10
|
+
return { heapUsed: percent, heapLimit: 100, percent };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
test("reports bounded worker heap pressure", () => {
|
|
14
|
+
assert.deepEqual(readWorkerHeapPressure({
|
|
15
|
+
memoryUsage: () => ({ heapUsed: 40 }),
|
|
16
|
+
heapStatistics: () => ({ heap_size_limit: 100 })
|
|
17
|
+
}), pressure(40));
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("evicts inactive sessions under soft pressure and admits recovered work", async () => {
|
|
21
|
+
const samples = [pressure(75), pressure(60)];
|
|
22
|
+
let evictions = 0;
|
|
23
|
+
const breaker = new WorkerHeapCircuitBreaker({
|
|
24
|
+
lifecycle: { async evictInactive() { evictions += 1; return [{ sessionKey: "idle" }]; } },
|
|
25
|
+
measure: () => samples.shift() || pressure(60)
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
assert.deepEqual(await breaker.admit(), pressure(60));
|
|
29
|
+
assert.equal(evictions, 1);
|
|
30
|
+
assert.deepEqual(breaker.getDiagnostic(), {
|
|
31
|
+
enabled: true,
|
|
32
|
+
softPercent: 70,
|
|
33
|
+
criticalPercent: 82,
|
|
34
|
+
waitMs: 15_000,
|
|
35
|
+
pollMs: 500,
|
|
36
|
+
heapUsed: 60,
|
|
37
|
+
heapLimit: 100,
|
|
38
|
+
currentHeapPercent: 60,
|
|
39
|
+
pressureEvents: 1,
|
|
40
|
+
evictedSessions: 1,
|
|
41
|
+
delayedAdmissions: 0,
|
|
42
|
+
rejectedAdmissions: 0,
|
|
43
|
+
peakHeapPercent: 75
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("delays critical admissions until active work releases memory", async () => {
|
|
48
|
+
const samples = [pressure(90), pressure(89), pressure(78)];
|
|
49
|
+
let now = 0;
|
|
50
|
+
const breaker = new WorkerHeapCircuitBreaker({
|
|
51
|
+
lifecycle: { async evictInactive() { return []; } },
|
|
52
|
+
config: { waitMs: 1_000, pollMs: 100 },
|
|
53
|
+
measure: () => samples.shift() || pressure(78),
|
|
54
|
+
now: () => now,
|
|
55
|
+
sleep: async (ms) => { now += ms; }
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
assert.deepEqual(await breaker.admit(), pressure(78));
|
|
59
|
+
assert.equal(breaker.getDiagnostic().delayedAdmissions, 1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("rejects persistent critical pressure with a retryable error", async () => {
|
|
63
|
+
let now = 0;
|
|
64
|
+
const breaker = new WorkerHeapCircuitBreaker({
|
|
65
|
+
lifecycle: { async evictInactive() { return []; } },
|
|
66
|
+
config: { waitMs: 200, pollMs: 100 },
|
|
67
|
+
measure: () => pressure(90),
|
|
68
|
+
now: () => now,
|
|
69
|
+
sleep: async (ms) => { now += ms; }
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await assert.rejects(
|
|
73
|
+
breaker.admit(),
|
|
74
|
+
(error) => error instanceof WorkerHeapPressureError
|
|
75
|
+
&& error.code === "WORKER_HEAP_PRESSURE"
|
|
76
|
+
&& error.retryable === true
|
|
77
|
+
);
|
|
78
|
+
assert.equal(breaker.getDiagnostic().rejectedAdmissions, 1);
|
|
79
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import {
|
|
7
|
+
consumeWorkerRecoveryReport,
|
|
8
|
+
loadWorkerRecoveryReport,
|
|
9
|
+
recordUnexpectedWorkerExit,
|
|
10
|
+
summarizeRecoveryEvidence
|
|
11
|
+
} from "../src/runtime/worker-recovery-report.js";
|
|
12
|
+
|
|
13
|
+
function localLogTimestamp(date) {
|
|
14
|
+
const two = (value) => String(value).padStart(2, "0");
|
|
15
|
+
return `${date.getFullYear()}-${two(date.getMonth() + 1)}-${two(date.getDate())} ${two(date.getHours())}:${two(date.getMinutes())}:${two(date.getSeconds())}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test("summarizes only bounded crash evidence", () => {
|
|
19
|
+
const evidence = summarizeRecoveryEvidence([
|
|
20
|
+
"FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory",
|
|
21
|
+
"[agent] run_tool web-browser",
|
|
22
|
+
"[agent] run_tool web-browser",
|
|
23
|
+
"[agent] run_tool campaign-draft-runner"
|
|
24
|
+
], { occurredAt: "invalid", signal: "SIGABRT" });
|
|
25
|
+
|
|
26
|
+
assert.equal(evidence.cause, "JavaScript heap out of memory");
|
|
27
|
+
assert.deepEqual(evidence.tools, [["web-browser", 2], ["campaign-draft-runner", 1]]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("persists, formats, and consumes one automatic recovery report", async () => {
|
|
31
|
+
const directory = await mkdtemp(path.join(os.tmpdir(), "arisa-worker-recovery-"));
|
|
32
|
+
const reportFile = path.join(directory, "report.json");
|
|
33
|
+
const taskFile = path.join(directory, "tasks.json");
|
|
34
|
+
const occurredAt = new Date();
|
|
35
|
+
const report = await recordUnexpectedWorkerExit({
|
|
36
|
+
occurredAt: occurredAt.toISOString(),
|
|
37
|
+
runtimeMs: 10_000,
|
|
38
|
+
restartDelayMs: 2_000,
|
|
39
|
+
consecutiveFailures: 1,
|
|
40
|
+
code: null,
|
|
41
|
+
signal: "SIGABRT",
|
|
42
|
+
detail: "signal=SIGABRT"
|
|
43
|
+
}, { reportFile });
|
|
44
|
+
await writeFile(taskFile, `${JSON.stringify({ tasks: [{
|
|
45
|
+
updatedAt: occurredAt.toISOString(),
|
|
46
|
+
lastOutcome: "outcome_uncertain",
|
|
47
|
+
lastError: "execution interrupted before confirmation"
|
|
48
|
+
}] })}\n`);
|
|
49
|
+
const timestamp = localLogTimestamp(occurredAt);
|
|
50
|
+
const recovery = await loadWorkerRecoveryReport({
|
|
51
|
+
reportFile,
|
|
52
|
+
taskFile,
|
|
53
|
+
readLines: async () => ({ text: [
|
|
54
|
+
`[${timestamp}] [agent] run_tool web-browser`,
|
|
55
|
+
`[${timestamp}] [agent] run_tool web-browser`,
|
|
56
|
+
"FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory",
|
|
57
|
+
`[${timestamp}] [service] worker exited unexpectedly (signal=SIGABRT)`
|
|
58
|
+
].join("\n") }),
|
|
59
|
+
getVersion: async () => "5.1.70"
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
assert.match(recovery.text, /Cause: JavaScript heap out of memory/);
|
|
63
|
+
assert.match(recovery.text, /web-browser ×2/);
|
|
64
|
+
assert.match(recovery.text, /1 scheduled execution was marked outcome-uncertain and not replayed/);
|
|
65
|
+
assert.match(recovery.text, /restarted after 2s; Arisa 5\.1\.70 is running/);
|
|
66
|
+
assert.equal(await consumeWorkerRecoveryReport(report.id, { reportFile }), true);
|
|
67
|
+
await assert.rejects(() => readFile(reportFile), /ENOENT/);
|
|
68
|
+
await rm(directory, { recursive: true, force: true });
|
|
69
|
+
});
|
|
@@ -43,6 +43,11 @@ await runtime.workLoop({
|
|
|
43
43
|
recover,
|
|
44
44
|
processJob: async (payload, execution) => {
|
|
45
45
|
if (payload.action === "fail") throw new Error("synthetic job failure");
|
|
46
|
+
if (payload.action === "hang-until-cancelled") {
|
|
47
|
+
await new Promise((_, reject) => execution.signal.addEventListener("abort", () => {
|
|
48
|
+
reject(Object.assign(new Error("synthetic job cancelled"), { code: "DAEMON_JOB_CANCELLED" }));
|
|
49
|
+
}, { once: true }));
|
|
50
|
+
}
|
|
46
51
|
if (payload.action === "stream") {
|
|
47
52
|
await execution.emit("progress", { percent: 50 });
|
|
48
53
|
await execution.emit("chunk", { text: "partial" });
|