arisa 5.2.7 → 5.2.19
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 +25 -4
- package/package.json +10 -9
- package/pnpm-workspace.yaml +7 -5
- package/src/core/agent/agent-manager.js +92 -18
- package/src/core/agent/agent-session-lifecycle.js +29 -6
- package/src/core/agent/agent-turn-coordinator.js +143 -0
- package/src/core/agent/auth-flow.js +6 -6
- package/src/core/agent/model-speed.js +3 -1
- package/src/core/agent/pi-auth-login.js +28 -28
- package/src/core/agent/pi-capability-tools.js +8 -4
- package/src/core/agent/pi-runtime.js +14 -21
- package/src/core/agent/session-history-reader.js +168 -0
- package/src/core/agent/session-preload-migration.js +162 -0
- package/src/core/agent/session-rotation.js +29 -0
- package/src/core/agent/worker-tool-fanout.js +117 -0
- package/src/core/capabilities/capability-service.js +28 -2
- package/src/core/config/config-defaults.js +29 -0
- package/src/core/tasks/task-runner.js +8 -4
- package/src/core/tasks/task-store.js +48 -4
- package/src/core/tools/daemon-processes.js +7 -0
- package/src/core/tools/memory-pressure.js +2 -2
- package/src/core/tools/tool-registry.js +35 -7
- package/src/core/tools/weighted-resource-governor.js +7 -6
- package/src/official-tools.lock.json +129 -48
- package/src/runtime/bootstrap-cli.js +3 -3
- package/src/runtime/bootstrap-telegram.js +7 -7
- package/src/runtime/doctor.js +20 -73
- package/src/runtime/obsolete-daemon-reaper.js +43 -0
- package/src/runtime/process-inspection.js +78 -0
- package/src/runtime/slave-cli.js +20 -10
- package/src/runtime/slave-service.js +299 -7
- package/src/runtime/tool-process-supervisor.js +35 -8
- package/src/runtime/tui.js +6 -7
- package/src/transport/telegram/bot.js +11 -5
- package/src/transport/telegram/chat-queue.js +6 -2
- package/src/transport/telegram/model-controls.js +1 -1
- package/src/transport/telegram/task-dispatcher.js +67 -15
- package/src/transport/telegram/telegram-auth-controller.js +7 -7
- package/src/transport/telegram/telegram-prompt-controller.js +17 -4
- package/src/transport/telegram/telegram-session-bridge.js +2 -1
- package/test/agent-turn-coordinator.test.js +48 -0
- package/test/auth-flow.test.js +2 -2
- package/test/capabilities-security.test.js +36 -0
- package/test/context-and-task-bounds.test.js +2 -1
- package/test/daemon-runtime.test.js +2 -4
- package/test/doctor.test.js +19 -0
- package/test/memory-pressure.test.js +7 -2
- package/test/model-selection.test.js +3 -2
- package/test/obsolete-daemon-reaper.test.js +61 -0
- package/test/official-tool-dependencies.test.js +7 -2
- package/test/official-tool-installer.test.js +13 -0
- package/test/pi-auth-login.test.js +78 -0
- package/test/pi-capability-tools.test.js +3 -0
- package/test/pi-compaction.test.js +21 -0
- package/test/pi-speed-integration.test.js +176 -0
- package/test/session-history-reader.test.js +84 -0
- package/test/session-preload-migration.test.js +120 -0
- package/test/session-rotation.test.js +110 -0
- package/test/slave-cli.test.js +221 -5
- package/test/task-store.test.js +34 -0
- package/test/telegram-prompt-controller.test.js +2 -1
- package/test/telegram-task-dispatcher.test.js +121 -5
- package/test/tool-registry-run.test.js +10 -1
- package/test/weighted-resource-governor.test.js +28 -0
- package/test/worker-tool-fanout.test.js +79 -0
|
@@ -48,6 +48,17 @@ function normalizeAcknowledgement(value) {
|
|
|
48
48
|
return acknowledgement;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
function recordAgentTaskAuthBlock(execution, toolName, result) {
|
|
52
|
+
if (!execution || execution.blockedAuth || result?.ok !== false || result?.status !== "blocked_auth") return;
|
|
53
|
+
execution.blockedAuth = {
|
|
54
|
+
toolName,
|
|
55
|
+
error: String(result.error || `${toolName} authentication is required`).slice(0, 1_000),
|
|
56
|
+
resolution: result.resolution && typeof result.resolution === "object"
|
|
57
|
+
? structuredClone(result.resolution)
|
|
58
|
+
: {}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
51
62
|
function inferDeliveryMethod(artifact) {
|
|
52
63
|
if (artifact.kind === "audio" || (artifact.mimeType || "").startsWith("audio/")) return "audio";
|
|
53
64
|
if (artifact.kind === "image" || (artifact.mimeType || "").startsWith("image/")) return "photo";
|
|
@@ -71,11 +82,12 @@ export function selectScheduledTasks(tasks = [], { status, limit = defaultSchedu
|
|
|
71
82
|
maxScheduledTaskListLimit
|
|
72
83
|
);
|
|
73
84
|
const allTasks = Array.isArray(tasks) ? tasks : [];
|
|
85
|
+
const activeStatuses = new Set(["pending", "running", "blocked_auth"]);
|
|
74
86
|
const orderedTasks = status
|
|
75
87
|
? [...allTasks].reverse()
|
|
76
88
|
: [
|
|
77
|
-
...allTasks.filter((task) =>
|
|
78
|
-
...allTasks.filter((task) =>
|
|
89
|
+
...allTasks.filter((task) => activeStatuses.has(task.status)).reverse(),
|
|
90
|
+
...allTasks.filter((task) => !activeStatuses.has(task.status)).reverse()
|
|
79
91
|
];
|
|
80
92
|
const visibleTasks = orderedTasks.slice(0, resolvedLimit);
|
|
81
93
|
return {
|
|
@@ -177,6 +189,19 @@ export function createCapabilityService({
|
|
|
177
189
|
if (!toolExecutor?.runTool) throw new Error("tools.run requires toolExecutor");
|
|
178
190
|
const scopedChatId = requireChatId(chatId, method);
|
|
179
191
|
const targetToolName = requireString(params.name, "name");
|
|
192
|
+
const blocked = context.agentTaskExecution?.blockedAuth;
|
|
193
|
+
if (blocked) {
|
|
194
|
+
return {
|
|
195
|
+
ok: false,
|
|
196
|
+
status: "blocked_prerequisite",
|
|
197
|
+
error: `Authentication for ${blocked.toolName} is blocking this scheduled task.`,
|
|
198
|
+
resolution: {
|
|
199
|
+
type: "blocked_prerequisite",
|
|
200
|
+
prerequisiteStatus: "blocked_auth",
|
|
201
|
+
toolName: blocked.toolName
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
180
205
|
const chatArtifactStore = artifactStore.forChat(scopedChatId);
|
|
181
206
|
const artifact = params.artifactId
|
|
182
207
|
? await chatArtifactStore.get(requireString(params.artifactId, "artifactId"))
|
|
@@ -196,6 +221,7 @@ export function createCapabilityService({
|
|
|
196
221
|
chatId: scopedChatId,
|
|
197
222
|
taskContext: context.taskContext || null
|
|
198
223
|
});
|
|
224
|
+
recordAgentTaskAuthBlock(context.agentTaskExecution, targetToolName, result);
|
|
199
225
|
if (params.deliver && result.output?.artifactId) {
|
|
200
226
|
const generated = await chatArtifactStore.get(result.output.artifactId);
|
|
201
227
|
if (generated?.path) {
|
|
@@ -72,6 +72,11 @@ export const piConfigDefaults = Object.freeze({
|
|
|
72
72
|
maxSessions: 3,
|
|
73
73
|
maxPersistedBytes: 48 * 1024 * 1024
|
|
74
74
|
}),
|
|
75
|
+
sessionRotation: Object.freeze({
|
|
76
|
+
enabled: true,
|
|
77
|
+
compactAtPersistedBytes: 24 * 1024 * 1024,
|
|
78
|
+
maxPersistedBytes: 32 * 1024 * 1024
|
|
79
|
+
}),
|
|
75
80
|
heapCircuitBreaker: Object.freeze({
|
|
76
81
|
enabled: true,
|
|
77
82
|
softPercent: 70,
|
|
@@ -79,6 +84,18 @@ export const piConfigDefaults = Object.freeze({
|
|
|
79
84
|
waitMs: 15_000,
|
|
80
85
|
pollMs: 500
|
|
81
86
|
}),
|
|
87
|
+
toolFanout: Object.freeze({
|
|
88
|
+
enabled: true,
|
|
89
|
+
maxConcurrent: 2,
|
|
90
|
+
pressureConcurrent: 1,
|
|
91
|
+
serializePercent: 60
|
|
92
|
+
}),
|
|
93
|
+
turnCoordinator: Object.freeze({
|
|
94
|
+
enabled: true,
|
|
95
|
+
backgroundQueueTtlMs: 10 * 60_000,
|
|
96
|
+
interactiveQueueTtlMs: 0,
|
|
97
|
+
maxQueued: 100
|
|
98
|
+
}),
|
|
82
99
|
compaction: Object.freeze({
|
|
83
100
|
enabled: true,
|
|
84
101
|
reserveTokens: 120_000,
|
|
@@ -133,10 +150,22 @@ export function applyConfigDefaults(config) {
|
|
|
133
150
|
...piConfigDefaults.sessionCache,
|
|
134
151
|
...(configuredPi.sessionCache || {})
|
|
135
152
|
},
|
|
153
|
+
sessionRotation: {
|
|
154
|
+
...piConfigDefaults.sessionRotation,
|
|
155
|
+
...(configuredPi.sessionRotation || {})
|
|
156
|
+
},
|
|
136
157
|
heapCircuitBreaker: {
|
|
137
158
|
...piConfigDefaults.heapCircuitBreaker,
|
|
138
159
|
...(configuredPi.heapCircuitBreaker || {})
|
|
139
160
|
},
|
|
161
|
+
toolFanout: {
|
|
162
|
+
...piConfigDefaults.toolFanout,
|
|
163
|
+
...(configuredPi.toolFanout || {})
|
|
164
|
+
},
|
|
165
|
+
turnCoordinator: {
|
|
166
|
+
...piConfigDefaults.turnCoordinator,
|
|
167
|
+
...(configuredPi.turnCoordinator || {})
|
|
168
|
+
},
|
|
140
169
|
compaction: {
|
|
141
170
|
...piConfigDefaults.compaction,
|
|
142
171
|
...(configuredPi.compaction || {})
|
|
@@ -18,7 +18,8 @@ export function createTaskRunner({ taskStore, dispatch, laneKey = (task) => task
|
|
|
18
18
|
async function reportTerminalFailure(task, updated, error) {
|
|
19
19
|
const isTerminal = updated?.status === "failed"
|
|
20
20
|
|| updated?.status === "outcome_uncertain"
|
|
21
|
-
|| updated?.terminalFailure === true
|
|
21
|
+
|| updated?.terminalFailure === true
|
|
22
|
+
|| updated?.authBlockedNew === true;
|
|
22
23
|
if (!isTerminal || typeof onTerminalFailure !== "function") return;
|
|
23
24
|
try {
|
|
24
25
|
await onTerminalFailure({ task, result: updated, error });
|
|
@@ -37,9 +38,12 @@ export function createTaskRunner({ taskStore, dispatch, laneKey = (task) => task
|
|
|
37
38
|
logger?.log("tasks", `task ${task.id} completed after confirmed execution`);
|
|
38
39
|
return { taskId: task.id, status: "completed" };
|
|
39
40
|
} catch (error) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
const updated = error?.authBlocked === true
|
|
42
|
+
? await taskStore.blockAuth(task.id, error, error.authResolution)
|
|
43
|
+
: await taskStore.retryOrFail(task.id, error, {
|
|
44
|
+
retryable: error?.retryable !== false,
|
|
45
|
+
...(error?.outcomeUncertain === true ? { outcomeUncertain: true } : {})
|
|
46
|
+
});
|
|
43
47
|
const status = updated?.status || "missing";
|
|
44
48
|
logger?.log("tasks", `task ${task.id} ${status}: ${errorMessage(error)}`);
|
|
45
49
|
await reportTerminalFailure(task, updated, error);
|
|
@@ -170,6 +170,17 @@ function retryDelayMs(task) {
|
|
|
170
170
|
return Math.max(1, Math.round(seconds * 1000));
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
function normalizeAuthResolution(resolution = {}) {
|
|
174
|
+
const value = resolution && typeof resolution === "object" && !Array.isArray(resolution) ? resolution : {};
|
|
175
|
+
const retryAfterSeconds = Math.max(300, boundedPositiveNumber(value.retryAfterSeconds, 3_600, 86_400));
|
|
176
|
+
const probeArgs = value.probeArgs && typeof value.probeArgs === "object" && !Array.isArray(value.probeArgs)
|
|
177
|
+
? structuredClone(value.probeArgs)
|
|
178
|
+
: {};
|
|
179
|
+
if (JSON.stringify(probeArgs).length > 4_096) throw new Error("Authentication probe arguments are too large");
|
|
180
|
+
const toolName = typeof value.toolName === "string" ? value.toolName.trim().slice(0, 128) : "";
|
|
181
|
+
return { retryAfterSeconds, probeArgs, ...(toolName ? { toolName } : {}) };
|
|
182
|
+
}
|
|
183
|
+
|
|
173
184
|
function failTask(task, error) {
|
|
174
185
|
const failedAt = new Date().toISOString();
|
|
175
186
|
task.status = "failed";
|
|
@@ -232,7 +243,7 @@ export class TaskStore {
|
|
|
232
243
|
return this.mutate(async (tasks) => {
|
|
233
244
|
const now = Date.now();
|
|
234
245
|
const due = tasks
|
|
235
|
-
.filter((task) => task.status
|
|
246
|
+
.filter((task) => ["pending", "blocked_auth"].includes(task.status)
|
|
236
247
|
&& task.runAt
|
|
237
248
|
&& !Number.isNaN(Date.parse(task.runAt))
|
|
238
249
|
&& Date.parse(task.runAt) <= now)
|
|
@@ -275,7 +286,7 @@ export class TaskStore {
|
|
|
275
286
|
if (task.status === "running") {
|
|
276
287
|
const interruptedAt = new Date(now).toISOString();
|
|
277
288
|
if (task.claimedAt && !task.executionStartedAt) {
|
|
278
|
-
task.status = "pending";
|
|
289
|
+
task.status = task.authBlock ? "blocked_auth" : "pending";
|
|
279
290
|
task.attempts = Math.max(0, Number(task.attempts || 0) - 1);
|
|
280
291
|
task.lastError = "execution interrupted before start";
|
|
281
292
|
task.lastFailedAt = interruptedAt;
|
|
@@ -294,8 +305,11 @@ export class TaskStore {
|
|
|
294
305
|
delete task.claimedAt;
|
|
295
306
|
delete task.executionStartedAt;
|
|
296
307
|
if (task.kind === "poll_tool") {
|
|
297
|
-
task.status = "pending";
|
|
298
|
-
|
|
308
|
+
task.status = task.authBlock ? "blocked_auth" : "pending";
|
|
309
|
+
const delayMs = task.authBlock
|
|
310
|
+
? Number(task.authBlock.retryAfterSeconds || 3_600) * 1_000
|
|
311
|
+
: retryDelayMs(task);
|
|
312
|
+
task.runAt = new Date(now + delayMs).toISOString();
|
|
299
313
|
delete task.startedAt;
|
|
300
314
|
} else {
|
|
301
315
|
const nextRunAt = computeNextRunAt(task, now);
|
|
@@ -337,6 +351,7 @@ export class TaskStore {
|
|
|
337
351
|
delete task.error;
|
|
338
352
|
delete task.lastOutcome;
|
|
339
353
|
delete task.consecutiveFailures;
|
|
354
|
+
delete task.authBlock;
|
|
340
355
|
delete task.claimedAt;
|
|
341
356
|
delete task.executionStartedAt;
|
|
342
357
|
if (nextRunAt) {
|
|
@@ -354,6 +369,35 @@ export class TaskStore {
|
|
|
354
369
|
});
|
|
355
370
|
}
|
|
356
371
|
|
|
372
|
+
async blockAuth(taskId, error, resolution = {}) {
|
|
373
|
+
return this.mutate(async (tasks) => {
|
|
374
|
+
const task = tasks.find((item) => item.id === taskId);
|
|
375
|
+
if (!task) return { result: null, changed: false };
|
|
376
|
+
const now = Date.now();
|
|
377
|
+
const checkedAt = new Date(now).toISOString();
|
|
378
|
+
const normalized = normalizeAuthResolution(resolution);
|
|
379
|
+
const wasBlocked = Boolean(task.authBlock);
|
|
380
|
+
task.status = "blocked_auth";
|
|
381
|
+
task.runAt = new Date(now + (normalized.retryAfterSeconds * 1_000)).toISOString();
|
|
382
|
+
task.attempts = 0;
|
|
383
|
+
task.authBlock = {
|
|
384
|
+
firstBlockedAt: task.authBlock?.firstBlockedAt || checkedAt,
|
|
385
|
+
lastCheckedAt: checkedAt,
|
|
386
|
+
...normalized
|
|
387
|
+
};
|
|
388
|
+
task.lastError = error instanceof Error ? error.message : String(error);
|
|
389
|
+
task.lastFailedAt = checkedAt;
|
|
390
|
+
task.updatedAt = checkedAt;
|
|
391
|
+
if (task.claimedAt) task.lastClaimedAt = task.claimedAt;
|
|
392
|
+
if (task.executionStartedAt) task.lastExecutionStartedAt = task.executionStartedAt;
|
|
393
|
+
delete task.startedAt;
|
|
394
|
+
delete task.claimedAt;
|
|
395
|
+
delete task.executionStartedAt;
|
|
396
|
+
delete task.error;
|
|
397
|
+
return { result: { ...structuredClone(task), authBlockedNew: !wasBlocked } };
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
357
401
|
async retryOrFail(taskId, error, { retryable = true, outcomeUncertain = false } = {}) {
|
|
358
402
|
return this.mutate(async (tasks) => {
|
|
359
403
|
const task = tasks.find((item) => item.id === taskId);
|
|
@@ -280,6 +280,13 @@ export async function unregisterManagedDaemon(toolNameOrOptions, { scope } = {})
|
|
|
280
280
|
return { toolName: paths.toolName, scope: paths.scope, instanceId: paths.instanceId };
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
export async function purgeManagedDaemon(toolNameOrOptions, { scope } = {}) {
|
|
284
|
+
const paths = daemonPaths(toolNameOrOptions, scope);
|
|
285
|
+
await unregisterManagedDaemon(toolNameOrOptions, { scope });
|
|
286
|
+
await rm(paths.root, { recursive: true, force: true });
|
|
287
|
+
return { toolName: paths.toolName, scope: paths.scope, instanceId: paths.instanceId };
|
|
288
|
+
}
|
|
289
|
+
|
|
283
290
|
export async function stopManagedDaemon(toolNameOrOptions, {
|
|
284
291
|
scope,
|
|
285
292
|
signal = "SIGTERM",
|
|
@@ -43,10 +43,10 @@ export async function readMemoryPressure({
|
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
export function memoryPressureReason(snapshot, policy) {
|
|
46
|
+
export function memoryPressureReason(snapshot, policy, { ignoreWorkerRss = false } = {}) {
|
|
47
47
|
const mebibyte = 1024 * 1024;
|
|
48
48
|
const workerRssMb = snapshot.workerRssBytes / mebibyte;
|
|
49
|
-
if (workerRssMb > policy.maxWorkerRssMb) {
|
|
49
|
+
if (!ignoreWorkerRss && workerRssMb > policy.maxWorkerRssMb) {
|
|
50
50
|
return `worker RSS ${Math.ceil(workerRssMb)} MiB exceeds the ${policy.maxWorkerRssMb} MiB limit`;
|
|
51
51
|
}
|
|
52
52
|
if (snapshot.swapTotalBytes > 0 && snapshot.swapUsedPercent > policy.maxSwapUsedPercent) {
|
|
@@ -42,6 +42,25 @@ function concurrentExecutionKey(name, chatId, request) {
|
|
|
42
42
|
return createHash("sha256").update(serialized).digest("hex");
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
export function readyDaemonAdmission(tool, diagnostic) {
|
|
46
|
+
const reusable = Boolean(tool?.daemon)
|
|
47
|
+
&& diagnostic?.alive === true
|
|
48
|
+
&& diagnostic?.state === "ready"
|
|
49
|
+
&& diagnostic?.restart?.requested !== true;
|
|
50
|
+
return reusable ? { ignoreWorkerRss: true } : {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function declaredDaemonScope(tool, chatId) {
|
|
54
|
+
if (!tool?.daemon) return null;
|
|
55
|
+
return tool.daemon.scope === "chat"
|
|
56
|
+
? { type: "chat", chatId }
|
|
57
|
+
: { type: "global" };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function nativeDaemonScope(tool, chatId) {
|
|
61
|
+
return tool?.daemon?.protocol === "arisa-daemon-v1" ? declaredDaemonScope(tool, chatId) : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
45
64
|
function executionForLease(execution, lease) {
|
|
46
65
|
if (!execution) return null;
|
|
47
66
|
return {
|
|
@@ -399,20 +418,29 @@ export class ToolRegistry {
|
|
|
399
418
|
let leaseOutcome = {};
|
|
400
419
|
let result;
|
|
401
420
|
try {
|
|
402
|
-
|
|
421
|
+
const admissionScope = declaredDaemonScope(tool, chatId);
|
|
422
|
+
let admission = {};
|
|
423
|
+
if (admissionScope) {
|
|
424
|
+
const diagnostic = await readDaemonDiagnostic({
|
|
425
|
+
toolName: name,
|
|
426
|
+
scope: admissionScope,
|
|
427
|
+
autoStart: Boolean(tool.daemon.autoStart)
|
|
428
|
+
}).catch(() => null);
|
|
429
|
+
admission = readyDaemonAdmission(tool, diagnostic);
|
|
430
|
+
if (admission.ignoreWorkerRss) this.logger?.log("tools", `reusing ready ${name} daemon without worker RSS spawn admission`);
|
|
431
|
+
}
|
|
432
|
+
lease = await this.executionGovernor.acquire(tool.execution, name, admission);
|
|
403
433
|
this.logger?.log("tools", `running ${name}`);
|
|
404
434
|
await mkdir(tmpDir, { recursive: true });
|
|
405
435
|
const skills = await this.resolveSkills(name);
|
|
406
436
|
const enrichedRequest = { ...request, chatId, skills };
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
? { type: "chat", chatId }
|
|
410
|
-
: { type: "global" };
|
|
437
|
+
const runtimeScope = nativeDaemonScope(tool, chatId);
|
|
438
|
+
if (runtimeScope) {
|
|
411
439
|
const runtime = createDaemonRuntime({
|
|
412
440
|
toolName: name,
|
|
413
441
|
entryPath: tool.entry,
|
|
414
|
-
scope,
|
|
415
|
-
startupContext:
|
|
442
|
+
scope: runtimeScope,
|
|
443
|
+
startupContext: runtimeScope.type === "chat" ? { chatId: String(chatId) } : {},
|
|
416
444
|
autoStart: Boolean(tool.daemon.autoStart)
|
|
417
445
|
});
|
|
418
446
|
result = await runtime.submit(enrichedRequest, { onEvent });
|
|
@@ -205,11 +205,11 @@ export class WeightedResourceGovernor {
|
|
|
205
205
|
request.resolve(this.createLease(request, waitedMs));
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
async pressureSnapshot(label, resourceClass) {
|
|
208
|
+
async pressureSnapshot(label, resourceClass, { ignoreWorkerRss = false } = {}) {
|
|
209
209
|
const pressure = await this.memoryPressure();
|
|
210
210
|
this.lastPressure = pressure;
|
|
211
211
|
this.lastMemoryBudgetMb = this.memoryBudgetMb(pressure);
|
|
212
|
-
const reason = memoryPressureReason(pressure, this.policy);
|
|
212
|
+
const reason = memoryPressureReason(pressure, this.policy, { ignoreWorkerRss });
|
|
213
213
|
if (!reason) return pressure;
|
|
214
214
|
const error = new Error(`Tool ${label} was not started because ${reason}`);
|
|
215
215
|
error.code = "TOOL_RESOURCE_PRESSURE";
|
|
@@ -234,7 +234,7 @@ export class WeightedResourceGovernor {
|
|
|
234
234
|
const next = state.queue[0];
|
|
235
235
|
if (!next) continue;
|
|
236
236
|
try {
|
|
237
|
-
const pressure = await this.pressureSnapshot(next.label, resourceClass);
|
|
237
|
+
const pressure = await this.pressureSnapshot(next.label, resourceClass, next.admission);
|
|
238
238
|
const budgetMb = this.memoryBudgetMb(pressure);
|
|
239
239
|
next.memoryMb = this.requestedMemoryMb(next.execution, next.label, budgetMb);
|
|
240
240
|
if (next.memoryMb < this.policy.minimumToolMemoryMb || !this.canGrant(next)) continue;
|
|
@@ -253,14 +253,14 @@ export class WeightedResourceGovernor {
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
async acquire(rawExecution, label = "tool") {
|
|
256
|
+
async acquire(rawExecution, label = "tool", admission = {}) {
|
|
257
257
|
const execution = normalizeToolExecution(rawExecution);
|
|
258
258
|
if (!execution) return noopLease();
|
|
259
259
|
const resourceClass = execution.resourceClass;
|
|
260
260
|
const capacity = this.capacityFor(resourceClass);
|
|
261
261
|
const weight = Math.min(execution.weight, capacity);
|
|
262
262
|
const state = this.stateFor(resourceClass);
|
|
263
|
-
const pressure = await this.pressureSnapshot(label, resourceClass);
|
|
263
|
+
const pressure = await this.pressureSnapshot(label, resourceClass, admission);
|
|
264
264
|
const budgetMb = this.memoryBudgetMb(pressure);
|
|
265
265
|
const memoryMb = this.requestedMemoryMb(execution, label, budgetMb);
|
|
266
266
|
if (budgetMb < this.policy.minimumToolMemoryMb || memoryMb < this.policy.minimumToolMemoryMb) {
|
|
@@ -274,7 +274,8 @@ export class WeightedResourceGovernor {
|
|
|
274
274
|
weight,
|
|
275
275
|
memoryMb,
|
|
276
276
|
label,
|
|
277
|
-
queuedAt: this.now()
|
|
277
|
+
queuedAt: this.now(),
|
|
278
|
+
admission: { ignoreWorkerRss: admission?.ignoreWorkerRss === true }
|
|
278
279
|
};
|
|
279
280
|
if (!state.queue.length && this.canGrant(request)) {
|
|
280
281
|
return new Promise((resolve) => this.grant({ ...request, resolve }));
|