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
|
@@ -12,11 +12,30 @@ function deferred() {
|
|
|
12
12
|
return { promise, resolve };
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
const safeMemoryPressure = async () => ({
|
|
16
|
+
availableBytes: 512 * 1024 * 1024,
|
|
17
|
+
totalBytes: 4 * 1024 * 1024 * 1024,
|
|
18
|
+
workerRssBytes: 100 * 1024 * 1024,
|
|
19
|
+
swapTotalBytes: 1024,
|
|
20
|
+
swapUsedPercent: 50
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const settle = () => new Promise((resolve) => setImmediate(resolve));
|
|
24
|
+
|
|
15
25
|
test("normalizes manifest weights and configurable class capacities", () => {
|
|
16
26
|
assert.deepEqual(normalizeToolExecution({ resourceClass: "browser", weight: 2 }), {
|
|
17
27
|
resourceClass: "browser",
|
|
18
|
-
weight: 2
|
|
28
|
+
weight: 2,
|
|
29
|
+
deduplicateConcurrent: false,
|
|
30
|
+
maxHeapMb: 4096,
|
|
31
|
+
maxMemoryMb: 16_384,
|
|
32
|
+
maxOutputBytes: 1_048_576
|
|
19
33
|
});
|
|
34
|
+
assert.equal(normalizeToolExecution({
|
|
35
|
+
resourceClass: "orchestrator",
|
|
36
|
+
weight: 1,
|
|
37
|
+
deduplicateConcurrent: true
|
|
38
|
+
}).deduplicateConcurrent, true);
|
|
20
39
|
assert.equal(normalizeToolExecution(undefined), null);
|
|
21
40
|
assert.throws(() => normalizeToolExecution({ resourceClass: "Browser!", weight: 1 }), /Invalid tool execution resource class/);
|
|
22
41
|
assert.throws(() => normalizeToolExecution({ resourceClass: "browser", weight: 0 }), /positive integer/);
|
|
@@ -27,6 +46,16 @@ test("normalizes manifest weights and configurable class capacities", () => {
|
|
|
27
46
|
}), {
|
|
28
47
|
defaultCapacity: 3,
|
|
29
48
|
maxQueuedPerClass: 12,
|
|
49
|
+
maxWorkerRssMb: 384,
|
|
50
|
+
maxSwapUsedPercent: 95,
|
|
51
|
+
initialToolMemoryMb: 384,
|
|
52
|
+
minimumToolMemoryMb: 128,
|
|
53
|
+
maximumToolMemoryMb: 4096,
|
|
54
|
+
systemReserveMb: 128,
|
|
55
|
+
coreReserveMb: 384,
|
|
56
|
+
toolHeapPercent: 65,
|
|
57
|
+
toolMemoryHighPercent: 85,
|
|
58
|
+
toolSwapMaxMb: 128,
|
|
30
59
|
capacities: { browser: 4 }
|
|
31
60
|
});
|
|
32
61
|
});
|
|
@@ -36,13 +65,15 @@ test("queues weighted work fairly within one resource class", async () => {
|
|
|
36
65
|
const governor = new WeightedResourceGovernor({
|
|
37
66
|
policy: { defaultCapacity: 2 },
|
|
38
67
|
now: () => time,
|
|
39
|
-
memoryUsage: () => ({ rss: 100 * 1024 * 1024 })
|
|
68
|
+
memoryUsage: () => ({ rss: 100 * 1024 * 1024 }),
|
|
69
|
+
memoryPressure: safeMemoryPressure
|
|
40
70
|
});
|
|
41
71
|
const execution = { resourceClass: "browser", weight: 1 };
|
|
42
72
|
const first = await governor.acquire(execution, "first");
|
|
43
73
|
const second = await governor.acquire(execution, "second");
|
|
44
74
|
const thirdLease = governor.acquire(execution, "third");
|
|
45
75
|
const fourthLease = governor.acquire(execution, "fourth");
|
|
76
|
+
await settle();
|
|
46
77
|
|
|
47
78
|
assert.deepEqual(governor.snapshot().resources.browser, {
|
|
48
79
|
capacity: 2,
|
|
@@ -66,26 +97,52 @@ test("queues weighted work fairly within one resource class", async () => {
|
|
|
66
97
|
assert.equal(governor.snapshot().resources.browser.activeWeight, 0);
|
|
67
98
|
});
|
|
68
99
|
|
|
69
|
-
test("undeclared
|
|
70
|
-
const governor = new WeightedResourceGovernor({ policy: { defaultCapacity: 1 } });
|
|
100
|
+
test("undeclared work bypasses the optional resource governor", async () => {
|
|
101
|
+
const governor = new WeightedResourceGovernor({ policy: { defaultCapacity: 1 }, memoryPressure: safeMemoryPressure });
|
|
71
102
|
const heavy = await governor.acquire({ resourceClass: "browser", weight: 1 }, "heavy");
|
|
72
103
|
const queued = governor.acquire({ resourceClass: "browser", weight: 1 }, "queued");
|
|
104
|
+
await settle();
|
|
73
105
|
const light = await governor.acquire(null, "light");
|
|
74
106
|
assert.equal(light.waitedMs, 0);
|
|
107
|
+
assert.equal(light.memoryLimitMb, undefined);
|
|
108
|
+
assert.equal(light.heapLimitMb, undefined);
|
|
75
109
|
assert.equal(governor.snapshot().resources.browser.queued, 1);
|
|
76
110
|
light.release();
|
|
77
111
|
heavy.release();
|
|
78
112
|
(await queued).release();
|
|
79
113
|
});
|
|
80
114
|
|
|
115
|
+
test("rejects declared heavy tools before spawn when memory pressure is unsafe", async () => {
|
|
116
|
+
const logs = [];
|
|
117
|
+
const governor = new WeightedResourceGovernor({
|
|
118
|
+
policy: { maxWorkerRssMb: 384, maxSwapUsedPercent: 95 },
|
|
119
|
+
memoryPressure: async () => ({
|
|
120
|
+
availableBytes: 80 * 1024 * 1024,
|
|
121
|
+
workerRssBytes: 400 * 1024 * 1024,
|
|
122
|
+
swapTotalBytes: 1024,
|
|
123
|
+
swapUsedPercent: 50
|
|
124
|
+
}),
|
|
125
|
+
logger: { log: (...parts) => logs.push(parts.join(" ")) }
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await assert.rejects(
|
|
129
|
+
() => governor.acquire({ resourceClass: "browser", weight: 1 }, "web-browser"),
|
|
130
|
+
(error) => error.code === "TOOL_RESOURCE_PRESSURE" && /was not started/.test(error.message)
|
|
131
|
+
);
|
|
132
|
+
assert.match(logs.join("\n"), /web-browser rejected for browser/);
|
|
133
|
+
assert.equal(governor.snapshot().resources.browser.activeWeight, 0);
|
|
134
|
+
});
|
|
135
|
+
|
|
81
136
|
test("larger weights consume shared capacity and worker RSS peaks are retained", async () => {
|
|
82
137
|
let rss = 120 * 1024 * 1024;
|
|
83
138
|
const governor = new WeightedResourceGovernor({
|
|
84
139
|
policy: { defaultCapacity: 3 },
|
|
85
|
-
memoryUsage: () => ({ rss })
|
|
140
|
+
memoryUsage: () => ({ rss }),
|
|
141
|
+
memoryPressure: safeMemoryPressure
|
|
86
142
|
});
|
|
87
143
|
const large = await governor.acquire({ resourceClass: "browser", weight: 2 }, "large");
|
|
88
144
|
const waiting = governor.acquire({ resourceClass: "browser", weight: 2 }, "waiting");
|
|
145
|
+
await settle();
|
|
89
146
|
assert.equal(governor.snapshot().resources.browser.queued, 1);
|
|
90
147
|
rss = 180 * 1024 * 1024;
|
|
91
148
|
large.release();
|
|
@@ -93,3 +150,44 @@ test("larger weights consume shared capacity and worker RSS peaks are retained",
|
|
|
93
150
|
assert.equal(governor.snapshot().peakRssBytes, rss);
|
|
94
151
|
next.release();
|
|
95
152
|
});
|
|
153
|
+
|
|
154
|
+
test("shares one host memory budget across independent resource classes", async () => {
|
|
155
|
+
const pressure = async () => ({
|
|
156
|
+
availableBytes: 512 * 1024 * 1024,
|
|
157
|
+
totalBytes: 1024 * 1024 * 1024,
|
|
158
|
+
workerRssBytes: 100 * 1024 * 1024,
|
|
159
|
+
swapTotalBytes: 0,
|
|
160
|
+
swapUsedPercent: 0
|
|
161
|
+
});
|
|
162
|
+
const governor = new WeightedResourceGovernor({
|
|
163
|
+
policy: { systemReserveMb: 128, coreReserveMb: 384, initialToolMemoryMb: 384 },
|
|
164
|
+
memoryPressure: pressure
|
|
165
|
+
});
|
|
166
|
+
const browser = await governor.acquire({ resourceClass: "browser" }, "browser");
|
|
167
|
+
const queued = governor.acquire({ resourceClass: "orchestrator" }, "orchestrator");
|
|
168
|
+
await settle();
|
|
169
|
+
|
|
170
|
+
assert.equal(governor.snapshot().memory.budgetMb, 512);
|
|
171
|
+
assert.equal(governor.snapshot().memory.activeMb, 384);
|
|
172
|
+
assert.equal(governor.snapshot().resources.orchestrator.queued, 1);
|
|
173
|
+
|
|
174
|
+
browser.release({ success: true });
|
|
175
|
+
const orchestrator = await queued;
|
|
176
|
+
assert.equal(orchestrator.memoryLimitMb, 384);
|
|
177
|
+
orchestrator.release({ success: true });
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("raises a tool memory recommendation after an isolated limit failure", async () => {
|
|
181
|
+
const governor = new WeightedResourceGovernor({
|
|
182
|
+
policy: { initialToolMemoryMb: 256 },
|
|
183
|
+
memoryPressure: safeMemoryPressure
|
|
184
|
+
});
|
|
185
|
+
const first = await governor.acquire({}, "growing-tool");
|
|
186
|
+
assert.equal(first.memoryLimitMb, 256);
|
|
187
|
+
first.release({ memoryLimited: true });
|
|
188
|
+
|
|
189
|
+
const second = await governor.acquire({}, "growing-tool");
|
|
190
|
+
assert.equal(second.memoryLimitMb, 384);
|
|
191
|
+
assert.equal(governor.snapshot().memory.profiles["growing-tool"].memoryLimitFailures, 1);
|
|
192
|
+
second.release({ success: true });
|
|
193
|
+
});
|
|
@@ -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" });
|