@pasko70/pibo 1.11.1 → 1.11.2
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/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/{pibo-vscode-ext-1.11.1.vsix → pibo-vscode-ext-1.11.2.vsix} +0 -0
- package/dist/core/gateway-resource-guard.js +51 -5
- package/dist/core/session-router.js +23 -12
- package/dist/debug/index.js +3 -1
- package/dist/reliability/store.js +19 -5
- package/dist/runs/registry.js +10 -7
- package/package.json +1 -1
|
Binary file
|
package/dist/apps/vscode-artifacts/{pibo-vscode-ext-1.11.1.vsix → pibo-vscode-ext-1.11.2.vsix}
RENAMED
|
Binary file
|
|
@@ -4,11 +4,13 @@ import { promisify } from "node:util";
|
|
|
4
4
|
import { getHeapStatistics } from "node:v8";
|
|
5
5
|
const execFileAsync = promisify(execFile);
|
|
6
6
|
const DEFAULT_POLICY = Object.freeze({
|
|
7
|
-
mode: "
|
|
7
|
+
mode: "block",
|
|
8
8
|
minFreeMemoryBytes: 256 * 1024 * 1024,
|
|
9
9
|
minHeapAvailableBytes: 64 * 1024 * 1024,
|
|
10
10
|
maxRssBytes: 1536 * 1024 * 1024,
|
|
11
11
|
knownDaemonWarningRssBytes: 2 * 1024 * 1024 * 1024,
|
|
12
|
+
maxConcurrentYieldedRuns: 1,
|
|
13
|
+
yieldedRunMemoryReservationBytes: 2 * 1024 * 1024 * 1024,
|
|
12
14
|
});
|
|
13
15
|
export function resolveGatewayResourceGuardPolicy(env = process.env) {
|
|
14
16
|
return {
|
|
@@ -17,6 +19,8 @@ export function resolveGatewayResourceGuardPolicy(env = process.env) {
|
|
|
17
19
|
minHeapAvailableBytes: parseByteThreshold(env.PIBO_GATEWAY_MIN_HEAP_AVAILABLE_BYTES, DEFAULT_POLICY.minHeapAvailableBytes),
|
|
18
20
|
maxRssBytes: parseByteThreshold(env.PIBO_GATEWAY_MAX_RSS_BYTES, DEFAULT_POLICY.maxRssBytes),
|
|
19
21
|
knownDaemonWarningRssBytes: parseByteThreshold(env.PIBO_GATEWAY_KNOWN_DAEMON_WARNING_RSS_BYTES, DEFAULT_POLICY.knownDaemonWarningRssBytes),
|
|
22
|
+
maxConcurrentYieldedRuns: parsePositiveInteger(env.PIBO_GATEWAY_MAX_CONCURRENT_YIELDED_RUNS, DEFAULT_POLICY.maxConcurrentYieldedRuns),
|
|
23
|
+
yieldedRunMemoryReservationBytes: parseByteThreshold(env.PIBO_GATEWAY_YIELDED_RUN_MEMORY_RESERVATION_BYTES, DEFAULT_POLICY.yieldedRunMemoryReservationBytes),
|
|
20
24
|
};
|
|
21
25
|
}
|
|
22
26
|
export function collectGatewayProcessMemory() {
|
|
@@ -74,8 +78,39 @@ export function assertGatewayResourceAvailableForWork(workLabel, env = process.e
|
|
|
74
78
|
const snapshot = buildGatewayResourceSnapshot({ env, includeProcesses: false });
|
|
75
79
|
if (snapshot.guardAction !== "block")
|
|
76
80
|
return;
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
throwGatewayResourceBlock(workLabel, snapshot.checks.filter((check) => check.severity === "critical").map((check) => check.message));
|
|
82
|
+
}
|
|
83
|
+
export class GatewayWorkAdmissionController {
|
|
84
|
+
activeReservations = new Set();
|
|
85
|
+
reserve(workLabel, env = process.env) {
|
|
86
|
+
const snapshot = buildGatewayResourceSnapshot({ env, includeProcesses: false });
|
|
87
|
+
const policy = snapshot.policy;
|
|
88
|
+
if (snapshot.guardAction === "block") {
|
|
89
|
+
throwGatewayResourceBlock(workLabel, snapshot.checks.filter((check) => check.severity === "critical").map((check) => check.message));
|
|
90
|
+
}
|
|
91
|
+
if (policy.mode === "block" && this.activeReservations.size >= policy.maxConcurrentYieldedRuns) {
|
|
92
|
+
throwGatewayResourceBlock(workLabel, [
|
|
93
|
+
`Active yielded runs ${this.activeReservations.size} reached the configured limit ${policy.maxConcurrentYieldedRuns}. Wait for an active run to settle or raise PIBO_GATEWAY_MAX_CONCURRENT_YIELDED_RUNS explicitly.`,
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
if (policy.mode === "block" &&
|
|
97
|
+
snapshot.host.freeBytes < policy.minFreeMemoryBytes + policy.yieldedRunMemoryReservationBytes) {
|
|
98
|
+
throwGatewayResourceBlock(workLabel, [
|
|
99
|
+
`Host free memory ${snapshot.host.freeBytes} cannot preserve reserve ${policy.minFreeMemoryBytes} after the yielded-run reservation ${policy.yieldedRunMemoryReservationBytes}.`,
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
const reservation = Symbol(workLabel);
|
|
103
|
+
this.activeReservations.add(reservation);
|
|
104
|
+
let released = false;
|
|
105
|
+
return {
|
|
106
|
+
release: () => {
|
|
107
|
+
if (released)
|
|
108
|
+
return;
|
|
109
|
+
released = true;
|
|
110
|
+
this.activeReservations.delete(reservation);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
79
114
|
}
|
|
80
115
|
export function parseHostProcessResourceList(output, gatewayPid, policy = DEFAULT_POLICY) {
|
|
81
116
|
const rows = [];
|
|
@@ -106,7 +141,7 @@ export function renderGatewayResourceSnapshotText(snapshot) {
|
|
|
106
141
|
lines.push(`Gateway PID: ${snapshot.gateway.pid}`);
|
|
107
142
|
lines.push(`Gateway memory: rss=${snapshot.gateway.rssBytes} heapUsed=${snapshot.gateway.heapUsedBytes} heapAvailable=${snapshot.gateway.heapAvailableBytes} heapLimit=${snapshot.gateway.heapLimitBytes}`);
|
|
108
143
|
lines.push(`Host memory: free=${snapshot.host.freeBytes} total=${snapshot.host.totalBytes}`);
|
|
109
|
-
lines.push(`Thresholds: minFree=${snapshot.policy.minFreeMemoryBytes} minHeapAvailable=${snapshot.policy.minHeapAvailableBytes} maxRss=${snapshot.policy.maxRssBytes} daemonWarnRss=${snapshot.policy.knownDaemonWarningRssBytes}`);
|
|
144
|
+
lines.push(`Thresholds: minFree=${snapshot.policy.minFreeMemoryBytes} minHeapAvailable=${snapshot.policy.minHeapAvailableBytes} maxRss=${snapshot.policy.maxRssBytes} daemonWarnRss=${snapshot.policy.knownDaemonWarningRssBytes} maxYieldedRuns=${snapshot.policy.maxConcurrentYieldedRuns} yieldedRunReservation=${snapshot.policy.yieldedRunMemoryReservationBytes}`);
|
|
110
145
|
lines.push(`Related processes: children=${snapshot.processes.children.length} knownDaemons=${snapshot.processes.knownDaemons.length} processList=${snapshot.processes.available ? "available" : "unavailable"}`);
|
|
111
146
|
if (snapshot.processes.error)
|
|
112
147
|
lines.push(`Process list error: ${snapshot.processes.error}`);
|
|
@@ -160,11 +195,13 @@ function processResultFromOptions(gatewayPid, options, policy) {
|
|
|
160
195
|
}
|
|
161
196
|
function parseMode(value, fallback) {
|
|
162
197
|
const normalized = value?.trim().toLowerCase();
|
|
198
|
+
if (normalized === undefined || normalized === "")
|
|
199
|
+
return fallback;
|
|
163
200
|
if (normalized === "off" || normalized === "0" || normalized === "false")
|
|
164
201
|
return "off";
|
|
165
202
|
if (normalized === "block" || normalized === "strict")
|
|
166
203
|
return "block";
|
|
167
|
-
if (normalized === "warn" || normalized === "1" || normalized === "true"
|
|
204
|
+
if (normalized === "warn" || normalized === "1" || normalized === "true")
|
|
168
205
|
return "warn";
|
|
169
206
|
return fallback;
|
|
170
207
|
}
|
|
@@ -174,6 +211,15 @@ function parseByteThreshold(value, fallback) {
|
|
|
174
211
|
const parsed = Number(value);
|
|
175
212
|
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : fallback;
|
|
176
213
|
}
|
|
214
|
+
function parsePositiveInteger(value, fallback) {
|
|
215
|
+
if (value === undefined || value.trim() === "")
|
|
216
|
+
return fallback;
|
|
217
|
+
const parsed = Number(value);
|
|
218
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
219
|
+
}
|
|
220
|
+
function throwGatewayResourceBlock(workLabel, reasons) {
|
|
221
|
+
throw new Error(`Gateway resource guard blocked ${workLabel} before starting: ${reasons.join("; ")}`);
|
|
222
|
+
}
|
|
177
223
|
function knownDaemonLabel(commandName, args) {
|
|
178
224
|
const combined = `${commandName} ${args}`;
|
|
179
225
|
if (/comfyui|main\.py.*--port\s+8188/i.test(combined))
|
|
@@ -16,7 +16,7 @@ import { loadPiboUserSettings } from "./user-settings.js";
|
|
|
16
16
|
import { resolvePiboSessionActiveModel } from "./session-model.js";
|
|
17
17
|
import { isPiboThinkingLevel } from "./thinking.js";
|
|
18
18
|
import { RuntimeSessionRegistry } from "../tools/runtime/registry.js";
|
|
19
|
-
import {
|
|
19
|
+
import { GatewayWorkAdmissionController } from "./gateway-resource-guard.js";
|
|
20
20
|
import { withWorkflowSessionKind } from "../sessions/workflow-session-kind.js";
|
|
21
21
|
import { PiboRuntimeTelemetryRecorder } from "./runtime-telemetry.js";
|
|
22
22
|
import { createPiboProviderTelemetryExtension } from "./provider-telemetry.js";
|
|
@@ -143,6 +143,7 @@ export class PiboSessionRouter {
|
|
|
143
143
|
pendingSessions = new Map();
|
|
144
144
|
listeners = new Set();
|
|
145
145
|
runRegistry;
|
|
146
|
+
gatewayWorkAdmission = new GatewayWorkAdmissionController();
|
|
146
147
|
signalRegistry;
|
|
147
148
|
runtimeRegistry;
|
|
148
149
|
scheduledRunReminders = new Map();
|
|
@@ -697,17 +698,24 @@ export class PiboSessionRouter {
|
|
|
697
698
|
createRunToolController(parentPiboSessionId) {
|
|
698
699
|
return {
|
|
699
700
|
startToolRun: ({ toolName, params, completionPolicy, retryable, maxAttempts, timeoutMs, serviceWarning, execute }) => {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
701
|
+
const admission = this.gatewayWorkAdmission.reserve(`yielded run ${toolName}`);
|
|
702
|
+
let run;
|
|
703
|
+
try {
|
|
704
|
+
run = this.runRegistry.startToolRun({
|
|
705
|
+
controllerPiboSessionId: parentPiboSessionId,
|
|
706
|
+
toolName,
|
|
707
|
+
params,
|
|
708
|
+
completionPolicy,
|
|
709
|
+
retryable,
|
|
710
|
+
maxAttempts,
|
|
711
|
+
timeoutMs,
|
|
712
|
+
serviceWarning,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
catch (error) {
|
|
716
|
+
admission.release();
|
|
717
|
+
throw error;
|
|
718
|
+
}
|
|
711
719
|
void (async () => {
|
|
712
720
|
try {
|
|
713
721
|
const result = await execute();
|
|
@@ -723,6 +731,9 @@ export class PiboSessionRouter {
|
|
|
723
731
|
if (terminalRun)
|
|
724
732
|
this.scheduleRunReminder(parentPiboSessionId, false);
|
|
725
733
|
}
|
|
734
|
+
finally {
|
|
735
|
+
admission.release();
|
|
736
|
+
}
|
|
726
737
|
})();
|
|
727
738
|
return run;
|
|
728
739
|
},
|
package/dist/debug/index.js
CHANGED
|
@@ -1058,11 +1058,13 @@ Reports:
|
|
|
1058
1058
|
Gateway RSS/heap headroom, host free-memory reserve, direct child processes, and known heavy local daemons such as ComfyUI or Unity when process listing is available.
|
|
1059
1059
|
|
|
1060
1060
|
Environment:
|
|
1061
|
-
PIBO_GATEWAY_RESOURCE_GUARD=warn|block
|
|
1061
|
+
PIBO_GATEWAY_RESOURCE_GUARD=block|warn|off (default: block)
|
|
1062
1062
|
PIBO_GATEWAY_MIN_FREE_MEMORY_BYTES=<bytes>
|
|
1063
1063
|
PIBO_GATEWAY_MIN_HEAP_AVAILABLE_BYTES=<bytes>
|
|
1064
1064
|
PIBO_GATEWAY_MAX_RSS_BYTES=<bytes>
|
|
1065
1065
|
PIBO_GATEWAY_KNOWN_DAEMON_WARNING_RSS_BYTES=<bytes>
|
|
1066
|
+
PIBO_GATEWAY_MAX_CONCURRENT_YIELDED_RUNS=<count> (default: 1)
|
|
1067
|
+
PIBO_GATEWAY_YIELDED_RUN_MEMORY_RESERVATION_BYTES=<bytes> (default: 2147483648)
|
|
1066
1068
|
|
|
1067
1069
|
Next:
|
|
1068
1070
|
pibo debug resources --json
|
|
@@ -500,7 +500,7 @@ export class PiboReliabilityStore {
|
|
|
500
500
|
) VALUES (?, 'tool', ?, 'running', ?, 0, ?, ?, NULL, NULL, NULL, NULL, ?, ?, NULL, ?, ?, ?, ?, ?, NULL, ?)
|
|
501
501
|
`)
|
|
502
502
|
.run(runId, input.controllerPiboSessionId, input.completionPolicy, input.toolName, `${input.toolName} run is running.`, timestamp, timestamp, job.jobId, input.retryable ? 1 : 0, maxAttempts, input.timeoutMs ?? null, timeoutAt ?? null, input.serviceWarning ?? null);
|
|
503
|
-
this.claimJob(job.jobId, `run-registry:${process.pid}`, 24 * 60 * 60 * 1000);
|
|
503
|
+
this.claimJob(job.jobId, input.workerId ?? `run-registry:${process.pid}`, 24 * 60 * 60 * 1000);
|
|
504
504
|
return this.requireRun(runId);
|
|
505
505
|
}
|
|
506
506
|
updateRun(runId, patch) {
|
|
@@ -572,14 +572,27 @@ export class PiboReliabilityStore {
|
|
|
572
572
|
const result = this.db.prepare(`DELETE FROM pibo_runs WHERE run_id IN (${placeholders})`).run(...ids);
|
|
573
573
|
return Number(result.changes ?? 0);
|
|
574
574
|
}
|
|
575
|
-
recoverInterruptedRuns() {
|
|
575
|
+
recoverInterruptedRuns(workerId = `run-registry:${process.pid}`) {
|
|
576
576
|
const rows = this.db.prepare("SELECT * FROM pibo_runs WHERE status = 'running'").all();
|
|
577
577
|
const recovered = [];
|
|
578
578
|
const timestamp = now();
|
|
579
579
|
for (const row of rows) {
|
|
580
|
-
if (row.job_id && this.hasUnexpiredJobClaim(row.job_id, timestamp))
|
|
580
|
+
if (row.job_id && this.hasUnexpiredJobClaim(row.job_id, timestamp, workerId))
|
|
581
581
|
continue;
|
|
582
582
|
const run = runFromRow(row);
|
|
583
|
+
if (run.timeoutAt && run.timeoutAt <= timestamp) {
|
|
584
|
+
const error = `Run deadline ${run.timeoutAt} elapsed before the interrupted runtime recovered.`;
|
|
585
|
+
if (run.jobId)
|
|
586
|
+
this.moveLiveJobToDead(run.jobId, error, "timeout", timestamp);
|
|
587
|
+
recovered.push(this.updateRun(run.runId, {
|
|
588
|
+
status: "timed_out",
|
|
589
|
+
error,
|
|
590
|
+
timeoutPhase: "lifetime",
|
|
591
|
+
summary: `${run.toolName} run started successfully, then reached its configured timeout.`,
|
|
592
|
+
completedAt: timestamp,
|
|
593
|
+
}) ?? run);
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
583
596
|
if (run.retryable && run.maxAttempts > 1) {
|
|
584
597
|
if (run.jobId)
|
|
585
598
|
this.releaseJobForRetry(run.jobId, timestamp);
|
|
@@ -653,16 +666,17 @@ export class PiboReliabilityStore {
|
|
|
653
666
|
for (const row of expired)
|
|
654
667
|
this.moveJobToDead(row, row.last_error ?? "Job expired.", "expired", timestamp);
|
|
655
668
|
}
|
|
656
|
-
hasUnexpiredJobClaim(jobId, timestamp) {
|
|
669
|
+
hasUnexpiredJobClaim(jobId, timestamp, workerId) {
|
|
657
670
|
const row = this.db
|
|
658
671
|
.prepare(`
|
|
659
672
|
SELECT job_id FROM pibo_jobs
|
|
660
673
|
WHERE job_id = ?
|
|
661
674
|
AND state = 'running'
|
|
675
|
+
AND worker_id = ?
|
|
662
676
|
AND claim_expires_at IS NOT NULL
|
|
663
677
|
AND claim_expires_at > ?
|
|
664
678
|
`)
|
|
665
|
-
.get(jobId, timestamp);
|
|
679
|
+
.get(jobId, workerId, timestamp);
|
|
666
680
|
return row !== undefined;
|
|
667
681
|
}
|
|
668
682
|
releaseJobForRetry(jobId, timestamp) {
|
package/dist/runs/registry.js
CHANGED
|
@@ -46,14 +46,16 @@ export class PiboRunRegistry {
|
|
|
46
46
|
runs = new Map();
|
|
47
47
|
waiters = new Map();
|
|
48
48
|
listeners = new Set();
|
|
49
|
+
workerId;
|
|
49
50
|
subscribe(listener) {
|
|
50
51
|
this.listeners.add(listener);
|
|
51
52
|
return () => this.listeners.delete(listener);
|
|
52
53
|
}
|
|
53
54
|
constructor(options = {}) {
|
|
54
55
|
this.options = options;
|
|
56
|
+
this.workerId = options.workerId ?? `run-registry:${process.pid}:${randomUUID()}`;
|
|
55
57
|
if (this.options.store) {
|
|
56
|
-
this.options.store.recoverInterruptedRuns();
|
|
58
|
+
this.options.store.recoverInterruptedRuns(this.workerId);
|
|
57
59
|
for (const record of this.options.store.listRuns({ includeConsumed: true, includeDetached: true })) {
|
|
58
60
|
this.runs.set(record.runId, recordFromStored(record));
|
|
59
61
|
}
|
|
@@ -71,6 +73,7 @@ export class PiboRunRegistry {
|
|
|
71
73
|
maxAttempts: input.maxAttempts ?? 1,
|
|
72
74
|
timeoutMs: input.timeoutMs,
|
|
73
75
|
serviceWarning: input.serviceWarning,
|
|
76
|
+
workerId: this.workerId,
|
|
74
77
|
});
|
|
75
78
|
const record = recordFromStored(stored);
|
|
76
79
|
this.runs.set(record.runId, record);
|
|
@@ -112,7 +115,7 @@ export class PiboRunRegistry {
|
|
|
112
115
|
this.finish(record);
|
|
113
116
|
this.options.store?.updateRun(runId, record);
|
|
114
117
|
if (record.jobId)
|
|
115
|
-
this.options.store?.ack(record.jobId,
|
|
118
|
+
this.options.store?.ack(record.jobId, this.workerId);
|
|
116
119
|
const output = snapshot(record);
|
|
117
120
|
this.notify({ type: "run_changed", run: output, previousStatus });
|
|
118
121
|
return output;
|
|
@@ -128,7 +131,7 @@ export class PiboRunRegistry {
|
|
|
128
131
|
this.finish(record);
|
|
129
132
|
this.options.store?.updateRun(runId, record);
|
|
130
133
|
if (record.jobId)
|
|
131
|
-
this.options.store?.fail(record.jobId,
|
|
134
|
+
this.options.store?.fail(record.jobId, this.workerId, error);
|
|
132
135
|
const output = snapshot(record);
|
|
133
136
|
this.notify({ type: "run_changed", run: output, previousStatus, reason: error });
|
|
134
137
|
return output;
|
|
@@ -147,7 +150,7 @@ export class PiboRunRegistry {
|
|
|
147
150
|
this.finish(record);
|
|
148
151
|
this.options.store?.updateRun(runId, record);
|
|
149
152
|
if (record.jobId)
|
|
150
|
-
this.options.store?.fail(record.jobId,
|
|
153
|
+
this.options.store?.fail(record.jobId, this.workerId, error);
|
|
151
154
|
const output = snapshot(record);
|
|
152
155
|
this.notify({ type: "run_changed", run: output, previousStatus, reason: error });
|
|
153
156
|
return output;
|
|
@@ -227,7 +230,7 @@ export class PiboRunRegistry {
|
|
|
227
230
|
record.summary = `${record.toolName} run cancelled.`;
|
|
228
231
|
this.finish(record);
|
|
229
232
|
if (record.jobId)
|
|
230
|
-
this.options.store?.fail(record.jobId,
|
|
233
|
+
this.options.store?.fail(record.jobId, this.workerId, "Run was cancelled.");
|
|
231
234
|
}
|
|
232
235
|
record.consumed = true;
|
|
233
236
|
record.updatedAt = now();
|
|
@@ -292,7 +295,7 @@ export class PiboRunRegistry {
|
|
|
292
295
|
this.finish(record);
|
|
293
296
|
this.options.store?.updateRun(record.runId, record);
|
|
294
297
|
if (record.jobId)
|
|
295
|
-
this.options.store?.fail(record.jobId,
|
|
298
|
+
this.options.store?.fail(record.jobId, this.workerId, reason);
|
|
296
299
|
const output = snapshot(record);
|
|
297
300
|
this.notify({ type: "run_changed", run: output, previousStatus: "running", reason });
|
|
298
301
|
cancelled.push(output);
|
|
@@ -311,7 +314,7 @@ export class PiboRunRegistry {
|
|
|
311
314
|
this.finish(record);
|
|
312
315
|
this.options.store?.updateRun(record.runId, record);
|
|
313
316
|
if (record.jobId)
|
|
314
|
-
this.options.store?.fail(record.jobId,
|
|
317
|
+
this.options.store?.fail(record.jobId, this.workerId, reason);
|
|
315
318
|
const output = snapshot(record);
|
|
316
319
|
this.notify({ type: "run_changed", run: output, previousStatus: "running", reason });
|
|
317
320
|
cancelled.push(output);
|