acpx 0.19.0 → 0.19.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/{agent-registry-Ct2yWPW7.js → agent-registry-DWlx5BQ4.js} +3 -2
- package/dist/agent-registry-DWlx5BQ4.js.map +1 -0
- package/dist/agent-registry.js +1 -1
- package/dist/{cli-Bmki9cj5.js → cli-D-s_eTUy.js} +5 -5
- package/dist/{cli-Bmki9cj5.js.map → cli-D-s_eTUy.js.map} +1 -1
- package/dist/cli.js +31 -15
- package/dist/cli.js.map +1 -1
- package/dist/{client-DzaUKazo.d.ts → client-BchvQRpz.d.ts} +8 -1
- package/dist/client-BchvQRpz.d.ts.map +1 -0
- package/dist/{flows--Ewxn_fQ.js → flows-DdxEaeZo.js} +33 -20
- package/dist/flows-DdxEaeZo.js.map +1 -0
- package/dist/flows.d.ts +2 -1
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +1 -1
- package/dist/{invocation-options-DRzxHZmr.js → invocation-options-HjxSBsK0.js} +5 -5
- package/dist/{invocation-options-DRzxHZmr.js.map → invocation-options-HjxSBsK0.js.map} +1 -1
- package/dist/{ipc-fMgj5vrO.js → ipc-D0ZMQ0q9.js} +1393 -86
- package/dist/ipc-D0ZMQ0q9.js.map +1 -0
- package/dist/{output-D6kDs8BN.js → output-GQnhkJMU.js} +20 -12
- package/dist/output-GQnhkJMU.js.map +1 -0
- package/dist/{queue-owner-runtime-CLwjMYNL.js → queue-owner-runtime-W0lly6ap.js} +344 -777
- package/dist/queue-owner-runtime-W0lly6ap.js.map +1 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +5 -5
- package/dist/runtime.js.map +1 -1
- package/dist/{session-watch-BfdIo-n4.js → session-watch-_8yxQ6ba.js} +4 -4
- package/dist/{session-watch-BfdIo-n4.js.map → session-watch-_8yxQ6ba.js.map} +1 -1
- package/dist/watch-C0mwpJcN.js +85 -0
- package/dist/watch-C0mwpJcN.js.map +1 -0
- package/package.json +1 -1
- package/skills/acpx/SKILL.md +2 -0
- package/dist/agent-registry-Ct2yWPW7.js.map +0 -1
- package/dist/client-DzaUKazo.d.ts.map +0 -1
- package/dist/flows--Ewxn_fQ.js.map +0 -1
- package/dist/ipc-fMgj5vrO.js.map +0 -1
- package/dist/output-D6kDs8BN.js.map +0 -1
- package/dist/queue-owner-runtime-CLwjMYNL.js.map +0 -1
- package/dist/watch-DH-zXQH2.js +0 -32
- package/dist/watch-DH-zXQH2.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { statSync } from "node:fs";
|
|
1
|
+
import { C as PROCESS_HELPER_TIMEOUT_MS, E as isChildProcessRunning, N as runTimedExecFile, S as PROCESS_HELPER_MAX_BUFFER_BYTES, n as AGENT_REGISTRY, r as BUILT_IN_AGENT_PACKAGES, t as AGENT_ARGV_REGISTRY } from "./agent-registry-DWlx5BQ4.js";
|
|
2
|
+
import fs, { statSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import fs$1 from "node:fs/promises";
|
|
5
5
|
import os from "node:os";
|
|
@@ -11,6 +11,7 @@ import { createHash, randomInt, randomUUID } from "node:crypto";
|
|
|
11
11
|
import { isHardlinkFallbackError } from "@openclaw/fs-safe/durability";
|
|
12
12
|
import { acquireFileLock } from "@openclaw/fs-safe/file-lock";
|
|
13
13
|
import net from "node:net";
|
|
14
|
+
import { resolveSecureTempRoot } from "@openclaw/fs-safe/secure-temp-root";
|
|
14
15
|
//#region src/errors.ts
|
|
15
16
|
var AcpxOperationalError = class extends Error {
|
|
16
17
|
outputCode;
|
|
@@ -231,12 +232,12 @@ const SESSION_RECORD_SCHEMA = "acpx.session.v1";
|
|
|
231
232
|
//#endregion
|
|
232
233
|
//#region src/acp/error-shapes.ts
|
|
233
234
|
const RESOURCE_NOT_FOUND_ACP_CODES = /* @__PURE__ */ new Set([-32001, -32002]);
|
|
234
|
-
function asRecord$
|
|
235
|
+
function asRecord$5(value) {
|
|
235
236
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
236
237
|
return value;
|
|
237
238
|
}
|
|
238
239
|
function toAcpErrorPayload(value) {
|
|
239
|
-
const record = asRecord$
|
|
240
|
+
const record = asRecord$5(value);
|
|
240
241
|
if (!record) return;
|
|
241
242
|
if (typeof record.code !== "number" || !Number.isFinite(record.code)) return;
|
|
242
243
|
if (typeof record.message !== "string" || record.message.length === 0) return;
|
|
@@ -250,7 +251,7 @@ function extractAcpErrorInternal(value, depth) {
|
|
|
250
251
|
if (depth > 5) return;
|
|
251
252
|
const direct = toAcpErrorPayload(value);
|
|
252
253
|
if (direct) return direct;
|
|
253
|
-
const record = asRecord$
|
|
254
|
+
const record = asRecord$5(value);
|
|
254
255
|
if (!record) return;
|
|
255
256
|
return extractNestedAcpError(record, depth);
|
|
256
257
|
}
|
|
@@ -285,7 +286,7 @@ function hasSessionNotFoundHint(value, depth = 0) {
|
|
|
285
286
|
if (depth > 4) return false;
|
|
286
287
|
if (isSessionNotFoundText(value)) return true;
|
|
287
288
|
if (Array.isArray(value)) return value.some((entry) => hasSessionNotFoundHint(entry, depth + 1));
|
|
288
|
-
const record = asRecord$
|
|
289
|
+
const record = asRecord$5(value);
|
|
289
290
|
if (!record) return false;
|
|
290
291
|
return Object.values(record).some((entry) => hasSessionNotFoundHint(entry, depth + 1));
|
|
291
292
|
}
|
|
@@ -302,6 +303,136 @@ function isAcpResourceNotFoundError(error) {
|
|
|
302
303
|
return isSessionNotFoundText(formatUnknownErrorMessage(error));
|
|
303
304
|
}
|
|
304
305
|
//#endregion
|
|
306
|
+
//#region src/acp/error-normalization.ts
|
|
307
|
+
const AUTH_REQUIRED_ACP_CODES = /* @__PURE__ */ new Set([-32e3]);
|
|
308
|
+
const QUERY_CLOSED_BEFORE_RESPONSE_DETAIL = "query closed before response received";
|
|
309
|
+
function asRecord$4(value) {
|
|
310
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
311
|
+
return value;
|
|
312
|
+
}
|
|
313
|
+
function isAuthRequiredMessage(value) {
|
|
314
|
+
if (!value) return false;
|
|
315
|
+
const normalized = value.toLowerCase();
|
|
316
|
+
return [
|
|
317
|
+
"auth required",
|
|
318
|
+
"authentication required",
|
|
319
|
+
"authorization required",
|
|
320
|
+
"credential required",
|
|
321
|
+
"credentials required",
|
|
322
|
+
"token required",
|
|
323
|
+
"login required"
|
|
324
|
+
].some((needle) => normalized.includes(needle));
|
|
325
|
+
}
|
|
326
|
+
function isAcpAuthRequiredPayload(acp) {
|
|
327
|
+
if (!acp) return false;
|
|
328
|
+
if (!AUTH_REQUIRED_ACP_CODES.has(acp.code)) return false;
|
|
329
|
+
if (isAuthRequiredMessage(acp.message)) return true;
|
|
330
|
+
const data = asRecord$4(acp.data);
|
|
331
|
+
if (!data) return false;
|
|
332
|
+
return hasAuthRequiredData(data);
|
|
333
|
+
}
|
|
334
|
+
function hasAuthRequiredData(data) {
|
|
335
|
+
return data.authRequired === true || hasNonEmptyString(data.methodId) || hasNonEmptyArray(data.methods);
|
|
336
|
+
}
|
|
337
|
+
function hasNonEmptyString(value) {
|
|
338
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
339
|
+
}
|
|
340
|
+
function hasNonEmptyArray(value) {
|
|
341
|
+
return Array.isArray(value) && value.length > 0;
|
|
342
|
+
}
|
|
343
|
+
function isOutputErrorCode$1(value) {
|
|
344
|
+
return typeof value === "string" && OUTPUT_ERROR_CODES.includes(value);
|
|
345
|
+
}
|
|
346
|
+
function isOutputErrorOrigin$1(value) {
|
|
347
|
+
return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
|
|
348
|
+
}
|
|
349
|
+
function readOutputErrorMeta(error) {
|
|
350
|
+
const record = asRecord$4(error);
|
|
351
|
+
if (!record) return {};
|
|
352
|
+
return {
|
|
353
|
+
outputCode: isOutputErrorCode$1(record.outputCode) ? record.outputCode : void 0,
|
|
354
|
+
detailCode: typeof record.detailCode === "string" && record.detailCode.trim().length > 0 ? record.detailCode : void 0,
|
|
355
|
+
origin: isOutputErrorOrigin$1(record.origin) ? record.origin : void 0,
|
|
356
|
+
retryable: typeof record.retryable === "boolean" ? record.retryable : void 0,
|
|
357
|
+
acp: extractAcpError(record.acp)
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function isTimeoutLike(error) {
|
|
361
|
+
return error instanceof Error && error.name === "TimeoutError";
|
|
362
|
+
}
|
|
363
|
+
function isNoSessionLike(error) {
|
|
364
|
+
return error instanceof Error && error.name === "NoSessionError";
|
|
365
|
+
}
|
|
366
|
+
function isUsageLike(error) {
|
|
367
|
+
if (!(error instanceof Error)) return false;
|
|
368
|
+
return error.name === "CommanderError" || error.name === "InvalidArgumentError" || asRecord$4(error)?.code === "commander.invalidArgument";
|
|
369
|
+
}
|
|
370
|
+
function formatErrorMessage(error) {
|
|
371
|
+
return formatUnknownErrorMessage(error);
|
|
372
|
+
}
|
|
373
|
+
function isAcpQueryClosedBeforeResponseError(error) {
|
|
374
|
+
const acp = extractAcpError(error);
|
|
375
|
+
if (!acp || acp.code !== -32603) return false;
|
|
376
|
+
const details = asRecord$4(acp.data)?.details;
|
|
377
|
+
if (typeof details !== "string") return false;
|
|
378
|
+
return details.toLowerCase().includes(QUERY_CLOSED_BEFORE_RESPONSE_DETAIL);
|
|
379
|
+
}
|
|
380
|
+
function mapErrorCode(error) {
|
|
381
|
+
if (error instanceof PermissionPromptUnavailableError) return "PERMISSION_PROMPT_UNAVAILABLE";
|
|
382
|
+
if (error instanceof PermissionDeniedError) return "PERMISSION_DENIED";
|
|
383
|
+
if (isTimeoutLike(error)) return "TIMEOUT";
|
|
384
|
+
if (isNoSessionLike(error) || isAcpResourceNotFoundError(error)) return "NO_SESSION";
|
|
385
|
+
if (isUsageLike(error)) return "USAGE";
|
|
386
|
+
}
|
|
387
|
+
function normalizeOutputError(error, options = {}) {
|
|
388
|
+
const meta = readOutputErrorMeta(error);
|
|
389
|
+
const code = resolveOutputErrorCode(error, options, meta);
|
|
390
|
+
const acp = options.acp ?? meta.acp ?? extractAcpError(error);
|
|
391
|
+
return {
|
|
392
|
+
code,
|
|
393
|
+
message: formatErrorMessage(error),
|
|
394
|
+
detailCode: resolveDetailCode(error, acp, options, meta),
|
|
395
|
+
origin: meta.origin ?? options.origin,
|
|
396
|
+
retryable: meta.retryable ?? options.retryable,
|
|
397
|
+
acp
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
function resolveOutputErrorCode(error, options, meta) {
|
|
401
|
+
const code = meta.outputCode ?? mapErrorCode(error) ?? options.defaultCode ?? "RUNTIME";
|
|
402
|
+
if (code === "RUNTIME" && isAcpResourceNotFoundError(error)) return "NO_SESSION";
|
|
403
|
+
return code;
|
|
404
|
+
}
|
|
405
|
+
function resolveDetailCode(error, acp, options, meta) {
|
|
406
|
+
return meta.detailCode ?? options.detailCode ?? (error instanceof AuthPolicyError || isAcpAuthRequiredPayload(acp) ? "AUTH_REQUIRED" : void 0);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Returns true when an error from `client.prompt()` looks transient and
|
|
410
|
+
* can reasonably be retried (e.g. model-API 400/500, network hiccups that
|
|
411
|
+
* surface as ACP internal errors).
|
|
412
|
+
*
|
|
413
|
+
* Errors that are definitively non-recoverable (auth, missing session,
|
|
414
|
+
* invalid params, timeout, permission) return false.
|
|
415
|
+
*/
|
|
416
|
+
function isRetryablePromptError(error) {
|
|
417
|
+
if (isNonRetryablePromptError(error)) return false;
|
|
418
|
+
const acp = extractAcpError(error);
|
|
419
|
+
if (!acp) return false;
|
|
420
|
+
return acp.code === -32603 || acp.code === -32700;
|
|
421
|
+
}
|
|
422
|
+
function isNonRetryablePromptError(error) {
|
|
423
|
+
return error instanceof PermissionDeniedError || error instanceof PermissionPromptUnavailableError || isTimeoutLike(error) || isNoSessionLike(error) || isUsageLike(error);
|
|
424
|
+
}
|
|
425
|
+
function exitCodeForOutputErrorCode(code) {
|
|
426
|
+
switch (code) {
|
|
427
|
+
case "USAGE": return EXIT_CODES.USAGE;
|
|
428
|
+
case "TIMEOUT": return EXIT_CODES.TIMEOUT;
|
|
429
|
+
case "NO_SESSION": return EXIT_CODES.NO_SESSION;
|
|
430
|
+
case "PERMISSION_DENIED":
|
|
431
|
+
case "PERMISSION_PROMPT_UNAVAILABLE": return EXIT_CODES.PERMISSION_DENIED;
|
|
432
|
+
default: return EXIT_CODES.ERROR;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
//#endregion
|
|
305
436
|
//#region src/process-liveness.ts
|
|
306
437
|
function isProcessAlive(pid) {
|
|
307
438
|
if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
|
|
@@ -686,7 +817,15 @@ function parseCursor(recordId, cursor) {
|
|
|
686
817
|
return parsed.data[1];
|
|
687
818
|
}
|
|
688
819
|
function fileIdentity(stat) {
|
|
689
|
-
return `${stat.dev}:${stat.ino}:${stat.
|
|
820
|
+
return `${stat.dev}:${stat.ino}:${stat.birthtimeNs}`;
|
|
821
|
+
}
|
|
822
|
+
async function pathIdentity(filePath) {
|
|
823
|
+
try {
|
|
824
|
+
return fileIdentity(await fs$1.lstat(filePath, { bigint: true }));
|
|
825
|
+
} catch (error) {
|
|
826
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return;
|
|
827
|
+
throw error;
|
|
828
|
+
}
|
|
690
829
|
}
|
|
691
830
|
function emptySegment() {
|
|
692
831
|
return {
|
|
@@ -718,12 +857,9 @@ var SessionJournalReader = class {
|
|
|
718
857
|
const segments = [];
|
|
719
858
|
try {
|
|
720
859
|
const paths = await this.openPresentSegments(segments);
|
|
721
|
-
const current = await Promise.all(paths.map(
|
|
860
|
+
const current = await Promise.all(paths.map(pathIdentity));
|
|
722
861
|
const identities = new Map(segments.map((entry) => [entry.filePath, entry.identity]));
|
|
723
|
-
if (paths.every((filePath, index) =>
|
|
724
|
-
const entry = current[index];
|
|
725
|
-
return (entry.missing ? void 0 : fileIdentity(entry.stat)) === identities.get(filePath);
|
|
726
|
-
})) return segments;
|
|
862
|
+
if (paths.every((filePath, index) => current[index] === identities.get(filePath))) return segments;
|
|
727
863
|
} catch (error) {
|
|
728
864
|
if (!retryableSnapshotError(error)) {
|
|
729
865
|
await this.closeSnapshot(segments);
|
|
@@ -744,11 +880,16 @@ var SessionJournalReader = class {
|
|
|
744
880
|
symlinks: "reject",
|
|
745
881
|
nonBlockingRead: true
|
|
746
882
|
});
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
883
|
+
try {
|
|
884
|
+
return {
|
|
885
|
+
filePath,
|
|
886
|
+
opened,
|
|
887
|
+
identity: fileIdentity(await opened.handle.stat({ bigint: true }))
|
|
888
|
+
};
|
|
889
|
+
} catch (error) {
|
|
890
|
+
await opened[Symbol.asyncDispose]();
|
|
891
|
+
throw error;
|
|
892
|
+
}
|
|
752
893
|
}));
|
|
753
894
|
let failure;
|
|
754
895
|
for (const result of results) if (result.status === "fulfilled") segments.push(result.value);
|
|
@@ -978,6 +1119,7 @@ async function* iterateJournal(record, options) {
|
|
|
978
1119
|
let after = options.cursor === void 0 ? -1 : parseCursor(record.acpxRecordId, options.cursor);
|
|
979
1120
|
const reader = new SessionJournalReader(record);
|
|
980
1121
|
let first = true;
|
|
1122
|
+
const observation = {};
|
|
981
1123
|
for (;;) {
|
|
982
1124
|
if (signal.aborted) return;
|
|
983
1125
|
const snapshot = await settleUnlessAborted(reader.read(signal, {
|
|
@@ -992,16 +1134,34 @@ async function* iterateJournal(record, options) {
|
|
|
992
1134
|
after = sequence;
|
|
993
1135
|
yield event;
|
|
994
1136
|
}
|
|
995
|
-
if (!await continueAfterPage(record, snapshot, options)) return;
|
|
1137
|
+
if (!await continueAfterPage(record, snapshot, options, observation)) return;
|
|
996
1138
|
}
|
|
997
1139
|
}
|
|
998
|
-
async function continueAfterPage(record, snapshot, options) {
|
|
1140
|
+
async function continueAfterPage(record, snapshot, options, observation) {
|
|
1141
|
+
if (options.signal.aborted || confirmsTerminalObservation(snapshot, observation)) return false;
|
|
999
1142
|
if (!snapshot.hasMore && options.continueWatching) {
|
|
1000
|
-
|
|
1143
|
+
const decision = await settleUnlessAborted(options.continueWatching(record, snapshot.requestId), options.signal).catch((error) => {
|
|
1144
|
+
if (error instanceof SessionWatchError && error.code === "WATCH_OUTCOME_UNKNOWN") return error;
|
|
1145
|
+
throw error;
|
|
1146
|
+
});
|
|
1147
|
+
if (decision !== true) {
|
|
1148
|
+
observation.terminal = {
|
|
1149
|
+
requestId: snapshot.requestId,
|
|
1150
|
+
error: decision instanceof SessionWatchError ? decision : void 0
|
|
1151
|
+
};
|
|
1152
|
+
return !options.signal.aborted;
|
|
1153
|
+
}
|
|
1001
1154
|
}
|
|
1002
1155
|
await settleUnlessAborted(setTimeout$1(snapshot.hasMore ? 0 : 100, void 0, { signal: options.signal }), options.signal);
|
|
1003
1156
|
return !options.signal.aborted;
|
|
1004
1157
|
}
|
|
1158
|
+
function confirmsTerminalObservation(snapshot, observation) {
|
|
1159
|
+
const terminal = observation.terminal;
|
|
1160
|
+
delete observation.terminal;
|
|
1161
|
+
if (!terminal || snapshot.events.length > 0 || snapshot.hasMore || snapshot.requestId !== terminal.requestId) return false;
|
|
1162
|
+
if (terminal.error) throw terminal.error;
|
|
1163
|
+
return true;
|
|
1164
|
+
}
|
|
1005
1165
|
function assertRetainedCursor(after, snapshot, first) {
|
|
1006
1166
|
if (after < 0) return;
|
|
1007
1167
|
if (after > snapshot.sequence) throw new SessionWatchError(first ? "WATCH_CURSOR_FUTURE" : "WATCH_CURSOR_EXPIRED", "Cursor is beyond the available session journal");
|
|
@@ -1727,17 +1887,18 @@ async function readSessionRecord(sessionId) {
|
|
|
1727
1887
|
return;
|
|
1728
1888
|
}
|
|
1729
1889
|
}
|
|
1730
|
-
function
|
|
1890
|
+
function hasGitMarker(dir) {
|
|
1731
1891
|
const gitPath = path.join(dir, ".git");
|
|
1732
1892
|
try {
|
|
1733
|
-
|
|
1893
|
+
const marker = statSync(gitPath);
|
|
1894
|
+
return marker.isDirectory() || marker.isFile();
|
|
1734
1895
|
} catch {
|
|
1735
1896
|
return false;
|
|
1736
1897
|
}
|
|
1737
1898
|
}
|
|
1738
1899
|
function isWithinBoundary(boundary, target) {
|
|
1739
1900
|
const relative = path.relative(boundary, target);
|
|
1740
|
-
return relative
|
|
1901
|
+
return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
1741
1902
|
}
|
|
1742
1903
|
function absolutePath(value) {
|
|
1743
1904
|
return path.resolve(value);
|
|
@@ -1746,7 +1907,7 @@ function findGitRepositoryRoot(startDir) {
|
|
|
1746
1907
|
let current = absolutePath(startDir);
|
|
1747
1908
|
const root = path.parse(current).root;
|
|
1748
1909
|
for (;;) {
|
|
1749
|
-
if (
|
|
1910
|
+
if (hasGitMarker(current)) return current;
|
|
1750
1911
|
if (current === root) return;
|
|
1751
1912
|
const parent = path.dirname(current);
|
|
1752
1913
|
if (parent === current) return;
|
|
@@ -2052,6 +2213,39 @@ const LINUX_PROCESS_QUERY = `
|
|
|
2052
2213
|
if (error.code === 'ENOENT') return 'unsupported';
|
|
2053
2214
|
throw error;
|
|
2054
2215
|
});
|
|
2216
|
+
const prefix = async (name, size) => {
|
|
2217
|
+
const file = await fs.open(name, 'r');
|
|
2218
|
+
try {
|
|
2219
|
+
const buffer = Buffer.alloc(size);
|
|
2220
|
+
const { bytesRead } = await file.read(buffer, 0, size, 0);
|
|
2221
|
+
return buffer.subarray(0, bytesRead).toString('base64');
|
|
2222
|
+
} finally { await file.close(); }
|
|
2223
|
+
};
|
|
2224
|
+
const targets = async () => {
|
|
2225
|
+
if (targetPid !== 'all') {
|
|
2226
|
+
return { targetStat: await fs.readFile('/proc/' + targetPid + '/stat', 'utf8').catch(() => null) };
|
|
2227
|
+
}
|
|
2228
|
+
const required = new Set(JSON.parse(process.argv[3] || '[]'));
|
|
2229
|
+
const pids = new Set((await fs.readdir('/proc')).filter(pid => /^[1-9][0-9]*$/.test(pid)));
|
|
2230
|
+
for (const pid of required) pids.add(String(pid));
|
|
2231
|
+
const processStats = [];
|
|
2232
|
+
let bytes = 0;
|
|
2233
|
+
// Sequential reads keep proc I/O and retained memory bounded within this
|
|
2234
|
+
// single killable helper; never launch one helper or pending read per PID.
|
|
2235
|
+
for (const pid of [...pids].sort((a, b) => Number(a) - Number(b))) {
|
|
2236
|
+
let stat;
|
|
2237
|
+
try { stat = await fs.readFile('/proc/' + pid + '/stat', 'utf8'); }
|
|
2238
|
+
catch (error) {
|
|
2239
|
+
if (error.code === 'ENOENT' || error.code === 'ESRCH') continue;
|
|
2240
|
+
if (!required.has(Number(pid)) && (error.code === 'EACCES' || error.code === 'EPERM')) continue;
|
|
2241
|
+
throw error;
|
|
2242
|
+
}
|
|
2243
|
+
bytes += Buffer.byteLength(JSON.stringify(stat)) + 1;
|
|
2244
|
+
if (bytes > ${PROCESS_HELPER_MAX_BUFFER_BYTES} - 8192) throw new Error('Process table exceeds its byte limit');
|
|
2245
|
+
processStats.push(stat);
|
|
2246
|
+
}
|
|
2247
|
+
return { processStats, auxv: await prefix('/proc/self/auxv', 4096), elf: await prefix('/proc/self/exe', 6) };
|
|
2248
|
+
};
|
|
2055
2249
|
Promise.all([
|
|
2056
2250
|
fs.readFile('/proc/sys/kernel/random/boot_id', 'utf8'),
|
|
2057
2251
|
fs.readlink('/proc/self/ns/pid'),
|
|
@@ -2059,12 +2253,12 @@ const LINUX_PROCESS_QUERY = `
|
|
|
2059
2253
|
fs.readlink('/proc/' + observerPid + '/ns/pid'),
|
|
2060
2254
|
timeNamespace(observerPid),
|
|
2061
2255
|
fs.readFile('/proc/self/stat', 'utf8'),
|
|
2062
|
-
|
|
2256
|
+
targets(),
|
|
2063
2257
|
]).then(([bootId, pidNamespace, timeNamespace, observerPidNamespace,
|
|
2064
|
-
observerTimeNamespace, selfStat,
|
|
2258
|
+
observerTimeNamespace, selfStat, target]) => {
|
|
2065
2259
|
process.stdout.write(JSON.stringify({bootId: bootId.trim(), pidNamespace,
|
|
2066
2260
|
timeNamespace, observerPidNamespace, observerTimeNamespace,
|
|
2067
|
-
helperPid: process.pid, selfStat,
|
|
2261
|
+
helperPid: process.pid, selfStat, ...target}));
|
|
2068
2262
|
}).catch(() => { process.exitCode = 1; });
|
|
2069
2263
|
`;
|
|
2070
2264
|
const POSIX_MONTHS = [
|
|
@@ -2146,31 +2340,48 @@ function parseLinuxStat(raw) {
|
|
|
2146
2340
|
const pid = Number(prefix[1]);
|
|
2147
2341
|
const fields = raw.slice(close + 2).trim().split(/\s+/u);
|
|
2148
2342
|
const startTicks = fields[19];
|
|
2149
|
-
if (!isPositivePid(pid) || !isStartTicks(startTicks) || !/^[RSDTtKWPI]$/.test(fields[0]
|
|
2150
|
-
|
|
2343
|
+
if (!isPositivePid(pid) || !isStartTicks(startTicks) || !/^[RSDTtKWPI]$/.test(fields[0])) return;
|
|
2344
|
+
const parentPid = Number(fields[1]);
|
|
2345
|
+
const groupPid = Number(fields[2]);
|
|
2346
|
+
return validProcessLinks(parentPid, groupPid) ? {
|
|
2151
2347
|
pid,
|
|
2348
|
+
parentPid,
|
|
2349
|
+
groupPid,
|
|
2152
2350
|
startTicks
|
|
2153
|
-
};
|
|
2351
|
+
} : void 0;
|
|
2154
2352
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2353
|
+
function validProcessLinks(parentPid, groupPid) {
|
|
2354
|
+
return Number.isSafeInteger(parentPid) && parentPid >= 0 && Number.isSafeInteger(groupPid) && groupPid >= 0;
|
|
2355
|
+
}
|
|
2356
|
+
async function readLinuxQuery(pid, timeoutMs, requiredPids = []) {
|
|
2357
|
+
const args = [
|
|
2157
2358
|
"--input-type=commonjs",
|
|
2158
2359
|
"-e",
|
|
2159
2360
|
LINUX_PROCESS_QUERY,
|
|
2160
2361
|
String(pid),
|
|
2161
2362
|
String(process.pid)
|
|
2162
|
-
]
|
|
2363
|
+
];
|
|
2364
|
+
if (pid === "all") args.push(JSON.stringify(requiredPids));
|
|
2365
|
+
const output = await runTimedExecFile(process.execPath, args, {
|
|
2163
2366
|
timeoutMs,
|
|
2164
|
-
maxBufferBytes: 8192,
|
|
2367
|
+
maxBufferBytes: pid === "all" ? PROCESS_HELPER_MAX_BUFFER_BYTES : 8192,
|
|
2165
2368
|
env: {
|
|
2166
2369
|
...process.env,
|
|
2167
2370
|
NODE_OPTIONS: ""
|
|
2168
2371
|
}
|
|
2169
2372
|
});
|
|
2170
|
-
|
|
2373
|
+
return JSON.parse(output);
|
|
2374
|
+
}
|
|
2375
|
+
function verifiedLinuxScope(raw) {
|
|
2171
2376
|
const scope = parseLinuxScope(raw);
|
|
2172
2377
|
const self = parseLinuxStat(raw.selfStat);
|
|
2173
2378
|
if (!scope || !self || self.pid !== raw.helperPid || scope.pidNamespace !== raw.observerPidNamespace || scope.timeNamespace !== raw.observerTimeNamespace) return;
|
|
2379
|
+
return scope;
|
|
2380
|
+
}
|
|
2381
|
+
async function readLinuxObservation(pid, timeoutMs) {
|
|
2382
|
+
const raw = await readLinuxQuery(pid, timeoutMs);
|
|
2383
|
+
const scope = verifiedLinuxScope(raw);
|
|
2384
|
+
if (!scope) return;
|
|
2174
2385
|
const target = parseLinuxStat(raw.targetStat);
|
|
2175
2386
|
return {
|
|
2176
2387
|
scope,
|
|
@@ -2181,6 +2392,80 @@ async function readLinuxObservation(pid, timeoutMs) {
|
|
|
2181
2392
|
} } : {}
|
|
2182
2393
|
};
|
|
2183
2394
|
}
|
|
2395
|
+
function linuxAuxv(raw) {
|
|
2396
|
+
if (typeof raw.elf !== "string" || typeof raw.auxv !== "string") throw new Error("Linux process clock is unavailable");
|
|
2397
|
+
const elf = Buffer.from(raw.elf, "base64");
|
|
2398
|
+
const auxv = Buffer.from(raw.auxv, "base64");
|
|
2399
|
+
if (elf.subarray(0, 4).toString("hex") !== "7f454c46" || ![1, 2].includes(elf[4]) || ![1, 2].includes(elf[5])) throw new Error("Linux process clock has an invalid ELF format");
|
|
2400
|
+
return {
|
|
2401
|
+
auxv,
|
|
2402
|
+
size: elf[4] === 1 ? 4 : 8,
|
|
2403
|
+
littleEndian: elf[5] === 1
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
function linuxClockTicks(raw) {
|
|
2407
|
+
const { auxv, size, littleEndian } = linuxAuxv(raw);
|
|
2408
|
+
const word = (offset) => readAuxvWord(auxv, offset, size, littleEndian);
|
|
2409
|
+
for (let offset = 0; offset + size * 2 <= auxv.length; offset += size * 2) {
|
|
2410
|
+
const key = word(offset);
|
|
2411
|
+
if (key === 0n) break;
|
|
2412
|
+
if (key === 17n && word(offset + size) > 0n) return word(offset + size);
|
|
2413
|
+
}
|
|
2414
|
+
throw new Error("Linux process clock has no AT_CLKTCK");
|
|
2415
|
+
}
|
|
2416
|
+
function readAuxvWord(buffer, offset, size, littleEndian) {
|
|
2417
|
+
if (size === 8) return littleEndian ? buffer.readBigUInt64LE(offset) : buffer.readBigUInt64BE(offset);
|
|
2418
|
+
return BigInt(littleEndian ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset));
|
|
2419
|
+
}
|
|
2420
|
+
async function readLinuxProcessTable(timeoutMs, requiredPids, pid) {
|
|
2421
|
+
const raw = await readLinuxQuery("all", timeoutMs, requiredPids);
|
|
2422
|
+
const scope = verifiedLinuxScope(raw);
|
|
2423
|
+
if (!scope || !Array.isArray(raw.processStats)) throw new Error("Linux process table scope is unverified");
|
|
2424
|
+
const clockTicksPerSecond = linuxClockTicks(raw);
|
|
2425
|
+
const table = /* @__PURE__ */ new Map();
|
|
2426
|
+
for (const value of raw.processStats) {
|
|
2427
|
+
const stat = parseLinuxStat(value);
|
|
2428
|
+
if (stat && (pid === void 0 || stat.pid === pid)) table.set(stat.pid, {
|
|
2429
|
+
pid: stat.pid,
|
|
2430
|
+
parentPid: stat.parentPid,
|
|
2431
|
+
groupPid: stat.groupPid,
|
|
2432
|
+
birth: {
|
|
2433
|
+
kind: "linux-proc",
|
|
2434
|
+
...scope,
|
|
2435
|
+
startTicks: stat.startTicks
|
|
2436
|
+
},
|
|
2437
|
+
clockTicksPerSecond
|
|
2438
|
+
});
|
|
2439
|
+
}
|
|
2440
|
+
return table;
|
|
2441
|
+
}
|
|
2442
|
+
function readLinuxExitClock() {
|
|
2443
|
+
let descriptor;
|
|
2444
|
+
try {
|
|
2445
|
+
descriptor = fs.openSync("/proc/uptime", "r");
|
|
2446
|
+
const buffer = Buffer.alloc(64);
|
|
2447
|
+
const bytes = fs.readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
2448
|
+
const match = /^(\d+)\.(\d{2})\s/u.exec(buffer.toString("ascii", 0, bytes));
|
|
2449
|
+
return match ? {
|
|
2450
|
+
centiseconds: BigInt(match[1]) * 100n + BigInt(match[2]),
|
|
2451
|
+
timeNamespace: readOwnTimeNamespace()
|
|
2452
|
+
} : void 0;
|
|
2453
|
+
} catch {
|
|
2454
|
+
return;
|
|
2455
|
+
} finally {
|
|
2456
|
+
if (descriptor !== void 0) try {
|
|
2457
|
+
fs.closeSync(descriptor);
|
|
2458
|
+
} catch {}
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
function readOwnTimeNamespace() {
|
|
2462
|
+
try {
|
|
2463
|
+
return fs.readlinkSync("/proc/self/ns/time");
|
|
2464
|
+
} catch (error) {
|
|
2465
|
+
if (error.code === "ENOENT") return "unsupported";
|
|
2466
|
+
throw error;
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2184
2469
|
function isPositivePid(pid) {
|
|
2185
2470
|
return Number.isSafeInteger(pid) && pid > 0;
|
|
2186
2471
|
}
|
|
@@ -2218,7 +2503,7 @@ function parseProcessTable(output) {
|
|
|
2218
2503
|
pid,
|
|
2219
2504
|
parentPid: Number(match[2]),
|
|
2220
2505
|
groupPid: Number(match[3]),
|
|
2221
|
-
birth
|
|
2506
|
+
birth: processBirthIdentity(birth)
|
|
2222
2507
|
});
|
|
2223
2508
|
}
|
|
2224
2509
|
return table;
|
|
@@ -2227,7 +2512,7 @@ function processTableBirth(value) {
|
|
|
2227
2512
|
if (process.platform !== "win32") return posixBirth(value);
|
|
2228
2513
|
return isCanonicalBirth(value, "windows-creation") ? value : void 0;
|
|
2229
2514
|
}
|
|
2230
|
-
async function readProcessTable(timeoutMs, pid) {
|
|
2515
|
+
async function readProcessTable(timeoutMs, pid, requiredPids = []) {
|
|
2231
2516
|
if (pid !== void 0 && (!Number.isSafeInteger(pid) || pid <= 0)) throw new Error("Process identity requires a positive PID");
|
|
2232
2517
|
if (process.platform === "win32") return parseProcessTable(await runTimedExecFile(windowsPowerShellPath(), [
|
|
2233
2518
|
"-NoLogo",
|
|
@@ -2239,6 +2524,7 @@ async function readProcessTable(timeoutMs, pid) {
|
|
|
2239
2524
|
timeoutMs,
|
|
2240
2525
|
windowsHide: true
|
|
2241
2526
|
}));
|
|
2527
|
+
if (process.platform === "linux") return await readLinuxProcessTable(timeoutMs, requiredPids, pid);
|
|
2242
2528
|
return parseProcessTable(await runTimedExecFile("ps", [
|
|
2243
2529
|
...pid === void 0 ? ["-e"] : ["-p", String(pid)],
|
|
2244
2530
|
"-o",
|
|
@@ -2266,8 +2552,7 @@ async function probeProcessIdentity(pid, timeoutMs = IDENTITY_QUERY_TIMEOUT_MS$1
|
|
|
2266
2552
|
}
|
|
2267
2553
|
async function readNativeIdentity(pid, timeoutMs) {
|
|
2268
2554
|
if (process.platform === "linux") return (await readLinuxObservation(pid, timeoutMs))?.identity;
|
|
2269
|
-
|
|
2270
|
-
return entry ? processBirthIdentity(entry.birth) : void 0;
|
|
2555
|
+
return (await readProcessTable(timeoutMs, pid)).get(pid)?.birth;
|
|
2271
2556
|
}
|
|
2272
2557
|
function processBirthIdentity(value) {
|
|
2273
2558
|
return {
|
|
@@ -2314,6 +2599,138 @@ async function getOwnProcessIdentity() {
|
|
|
2314
2599
|
return await ownIdentityPending;
|
|
2315
2600
|
}
|
|
2316
2601
|
//#endregion
|
|
2602
|
+
//#region src/acp/process-descendants.ts
|
|
2603
|
+
function hasStaleWindowsParent(identity, parent) {
|
|
2604
|
+
return identity.birth.kind === "windows-creation" && (parent?.birth.kind !== "windows-creation" || parent.birth.value > identity.birth.value);
|
|
2605
|
+
}
|
|
2606
|
+
function includeProcessDescendants(table, owned) {
|
|
2607
|
+
let expanded;
|
|
2608
|
+
do {
|
|
2609
|
+
expanded = false;
|
|
2610
|
+
for (const identity of table.values()) {
|
|
2611
|
+
if (hasStaleWindowsParent(identity, table.get(identity.parentPid))) continue;
|
|
2612
|
+
if (owned.has(identity.parentPid) && !owned.has(identity.pid)) {
|
|
2613
|
+
owned.add(identity.pid);
|
|
2614
|
+
expanded = true;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
} while (expanded);
|
|
2618
|
+
}
|
|
2619
|
+
/** Best-effort cleanup for descendants witnessed during this child launch. */
|
|
2620
|
+
var ProcessDescendants = class {
|
|
2621
|
+
child;
|
|
2622
|
+
identities = /* @__PURE__ */ new Map();
|
|
2623
|
+
rootBirth;
|
|
2624
|
+
pending;
|
|
2625
|
+
retired = false;
|
|
2626
|
+
ownProcessGroup;
|
|
2627
|
+
captureGroupAfterExit;
|
|
2628
|
+
groupExitedAt;
|
|
2629
|
+
onRootExit = () => {
|
|
2630
|
+
this.groupExitedAt = process.platform === "linux" ? readLinuxExitClock() ?? null : Date.now();
|
|
2631
|
+
};
|
|
2632
|
+
constructor(child, options = {}) {
|
|
2633
|
+
this.child = child;
|
|
2634
|
+
this.ownProcessGroup = process.platform !== "win32" && Boolean(options.ownProcessGroup);
|
|
2635
|
+
this.captureGroupAfterExit = this.ownProcessGroup && isChildProcessRunning(child);
|
|
2636
|
+
if (this.ownProcessGroup) child.once("exit", this.onRootExit);
|
|
2637
|
+
}
|
|
2638
|
+
capture(timeoutMs = PROCESS_HELPER_TIMEOUT_MS) {
|
|
2639
|
+
if (this.retired) return Promise.resolve(true);
|
|
2640
|
+
this.pending ??= this.readSnapshot(timeoutMs).finally(() => {
|
|
2641
|
+
this.pending = void 0;
|
|
2642
|
+
});
|
|
2643
|
+
return this.pending;
|
|
2644
|
+
}
|
|
2645
|
+
async readSnapshot(timeoutMs) {
|
|
2646
|
+
const deadline = performance.now() + timeoutMs;
|
|
2647
|
+
const rootWasRunning = isChildProcessRunning(this.child);
|
|
2648
|
+
const captureRootGroup = rootWasRunning || this.captureGroupAfterExit;
|
|
2649
|
+
if (!rootWasRunning) this.captureGroupAfterExit = false;
|
|
2650
|
+
try {
|
|
2651
|
+
const table = await readProcessTable(timeoutMs, void 0, this.requiredSnapshotPids(rootWasRunning));
|
|
2652
|
+
if (!this.retired) {
|
|
2653
|
+
table.delete(1);
|
|
2654
|
+
table.delete(process.pid);
|
|
2655
|
+
this.refresh(table, captureRootGroup);
|
|
2656
|
+
if (this.captureGroupAfterExit && !isChildProcessRunning(this.child)) return await this.readSnapshot(Math.max(1, deadline - performance.now()));
|
|
2657
|
+
}
|
|
2658
|
+
return true;
|
|
2659
|
+
} catch {
|
|
2660
|
+
if (!isChildProcessRunning(this.child)) this.captureGroupAfterExit = false;
|
|
2661
|
+
return false;
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
requiredSnapshotPids(rootWasRunning) {
|
|
2665
|
+
return [...this.identities.keys(), ...rootWasRunning && this.child.pid ? [this.child.pid] : []];
|
|
2666
|
+
}
|
|
2667
|
+
refresh(table, rootWasRunning) {
|
|
2668
|
+
this.verifyScope(table);
|
|
2669
|
+
const owned = /* @__PURE__ */ new Set();
|
|
2670
|
+
for (const [pid, identity] of this.identities) {
|
|
2671
|
+
const observed = table.get(pid);
|
|
2672
|
+
if (observed && compareProcessBirthIdentity(identity.birth, observed.birth) === "matching") owned.add(pid);
|
|
2673
|
+
}
|
|
2674
|
+
this.includeRoot(table, owned);
|
|
2675
|
+
if (this.ownProcessGroup) this.includeProcessGroup(table, owned, rootWasRunning);
|
|
2676
|
+
includeProcessDescendants(table, owned);
|
|
2677
|
+
owned.delete(this.child.pid ?? 0);
|
|
2678
|
+
this.identities = new Map([...table].filter(([pid]) => owned.has(pid)));
|
|
2679
|
+
}
|
|
2680
|
+
verifyScope(table) {
|
|
2681
|
+
const expected = this.rootBirth ?? this.identities.values().next().value?.birth;
|
|
2682
|
+
const observation = table.values().next().value?.birth;
|
|
2683
|
+
if (expected && observation && compareProcessBirthIdentity(expected, observation) === "unknown") throw new Error("Descendant process scope changed");
|
|
2684
|
+
}
|
|
2685
|
+
includeRoot(table, owned) {
|
|
2686
|
+
const root = this.child.pid;
|
|
2687
|
+
const rootIdentity = root && table.get(root);
|
|
2688
|
+
if (rootIdentity && isChildProcessRunning(this.child)) {
|
|
2689
|
+
this.rootBirth ??= rootIdentity.birth;
|
|
2690
|
+
if (compareProcessBirthIdentity(this.rootBirth, rootIdentity.birth) === "matching") owned.add(rootIdentity.pid);
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
includeProcessGroup(table, owned, rootWasRunning) {
|
|
2694
|
+
const root = this.child.pid;
|
|
2695
|
+
if (root && (rootWasRunning || [...owned].some((pid) => table.get(pid)?.groupPid === root))) {
|
|
2696
|
+
for (const identity of table.values()) if (identity.groupPid === root && this.startedBeforeRootExit(identity)) owned.add(identity.pid);
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
startedBeforeRootExit(identity) {
|
|
2700
|
+
const cutoff = this.groupExitedAt;
|
|
2701
|
+
if (cutoff === void 0) return true;
|
|
2702
|
+
if (cutoff === null) return false;
|
|
2703
|
+
if (identity.birth.kind !== "linux-proc") return typeof cutoff === "number" && Date.parse(identity.birth.value) <= cutoff;
|
|
2704
|
+
if (typeof cutoff === "number" || identity.birth.timeNamespace !== cutoff.timeNamespace || !identity.clockTicksPerSecond) return false;
|
|
2705
|
+
return BigInt(identity.birth.startTicks) * 100n < (cutoff.centiseconds + 1n) * identity.clockTicksPerSecond;
|
|
2706
|
+
}
|
|
2707
|
+
async signal(signal, timeoutMs) {
|
|
2708
|
+
if (!await this.capture(timeoutMs) || this.retired) return;
|
|
2709
|
+
for (const pid of this.identities.keys()) try {
|
|
2710
|
+
process.kill(pid, signal);
|
|
2711
|
+
} catch {
|
|
2712
|
+
this.identities.delete(pid);
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
hasTrackedProcesses() {
|
|
2716
|
+
return this.identities.size > 0;
|
|
2717
|
+
}
|
|
2718
|
+
async waitForExit(timeoutMs) {
|
|
2719
|
+
const deadline = performance.now() + timeoutMs;
|
|
2720
|
+
do {
|
|
2721
|
+
if (!await this.capture(Math.max(1, deadline - performance.now()))) return false;
|
|
2722
|
+
if (this.identities.size === 0) return true;
|
|
2723
|
+
await setTimeout$1(Math.min(100, Math.max(0, deadline - performance.now())));
|
|
2724
|
+
} while (performance.now() < deadline);
|
|
2725
|
+
return false;
|
|
2726
|
+
}
|
|
2727
|
+
retire() {
|
|
2728
|
+
this.retired = true;
|
|
2729
|
+
this.child.off("exit", this.onRootExit);
|
|
2730
|
+
this.identities.clear();
|
|
2731
|
+
}
|
|
2732
|
+
};
|
|
2733
|
+
//#endregion
|
|
2317
2734
|
//#region src/session/lock-owner.ts
|
|
2318
2735
|
const OBSERVATION_REUSE_MS = 1e3;
|
|
2319
2736
|
const IDENTITY_QUERY_TIMEOUT_MS = 1e3;
|
|
@@ -2377,6 +2794,145 @@ function queueSocketPath(sessionId, homeDir = os.homedir()) {
|
|
|
2377
2794
|
return path.join(queueSocketBaseDir(homeDir) ?? "/tmp", `${key}.sock`);
|
|
2378
2795
|
}
|
|
2379
2796
|
//#endregion
|
|
2797
|
+
//#region src/session/queue/retirement-receipt.ts
|
|
2798
|
+
const MAX_RETIREMENT_MEMBERS = 4096;
|
|
2799
|
+
const MAX_RETIREMENT_BYTES = 1048576;
|
|
2800
|
+
function hasQueueRetirementReceipt(value) {
|
|
2801
|
+
return value !== null && typeof value === "object" && Object.hasOwn(value, "retirement");
|
|
2802
|
+
}
|
|
2803
|
+
function parseWitness(raw) {
|
|
2804
|
+
if (!raw || typeof raw !== "object") return;
|
|
2805
|
+
const value = raw;
|
|
2806
|
+
const processIdentity = parseProcessBirthIdentity(value.processIdentity);
|
|
2807
|
+
if (typeof value.pid !== "number" || !Number.isSafeInteger(value.pid) || value.pid <= 1 || processIdentity?.kind !== "windows-creation") return;
|
|
2808
|
+
return {
|
|
2809
|
+
pid: value.pid,
|
|
2810
|
+
processIdentity
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
function parseQueueRetirementReceipt(raw, owner) {
|
|
2814
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
2815
|
+
const value = raw;
|
|
2816
|
+
const root = parseReceiptRoot(value, owner);
|
|
2817
|
+
if (!root) return null;
|
|
2818
|
+
const descendants = parseDescendants(value.descendants, root.pid);
|
|
2819
|
+
if (!descendants) return null;
|
|
2820
|
+
try {
|
|
2821
|
+
if (Buffer.byteLength(JSON.stringify(raw)) > MAX_RETIREMENT_BYTES) return null;
|
|
2822
|
+
} catch {
|
|
2823
|
+
return null;
|
|
2824
|
+
}
|
|
2825
|
+
return {
|
|
2826
|
+
ownerGeneration: owner.ownerGeneration,
|
|
2827
|
+
root,
|
|
2828
|
+
descendants
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
function parseReceiptRoot(value, owner) {
|
|
2832
|
+
const root = parseWitness(value.root);
|
|
2833
|
+
if (!root || root.pid !== owner.pid || value.ownerGeneration !== owner.ownerGeneration || compareProcessBirthIdentity(owner.processIdentity, root.processIdentity) !== "matching") return;
|
|
2834
|
+
return root;
|
|
2835
|
+
}
|
|
2836
|
+
function parseDescendants(raw, rootPid) {
|
|
2837
|
+
if (!Array.isArray(raw) || raw.length >= MAX_RETIREMENT_MEMBERS) return;
|
|
2838
|
+
const pids = /* @__PURE__ */ new Set([rootPid]);
|
|
2839
|
+
const witnesses = [];
|
|
2840
|
+
for (const value of raw) {
|
|
2841
|
+
const witness = parseWitness(value);
|
|
2842
|
+
if (!witness || pids.has(witness.pid)) return;
|
|
2843
|
+
pids.add(witness.pid);
|
|
2844
|
+
witnesses.push(witness);
|
|
2845
|
+
}
|
|
2846
|
+
return witnesses;
|
|
2847
|
+
}
|
|
2848
|
+
function queueRetirementIncomplete() {
|
|
2849
|
+
return new QueueConnectionError("Queue-owner descendant cleanup is incomplete or unverified. Its lease was retained. Retry cleanup with updated clients and process-query access; do not remove the lease while recorded processes may remain.", {
|
|
2850
|
+
detailCode: "QUEUE_OWNER_RETIREMENT_INCOMPLETE",
|
|
2851
|
+
origin: "queue",
|
|
2852
|
+
retryable: true
|
|
2853
|
+
});
|
|
2854
|
+
}
|
|
2855
|
+
function retirementTimeRemaining(deadline, maximumMs = 2e3) {
|
|
2856
|
+
const remaining = deadline - performance.now();
|
|
2857
|
+
if (remaining <= 0) throw queueRetirementIncomplete();
|
|
2858
|
+
return Math.min(maximumMs, remaining);
|
|
2859
|
+
}
|
|
2860
|
+
async function readRetirementSnapshot(deadline) {
|
|
2861
|
+
if (process.platform !== "win32") throw queueRetirementIncomplete();
|
|
2862
|
+
return await readProcessTable(retirementTimeRemaining(deadline)).catch(() => {
|
|
2863
|
+
throw queueRetirementIncomplete();
|
|
2864
|
+
});
|
|
2865
|
+
}
|
|
2866
|
+
function observeRetirementSnapshot(witness, table) {
|
|
2867
|
+
if (process.platform !== "win32" || !isWindowsRetirementIdentity(witness.processIdentity)) return "unknown";
|
|
2868
|
+
const observed = table?.get(witness.pid);
|
|
2869
|
+
const comparison = observed ? compareProcessBirthIdentity(witness.processIdentity, observed.birth) : "unknown";
|
|
2870
|
+
if (comparison === "different" || isProcessDefinitelyDead(witness.pid)) return "gone";
|
|
2871
|
+
return witness.pid === process.pid ? "unknown" : comparison;
|
|
2872
|
+
}
|
|
2873
|
+
function isWindowsRetirementIdentity(identity) {
|
|
2874
|
+
return identity === void 0 || identity.kind === "windows-creation";
|
|
2875
|
+
}
|
|
2876
|
+
function captureQueueRetirementReceipt(owner, previous, rootRunning, snapshot) {
|
|
2877
|
+
const root = parseWitness({
|
|
2878
|
+
pid: owner.pid,
|
|
2879
|
+
processIdentity: owner.processIdentity
|
|
2880
|
+
});
|
|
2881
|
+
if (!root || process.platform !== "win32") throw queueRetirementIncomplete();
|
|
2882
|
+
const saved = previous ? previous.descendants : [];
|
|
2883
|
+
const table = new Map(snapshot);
|
|
2884
|
+
const owned = matchingSnapshotPids(table, rootRunning ? [root, ...saved] : saved);
|
|
2885
|
+
if (rootRunning && !owned.has(root.pid)) throw queueRetirementIncomplete();
|
|
2886
|
+
table.delete(1);
|
|
2887
|
+
table.delete(process.pid);
|
|
2888
|
+
includeProcessDescendants(table, owned);
|
|
2889
|
+
owned.delete(root.pid);
|
|
2890
|
+
const receipt = {
|
|
2891
|
+
ownerGeneration: owner.ownerGeneration,
|
|
2892
|
+
root,
|
|
2893
|
+
descendants: unsettledSnapshotWitnesses(snapshot, mergeSnapshotDescendants(table, owned, saved))
|
|
2894
|
+
};
|
|
2895
|
+
if (!parseQueueRetirementReceipt(receipt, owner)) throw queueRetirementIncomplete();
|
|
2896
|
+
return receipt;
|
|
2897
|
+
}
|
|
2898
|
+
function unsettledSnapshotWitnesses(table, witnesses) {
|
|
2899
|
+
return witnesses.filter((witness) => {
|
|
2900
|
+
const observed = table.get(witness.pid);
|
|
2901
|
+
return !observed || compareProcessBirthIdentity(witness.processIdentity, observed.birth) !== "different";
|
|
2902
|
+
});
|
|
2903
|
+
}
|
|
2904
|
+
function matchingSnapshotPids(table, witnesses) {
|
|
2905
|
+
const owned = /* @__PURE__ */ new Set();
|
|
2906
|
+
for (const witness of witnesses) {
|
|
2907
|
+
const observed = table.get(witness.pid);
|
|
2908
|
+
if (observed && compareProcessBirthIdentity(witness.processIdentity, observed.birth) === "matching") owned.add(witness.pid);
|
|
2909
|
+
}
|
|
2910
|
+
return owned;
|
|
2911
|
+
}
|
|
2912
|
+
function mergeSnapshotDescendants(table, owned, saved) {
|
|
2913
|
+
const descendants = new Map(saved.map((witness) => [witness.pid, witness]));
|
|
2914
|
+
for (const pid of owned) {
|
|
2915
|
+
const observed = table.get(pid);
|
|
2916
|
+
if (observed) descendants.set(pid, {
|
|
2917
|
+
pid,
|
|
2918
|
+
processIdentity: observed.birth
|
|
2919
|
+
});
|
|
2920
|
+
}
|
|
2921
|
+
return [...descendants.values()];
|
|
2922
|
+
}
|
|
2923
|
+
async function observeRetirementWitness(witness, deadline) {
|
|
2924
|
+
if (witness.pid === process.pid) throw queueRetirementIncomplete();
|
|
2925
|
+
return await observeProcessIncarnation(witness.pid, witness.processIdentity, retirementTimeRemaining(deadline));
|
|
2926
|
+
}
|
|
2927
|
+
async function assertQueueRetirementComplete(receipt, deadline) {
|
|
2928
|
+
if (receipt === null) throw queueRetirementIncomplete();
|
|
2929
|
+
const witnesses = receipt?.descendants ?? [];
|
|
2930
|
+
if (witnesses.length === 0) return;
|
|
2931
|
+
const live = witnesses.filter((witness) => observeRetirementSnapshot(witness, void 0) !== "gone");
|
|
2932
|
+
const table = live.length > 0 ? await readRetirementSnapshot(deadline) : void 0;
|
|
2933
|
+
for (const witness of live) if (observeRetirementSnapshot(witness, table) !== "gone") throw queueRetirementIncomplete();
|
|
2934
|
+
}
|
|
2935
|
+
//#endregion
|
|
2380
2936
|
//#region src/session/queue/lease-mutation.ts
|
|
2381
2937
|
const GUARD_WAIT_MS = 2e3;
|
|
2382
2938
|
const localMutations = /* @__PURE__ */ new Map();
|
|
@@ -2432,7 +2988,7 @@ async function rollbackReservation(lockPath, expected) {
|
|
|
2432
2988
|
} catch {
|
|
2433
2989
|
return;
|
|
2434
2990
|
}
|
|
2435
|
-
if (matchesReservation(value, expected)) await fs$1.unlink(lockPath);
|
|
2991
|
+
if (matchesReservation(value, expected) && !hasQueueRetirementReceipt(value)) await fs$1.unlink(lockPath);
|
|
2436
2992
|
} catch (error) {
|
|
2437
2993
|
if (error.code !== "ENOENT") throw error;
|
|
2438
2994
|
}
|
|
@@ -2537,7 +3093,7 @@ function parseQueueOwnerRecord(raw) {
|
|
|
2537
3093
|
const record = raw;
|
|
2538
3094
|
if (!hasValidQueueOwnerRecordFields(record)) return null;
|
|
2539
3095
|
const processIdentity = parseProcessBirthIdentity(record.processIdentity);
|
|
2540
|
-
|
|
3096
|
+
const owner = {
|
|
2541
3097
|
pid: record.pid,
|
|
2542
3098
|
sessionId: record.sessionId,
|
|
2543
3099
|
socketPath: record.socketPath,
|
|
@@ -2550,6 +3106,8 @@ function parseQueueOwnerRecord(raw) {
|
|
|
2550
3106
|
...typeof record.mcpConfigPath === "string" ? { mcpConfigPath: record.mcpConfigPath } : {},
|
|
2551
3107
|
...typeof record.mcpConfigFingerprint === "string" ? { mcpConfigFingerprint: record.mcpConfigFingerprint } : {}
|
|
2552
3108
|
};
|
|
3109
|
+
if (hasQueueRetirementReceipt(record)) owner.retirement = parseQueueRetirementReceipt(record.retirement, owner);
|
|
3110
|
+
return owner;
|
|
2553
3111
|
}
|
|
2554
3112
|
function parseQueueOwnerCapabilities(record) {
|
|
2555
3113
|
return {
|
|
@@ -2611,20 +3169,21 @@ async function removeSocketFile(socketPath) {
|
|
|
2611
3169
|
}
|
|
2612
3170
|
}
|
|
2613
3171
|
async function waitForProcessExit(pid, timeoutMs, hasExited) {
|
|
2614
|
-
const deadline =
|
|
2615
|
-
while (
|
|
3172
|
+
const deadline = performance.now() + Math.max(0, timeoutMs);
|
|
3173
|
+
while (performance.now() <= deadline) {
|
|
2616
3174
|
if (hasExited(pid)) return true;
|
|
2617
3175
|
await waitMs(PROCESS_POLL_MS);
|
|
2618
3176
|
}
|
|
2619
3177
|
return hasExited(pid);
|
|
2620
3178
|
}
|
|
2621
|
-
async function cleanupQueueOwnerFiles(sessionId, socketPath, isCurrent, capturedIdentity) {
|
|
3179
|
+
async function cleanupQueueOwnerFiles(sessionId, socketPath, isCurrent, capturedIdentity, deadline = retirementDeadline()) {
|
|
2622
3180
|
await withQueueLeaseMutation(sessionId, async () => {
|
|
2623
|
-
await cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent);
|
|
3181
|
+
await cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent, deadline);
|
|
2624
3182
|
}, { capturedIdentity });
|
|
2625
3183
|
}
|
|
2626
|
-
async function cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent) {
|
|
3184
|
+
async function cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent, deadline = retirementDeadline()) {
|
|
2627
3185
|
if (!await isCurrent()) return;
|
|
3186
|
+
await assertQueueRetirementComplete((await readQueueOwnerRecord(sessionId))?.retirement, deadline);
|
|
2628
3187
|
await removeSocketFile(socketPath).catch(() => {});
|
|
2629
3188
|
if (!await isCurrent()) return;
|
|
2630
3189
|
await fs$1.unlink(queueLockFilePath(sessionId)).catch((error) => {
|
|
@@ -2640,6 +3199,9 @@ function matchesQueueOwner(current, expected) {
|
|
|
2640
3199
|
function ownerIsAlive(owner) {
|
|
2641
3200
|
return owner.pid === process.pid || isProcessAlive(owner.pid);
|
|
2642
3201
|
}
|
|
3202
|
+
function ownerAcceptsWork(owner) {
|
|
3203
|
+
return owner.retirement === void 0 && ownerIsAlive(owner) && !isQueueOwnerHeartbeatStale(owner);
|
|
3204
|
+
}
|
|
2643
3205
|
async function readQueueOwnerRecord(sessionId) {
|
|
2644
3206
|
const lockPath = queueLockFilePath(sessionId);
|
|
2645
3207
|
try {
|
|
@@ -2665,7 +3227,7 @@ function dispatchSignal(pid, signal) {
|
|
|
2665
3227
|
return false;
|
|
2666
3228
|
}
|
|
2667
3229
|
}
|
|
2668
|
-
async function dispatchQueueOwnerSignal(pid, signal) {
|
|
3230
|
+
async function dispatchQueueOwnerSignal(pid, signal, deadline) {
|
|
2669
3231
|
if (process.platform !== "win32") return dispatchSignal(pid, signal);
|
|
2670
3232
|
const systemRoot = process.env.SystemRoot;
|
|
2671
3233
|
if (!systemRoot || !path.win32.isAbsolute(systemRoot)) throw new Error("Windows queue-owner cleanup requires an absolute SystemRoot directory");
|
|
@@ -2676,15 +3238,15 @@ async function dispatchQueueOwnerSignal(pid, signal) {
|
|
|
2676
3238
|
"/T",
|
|
2677
3239
|
"/F"
|
|
2678
3240
|
], {
|
|
2679
|
-
timeoutMs: PROCESS_SIGTERM_GRACE_MS,
|
|
3241
|
+
timeoutMs: retirementTimeRemaining(deadline, PROCESS_SIGTERM_GRACE_MS),
|
|
2680
3242
|
windowsHide: true
|
|
2681
3243
|
});
|
|
2682
3244
|
return true;
|
|
2683
3245
|
}
|
|
2684
|
-
async function terminateWithDispatch(pid, dispatch, hasExited) {
|
|
3246
|
+
async function terminateWithDispatch(pid, dispatch, hasExited, deadline = Infinity) {
|
|
2685
3247
|
for (const [signal, graceMs] of [["SIGTERM", PROCESS_SIGTERM_GRACE_MS], ["SIGKILL", PROCESS_SIGKILL_GRACE_MS]]) {
|
|
2686
3248
|
if (!await dispatch(signal)) return false;
|
|
2687
|
-
if (await waitForProcessExit(pid, graceMs, hasExited)) return true;
|
|
3249
|
+
if (await waitForProcessExit(pid, Math.min(graceMs, deadline - performance.now()), hasExited)) return true;
|
|
2688
3250
|
}
|
|
2689
3251
|
return false;
|
|
2690
3252
|
}
|
|
@@ -2692,10 +3254,10 @@ async function resolveUsableQueueOwner(sessionId, owner) {
|
|
|
2692
3254
|
await settlePendingQueueLeaseGuard(sessionId);
|
|
2693
3255
|
const observed = await readQueueOwnerRecord(sessionId);
|
|
2694
3256
|
if (!matchesQueueOwner(observed, owner)) return;
|
|
2695
|
-
if (
|
|
3257
|
+
if (ownerAcceptsWork(observed)) return observed;
|
|
2696
3258
|
await terminateQueueOwnerForSession(sessionId, observed, true);
|
|
2697
3259
|
const current = await readQueueOwnerRecord(sessionId);
|
|
2698
|
-
return matchesQueueOwner(current, observed) &&
|
|
3260
|
+
return matchesQueueOwner(current, observed) && ownerAcceptsWork(current) ? current : void 0;
|
|
2699
3261
|
}
|
|
2700
3262
|
async function tryAcquireQueueOwnerLease(sessionId, mcpConfigOrNowIsoFactory, nowIsoFactory = nowIso) {
|
|
2701
3263
|
const { mcpConfigPath, clock } = resolveLeaseArguments(mcpConfigOrNowIsoFactory, nowIsoFactory);
|
|
@@ -2806,9 +3368,13 @@ function refreshQueueOwnerLease(lease, options, nowIsoFactory = nowIso) {
|
|
|
2806
3368
|
if (lease.released) return Promise.resolve();
|
|
2807
3369
|
const update = lease.updates.then(async () => {
|
|
2808
3370
|
await withQueueLeaseMutation(lease.sessionId, async () => {
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
3371
|
+
const current = await readQueueOwnerRecord(lease.sessionId);
|
|
3372
|
+
if (!matchesQueueOwner(current, lease)) return;
|
|
3373
|
+
if (current.retirement === null) throw queueRetirementIncomplete();
|
|
3374
|
+
await replaceQueueOwnerRecord({
|
|
3375
|
+
...current,
|
|
3376
|
+
heartbeatAt: nowIsoFactory(),
|
|
3377
|
+
queueDepth: Math.max(0, Math.round(options.queueDepth))
|
|
2812
3378
|
});
|
|
2813
3379
|
}, { capturedIdentity: lease });
|
|
2814
3380
|
});
|
|
@@ -2816,8 +3382,8 @@ function refreshQueueOwnerLease(lease, options, nowIsoFactory = nowIso) {
|
|
|
2816
3382
|
return update;
|
|
2817
3383
|
}
|
|
2818
3384
|
async function stageQueueOwnerRecord(lease, queueDepth, clock, publish) {
|
|
2819
|
-
|
|
2820
|
-
pid:
|
|
3385
|
+
await stagePreparedQueueOwnerRecord({
|
|
3386
|
+
pid: lease.pid,
|
|
2821
3387
|
sessionId: lease.sessionId,
|
|
2822
3388
|
socketPath: lease.socketPath,
|
|
2823
3389
|
createdAt: lease.createdAt,
|
|
@@ -2830,9 +3396,17 @@ async function stageQueueOwnerRecord(lease, queueDepth, clock, publish) {
|
|
|
2830
3396
|
persistsControlState: true,
|
|
2831
3397
|
...lease.mcpConfigPath ? { mcpConfigPath: lease.mcpConfigPath } : {},
|
|
2832
3398
|
...lease.mcpConfigFingerprint ? { mcpConfigFingerprint: lease.mcpConfigFingerprint } : {}
|
|
2833
|
-
},
|
|
3399
|
+
}, publish);
|
|
3400
|
+
}
|
|
3401
|
+
async function replaceQueueOwnerRecord(owner) {
|
|
3402
|
+
await stagePreparedQueueOwnerRecord(owner, async (tempPath) => {
|
|
3403
|
+
await fs$1.rename(tempPath, queueLockFilePath(owner.sessionId));
|
|
3404
|
+
});
|
|
3405
|
+
}
|
|
3406
|
+
async function stagePreparedQueueOwnerRecord(owner, publish) {
|
|
3407
|
+
const payload = JSON.stringify(owner, null, 2);
|
|
2834
3408
|
await withTempFile({
|
|
2835
|
-
rootDir: path.dirname(
|
|
3409
|
+
rootDir: path.dirname(queueLockFilePath(owner.sessionId)),
|
|
2836
3410
|
prefix: "owner",
|
|
2837
3411
|
fileName: "lease"
|
|
2838
3412
|
}, async (tempPath) => {
|
|
@@ -2856,40 +3430,166 @@ function unverifiedQueueOwnerError(owner) {
|
|
|
2856
3430
|
retryable: true
|
|
2857
3431
|
});
|
|
2858
3432
|
}
|
|
2859
|
-
|
|
2860
|
-
return
|
|
2861
|
-
const current = await readQueueOwnerRecord(owner.sessionId);
|
|
2862
|
-
retirement.leaseReleased = current?.pid !== owner.pid;
|
|
2863
|
-
if (!matchesQueueOwner(current, owner) || requireStale && signal === "SIGTERM" && !isQueueOwnerHeartbeatStale(current)) return false;
|
|
2864
|
-
const identity = await observeProcessIncarnation(current.pid, current.processIdentity);
|
|
2865
|
-
if (identity !== "matching") {
|
|
2866
|
-
retirement.gone = identity === "gone";
|
|
2867
|
-
retirement.unverified = identity === "unknown";
|
|
2868
|
-
return false;
|
|
2869
|
-
}
|
|
2870
|
-
return await dispatchQueueOwnerSignal(owner.pid, signal);
|
|
2871
|
-
});
|
|
3433
|
+
function retirementDeadline() {
|
|
3434
|
+
return performance.now() + PROCESS_SIGTERM_GRACE_MS + PROCESS_SIGKILL_GRACE_MS;
|
|
2872
3435
|
}
|
|
2873
|
-
async function
|
|
2874
|
-
const
|
|
2875
|
-
|
|
3436
|
+
async function createQueueOwnerRetirement(deadline) {
|
|
3437
|
+
const snapshot = process.platform === "win32" ? await readRetirementSnapshot(deadline).catch(() => void 0) : void 0;
|
|
3438
|
+
return {
|
|
2876
3439
|
gone: false,
|
|
2877
3440
|
unverified: false,
|
|
2878
|
-
leaseReleased: false
|
|
3441
|
+
leaseReleased: false,
|
|
3442
|
+
deadline,
|
|
3443
|
+
snapshot,
|
|
3444
|
+
capturedIdentity: process.platform === "win32" ? { processIdentity: snapshot?.get(process.pid)?.birth } : void 0
|
|
2879
3445
|
};
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
3446
|
+
}
|
|
3447
|
+
async function observeRetiringOwner(owner, deadline) {
|
|
3448
|
+
return await observeProcessIncarnation(owner.pid, owner.processIdentity, process.platform === "win32" ? retirementTimeRemaining(deadline) : void 0);
|
|
3449
|
+
}
|
|
3450
|
+
function retirementBlockedByHeartbeat(owner, signal, requireStale) {
|
|
3451
|
+
return requireStale && owner.retirement === void 0 && signal === "SIGTERM" && !isQueueOwnerHeartbeatStale(owner);
|
|
3452
|
+
}
|
|
3453
|
+
async function prepareWindowsRetirement(owner, table, deadline) {
|
|
3454
|
+
if (owner.retirement === null || !table) throw queueRetirementIncomplete();
|
|
3455
|
+
owner.retirement = captureQueueRetirementReceipt(owner, owner.retirement, true, table);
|
|
3456
|
+
await replaceQueueOwnerRecord(owner);
|
|
3457
|
+
return await observeRetirementWitness(owner.retirement.root, deadline);
|
|
3458
|
+
}
|
|
3459
|
+
async function prepareRetiringOwner(owner, signal, retirement) {
|
|
3460
|
+
const windows = process.platform === "win32";
|
|
3461
|
+
const table = windows && signal !== "SIGTERM" ? await readRetirementSnapshot(retirement.deadline) : retirement.snapshot;
|
|
3462
|
+
let identity = windows ? observeRetirementSnapshot(owner, table) : await observeRetiringOwner(owner, retirement.deadline);
|
|
3463
|
+
if (identity === "matching" && windows) identity = await prepareWindowsRetirement(owner, table, retirement.deadline);
|
|
3464
|
+
retirement.gone = identity === "gone";
|
|
3465
|
+
retirement.unverified = identity === "unknown";
|
|
3466
|
+
return identity === "matching";
|
|
3467
|
+
}
|
|
3468
|
+
async function dispatchVerifiedQueueOwnerSignal(owner, signal, requireStale, retirement) {
|
|
3469
|
+
return await withQueueLeaseMutation(owner.sessionId, async () => {
|
|
3470
|
+
const current = await readQueueOwnerRecord(owner.sessionId);
|
|
3471
|
+
retirement.leaseReleased = current?.pid !== owner.pid;
|
|
3472
|
+
if (!matchesQueueOwner(current, owner) || retirementBlockedByHeartbeat(current, signal, requireStale)) return false;
|
|
3473
|
+
if (current.retirement === null) throw queueRetirementIncomplete();
|
|
3474
|
+
if (!await prepareRetiringOwner(current, signal, retirement)) return false;
|
|
3475
|
+
return await dispatchQueueOwnerSignal(owner.pid, signal, retirement.deadline);
|
|
3476
|
+
}, { capturedIdentity: retirement.capturedIdentity });
|
|
3477
|
+
}
|
|
3478
|
+
async function retireQueueOwner(owner, requireStale, retirement) {
|
|
3479
|
+
const { deadline } = retirement;
|
|
3480
|
+
const exited = await terminateWithDispatch(owner.pid, (signal) => dispatchVerifiedQueueOwnerSignal(owner, signal, requireStale, retirement), isProcessDefinitelyDead, process.platform === "win32" ? deadline : Infinity);
|
|
3481
|
+
if (await waitForReleasedOwner(owner, requireStale, retirement)) return false;
|
|
3482
|
+
const gone = retirement.gone || process.platform === "win32" && exited || await observeRetiringOwner(owner, deadline) === "gone";
|
|
2884
3483
|
if (retirement.unverified && !gone) throw unverifiedQueueOwnerError(owner);
|
|
2885
3484
|
return gone;
|
|
2886
3485
|
}
|
|
3486
|
+
async function waitForReleasedOwner(owner, requireStale, retirement) {
|
|
3487
|
+
if (retirement.unverified && requireStale) throw unverifiedQueueOwnerError(owner);
|
|
3488
|
+
if (retirement.leaseReleased || retirement.unverified) {
|
|
3489
|
+
await waitForProcessExit(owner.pid, Math.max(0, retirement.deadline - performance.now()), isProcessDefinitelyDead);
|
|
3490
|
+
if (performance.now() >= retirement.deadline) {
|
|
3491
|
+
if (retirement.unverified) throw unverifiedQueueOwnerError(owner);
|
|
3492
|
+
return true;
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
return false;
|
|
3496
|
+
}
|
|
3497
|
+
async function retireRecordedDescendants(owner, retirement) {
|
|
3498
|
+
await withQueueLeaseMutation(owner.sessionId, async () => {
|
|
3499
|
+
const current = await readQueueOwnerRecord(owner.sessionId);
|
|
3500
|
+
if (!matchesQueueOwner(current, owner) || current.retirement === void 0) return;
|
|
3501
|
+
if (current.retirement === null || process.platform !== "win32") throw queueRetirementIncomplete();
|
|
3502
|
+
await retireSnapshotDescendants(current, current.retirement, retirement.deadline);
|
|
3503
|
+
}, { capturedIdentity: retirement.capturedIdentity });
|
|
3504
|
+
}
|
|
3505
|
+
function hasNewRetirementWitness(previous, receipt) {
|
|
3506
|
+
const saved = new Map(previous.descendants.map((witness) => [witness.pid, witness.processIdentity]));
|
|
3507
|
+
return receipt.descendants.some((witness) => compareProcessBirthIdentity(saved.get(witness.pid), witness.processIdentity) !== "matching");
|
|
3508
|
+
}
|
|
3509
|
+
async function retireSnapshotDescendants(owner, receipt, deadline) {
|
|
3510
|
+
while (receipt.descendants.length > 0) {
|
|
3511
|
+
const table = await readRetirementSnapshot(deadline);
|
|
3512
|
+
const discovered = captureQueueRetirementReceipt(owner, receipt, false, table);
|
|
3513
|
+
if (hasNewRetirementWitness(receipt, discovered)) {
|
|
3514
|
+
receipt = discovered;
|
|
3515
|
+
owner.retirement = receipt;
|
|
3516
|
+
await replaceQueueOwnerRecord(owner);
|
|
3517
|
+
continue;
|
|
3518
|
+
}
|
|
3519
|
+
signalRecordedDescendants(discovered, table, deadline);
|
|
3520
|
+
await waitForRecordedDescendants(discovered, deadline);
|
|
3521
|
+
discovered.descendants = discovered.descendants.filter((witness) => !isProcessDefinitelyDead(witness.pid));
|
|
3522
|
+
receipt = discovered;
|
|
3523
|
+
owner.retirement = receipt;
|
|
3524
|
+
await replaceQueueOwnerRecord(owner);
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
function leafFirstRetirementWitnesses(receipt, table) {
|
|
3528
|
+
const children = new Map(receipt.descendants.map((witness) => [witness.pid, 0]));
|
|
3529
|
+
for (const witness of receipt.descendants) {
|
|
3530
|
+
const parent = retirementParent(witness.pid, table);
|
|
3531
|
+
const count = children.get(parent);
|
|
3532
|
+
if (count !== void 0) children.set(parent, count + 1);
|
|
3533
|
+
}
|
|
3534
|
+
const pending = receipt.descendants.filter((witness) => children.get(witness.pid) === 0);
|
|
3535
|
+
const byPid = new Map(receipt.descendants.map((witness) => [witness.pid, witness]));
|
|
3536
|
+
for (let index = 0; index < pending.length; index += 1) {
|
|
3537
|
+
const parent = retirementParent(pending[index].pid, table);
|
|
3538
|
+
const count = children.get(parent);
|
|
3539
|
+
if (count !== void 0) {
|
|
3540
|
+
const remaining = count - 1;
|
|
3541
|
+
children.set(parent, remaining);
|
|
3542
|
+
if (remaining === 0) pending.push(byPid.get(parent));
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
if (pending.length !== receipt.descendants.length) throw queueRetirementIncomplete();
|
|
3546
|
+
return pending;
|
|
3547
|
+
}
|
|
3548
|
+
function retirementParent(pid, table) {
|
|
3549
|
+
const identity = table.get(pid);
|
|
3550
|
+
return identity && !hasStaleWindowsParent(identity, table.get(identity.parentPid)) ? identity.parentPid : 0;
|
|
3551
|
+
}
|
|
3552
|
+
function signalRecordedDescendants(receipt, table, deadline) {
|
|
3553
|
+
if (receipt.descendants.some((witness) => observeRetirementSnapshot(witness, table) === "unknown")) throw queueRetirementIncomplete();
|
|
3554
|
+
const ordered = leafFirstRetirementWitnesses(receipt, table);
|
|
3555
|
+
let failure;
|
|
3556
|
+
for (const witness of ordered) {
|
|
3557
|
+
if (observeRetirementSnapshot(witness, table) !== "matching") continue;
|
|
3558
|
+
retirementTimeRemaining(deadline);
|
|
3559
|
+
try {
|
|
3560
|
+
process.kill(witness.pid, "SIGKILL");
|
|
3561
|
+
} catch (error) {
|
|
3562
|
+
if (!isProcessDefinitelyDead(witness.pid)) failure ??= error;
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
if (failure) throw failure;
|
|
3566
|
+
}
|
|
3567
|
+
async function waitForRecordedDescendants(receipt, deadline) {
|
|
3568
|
+
for (let waited = 0; waited < 1e3; waited += PROCESS_POLL_MS) {
|
|
3569
|
+
if (receipt.descendants.every((witness) => isProcessDefinitelyDead(witness.pid))) return;
|
|
3570
|
+
await waitMs(Math.min(PROCESS_POLL_MS, retirementTimeRemaining(deadline)));
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
async function assertNoPendingRetirement(owner) {
|
|
3574
|
+
const current = await readQueueOwnerRecord(owner.sessionId);
|
|
3575
|
+
if (matchesQueueOwner(current, owner) && current.retirement !== void 0) throw queueRetirementIncomplete();
|
|
3576
|
+
}
|
|
2887
3577
|
async function terminateQueueOwnerForSession(sessionId, expectedOwner, requireStale = false) {
|
|
2888
3578
|
await settlePendingQueueLeaseGuard(sessionId);
|
|
2889
3579
|
const owner = expectedOwner ?? await readQueueOwnerRecord(sessionId);
|
|
2890
|
-
if (!owner || owner.sessionId !== sessionId
|
|
2891
|
-
if (
|
|
2892
|
-
|
|
3580
|
+
if (!owner || owner.sessionId !== sessionId) return;
|
|
3581
|
+
if (owner.pid === process.pid) {
|
|
3582
|
+
if (owner.retirement !== void 0) throw queueRetirementIncomplete();
|
|
3583
|
+
return;
|
|
3584
|
+
}
|
|
3585
|
+
const deadline = retirementDeadline();
|
|
3586
|
+
const retirement = await createQueueOwnerRetirement(deadline);
|
|
3587
|
+
if (!await retireQueueOwner(owner, requireStale, retirement)) {
|
|
3588
|
+
await assertNoPendingRetirement(owner);
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3591
|
+
await retireRecordedDescendants(owner, retirement);
|
|
3592
|
+
await cleanupQueueOwnerFiles(sessionId, owner.socketPath, () => ownsQueueLease(owner), retirement.capturedIdentity, deadline);
|
|
2893
3593
|
}
|
|
2894
3594
|
async function waitMs(ms) {
|
|
2895
3595
|
await new Promise((resolve) => {
|
|
@@ -3294,9 +3994,12 @@ function queueOwnerMessageParser(type) {
|
|
|
3294
3994
|
}
|
|
3295
3995
|
function parseEventOwnerMessage(message, context) {
|
|
3296
3996
|
if (!isAcpJsonRpcMessage(message.message)) return null;
|
|
3997
|
+
const direction = message.direction;
|
|
3998
|
+
if (direction !== void 0 && direction !== "inbound" && direction !== "outbound") return null;
|
|
3297
3999
|
return {
|
|
3298
4000
|
type: "event",
|
|
3299
4001
|
...context,
|
|
4002
|
+
...direction === void 0 ? {} : { direction },
|
|
3300
4003
|
message: message.message
|
|
3301
4004
|
};
|
|
3302
4005
|
}
|
|
@@ -3391,6 +4094,610 @@ function assertQueueRequestSize(line) {
|
|
|
3391
4094
|
});
|
|
3392
4095
|
}
|
|
3393
4096
|
//#endregion
|
|
4097
|
+
//#region src/session/queue/socket-output.ts
|
|
4098
|
+
const OUTPUT_CHUNK_BYTES = 65536;
|
|
4099
|
+
const DRAIN_TIMEOUT_MS = 1e3;
|
|
4100
|
+
const DEFAULT_LIMITS = Object.freeze({
|
|
4101
|
+
observerBytes: 67108864,
|
|
4102
|
+
totalBytes: 268435456,
|
|
4103
|
+
observers: 64
|
|
4104
|
+
});
|
|
4105
|
+
var QueueOutputBudget = class {
|
|
4106
|
+
limits;
|
|
4107
|
+
bytes = 0;
|
|
4108
|
+
files = 0;
|
|
4109
|
+
observers = 0;
|
|
4110
|
+
constructor(limits = DEFAULT_LIMITS) {
|
|
4111
|
+
this.limits = limits;
|
|
4112
|
+
}
|
|
4113
|
+
acquire() {
|
|
4114
|
+
if (this.files >= this.limits.observers) throw new Error("Queue output spool descriptor limit reached");
|
|
4115
|
+
this.files += 1;
|
|
4116
|
+
}
|
|
4117
|
+
grow(bytes) {
|
|
4118
|
+
if (bytes > this.limits.totalBytes - this.bytes) throw new Error("Queue output spool storage limit reached");
|
|
4119
|
+
this.bytes += bytes;
|
|
4120
|
+
}
|
|
4121
|
+
release(bytes) {
|
|
4122
|
+
this.bytes -= bytes;
|
|
4123
|
+
this.files -= 1;
|
|
4124
|
+
}
|
|
4125
|
+
acquireObserver() {
|
|
4126
|
+
if (this.observers >= this.limits.observers) throw new Error("Queue blocked observer limit reached");
|
|
4127
|
+
this.observers += 1;
|
|
4128
|
+
}
|
|
4129
|
+
releaseObserver() {
|
|
4130
|
+
this.observers -= 1;
|
|
4131
|
+
}
|
|
4132
|
+
};
|
|
4133
|
+
var OutputRing = class {
|
|
4134
|
+
budget;
|
|
4135
|
+
fd;
|
|
4136
|
+
physicalBytes = 0;
|
|
4137
|
+
readOffset = 0;
|
|
4138
|
+
length = 0;
|
|
4139
|
+
constructor(budget) {
|
|
4140
|
+
this.budget = budget;
|
|
4141
|
+
budget.acquire();
|
|
4142
|
+
try {
|
|
4143
|
+
const root = resolveSecureTempRoot({ fallbackPrefix: "acpx-output" });
|
|
4144
|
+
const file = path.join(root, `${randomUUID()}.tmp`);
|
|
4145
|
+
this.fd = fs.openSync(file, "wx+", 384);
|
|
4146
|
+
fs.unlinkSync(file);
|
|
4147
|
+
} catch (error) {
|
|
4148
|
+
if (this.fd === void 0) budget.release(0);
|
|
4149
|
+
else this.close();
|
|
4150
|
+
throw error;
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
append(bytes, offset) {
|
|
4154
|
+
const capacity = this.budget.limits.observerBytes;
|
|
4155
|
+
if (bytes.length - offset > capacity - this.length) throw new Error("Queue observer output backlog limit reached");
|
|
4156
|
+
while (offset < bytes.length) {
|
|
4157
|
+
const position = (this.readOffset + this.length) % capacity;
|
|
4158
|
+
const count = Math.min(OUTPUT_CHUNK_BYTES, bytes.length - offset, capacity - position);
|
|
4159
|
+
const growth = Math.max(0, position + count - this.physicalBytes);
|
|
4160
|
+
this.budget.grow(growth);
|
|
4161
|
+
this.physicalBytes += growth;
|
|
4162
|
+
const written = fs.writeSync(this.fd, bytes, offset, count, position);
|
|
4163
|
+
if (written <= 0) throw new Error("Queue output spool write made no progress");
|
|
4164
|
+
offset += written;
|
|
4165
|
+
this.length += written;
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
read() {
|
|
4169
|
+
const capacity = this.budget.limits.observerBytes;
|
|
4170
|
+
const bytes = Buffer.allocUnsafeSlow(Math.min(OUTPUT_CHUNK_BYTES, this.length, capacity - this.readOffset));
|
|
4171
|
+
let offset = 0;
|
|
4172
|
+
while (offset < bytes.length) {
|
|
4173
|
+
const read = fs.readSync(this.fd, bytes, offset, bytes.length - offset, this.readOffset + offset);
|
|
4174
|
+
if (read <= 0) throw new Error("Queue output spool ended before its committed bytes");
|
|
4175
|
+
offset += read;
|
|
4176
|
+
}
|
|
4177
|
+
this.readOffset = (this.readOffset + bytes.length) % capacity;
|
|
4178
|
+
this.length -= bytes.length;
|
|
4179
|
+
return bytes;
|
|
4180
|
+
}
|
|
4181
|
+
close() {
|
|
4182
|
+
const fd = this.fd;
|
|
4183
|
+
this.fd = void 0;
|
|
4184
|
+
if (fd !== void 0) {
|
|
4185
|
+
fs.closeSync(fd);
|
|
4186
|
+
this.budget.release(this.physicalBytes);
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
};
|
|
4190
|
+
var QueueSocketOutput = class {
|
|
4191
|
+
socket;
|
|
4192
|
+
budget;
|
|
4193
|
+
ring;
|
|
4194
|
+
blocked = false;
|
|
4195
|
+
ending = false;
|
|
4196
|
+
disposed = false;
|
|
4197
|
+
reserved = false;
|
|
4198
|
+
pendingWrites = 0;
|
|
4199
|
+
constructor(socket, budget) {
|
|
4200
|
+
this.socket = socket;
|
|
4201
|
+
this.budget = budget;
|
|
4202
|
+
socket.on("drain", this.onDrain);
|
|
4203
|
+
socket.once("close", () => {
|
|
4204
|
+
this.disposed = true;
|
|
4205
|
+
socket.off("drain", this.onDrain);
|
|
4206
|
+
this.closeSpool();
|
|
4207
|
+
this.releaseObserver();
|
|
4208
|
+
});
|
|
4209
|
+
}
|
|
4210
|
+
send(message) {
|
|
4211
|
+
if (this.ending || this.socket.destroyed || !this.socket.writable) return;
|
|
4212
|
+
try {
|
|
4213
|
+
this.writeFrame(Buffer.from(`${JSON.stringify(message)}\n`));
|
|
4214
|
+
} catch (error) {
|
|
4215
|
+
this.fail(error);
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
writeFrame(bytes) {
|
|
4219
|
+
let offset = 0;
|
|
4220
|
+
while (!this.blocked && offset < bytes.length) {
|
|
4221
|
+
const end = Math.min(offset + OUTPUT_CHUNK_BYTES, bytes.length);
|
|
4222
|
+
const chunk = Buffer.allocUnsafeSlow(end - offset);
|
|
4223
|
+
bytes.copy(chunk, 0, offset, end);
|
|
4224
|
+
this.write(chunk);
|
|
4225
|
+
offset = end;
|
|
4226
|
+
}
|
|
4227
|
+
if (offset < bytes.length) {
|
|
4228
|
+
this.ring ??= new OutputRing(this.budget);
|
|
4229
|
+
this.ring.append(bytes, offset);
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
end() {
|
|
4233
|
+
if (this.ending || this.socket.destroyed) return;
|
|
4234
|
+
this.ending = true;
|
|
4235
|
+
this.armTimeout();
|
|
4236
|
+
this.finishIfDrained();
|
|
4237
|
+
}
|
|
4238
|
+
write(bytes) {
|
|
4239
|
+
if (!this.reserved) {
|
|
4240
|
+
this.budget.acquireObserver();
|
|
4241
|
+
this.reserved = true;
|
|
4242
|
+
}
|
|
4243
|
+
this.pendingWrites += 1;
|
|
4244
|
+
try {
|
|
4245
|
+
this.blocked = !this.socket.write(bytes, this.onWrite);
|
|
4246
|
+
} catch (error) {
|
|
4247
|
+
this.pendingWrites -= 1;
|
|
4248
|
+
throw error;
|
|
4249
|
+
}
|
|
4250
|
+
if (this.blocked && !this.socket.timeout) this.armTimeout();
|
|
4251
|
+
}
|
|
4252
|
+
onWrite = (error) => {
|
|
4253
|
+
this.pendingWrites -= 1;
|
|
4254
|
+
if (this.disposed) return;
|
|
4255
|
+
if (error) {
|
|
4256
|
+
this.fail(error);
|
|
4257
|
+
return;
|
|
4258
|
+
}
|
|
4259
|
+
this.releaseIfIdle();
|
|
4260
|
+
};
|
|
4261
|
+
armTimeout() {
|
|
4262
|
+
if (process.platform !== "win32") this.socket.setTimeout(DRAIN_TIMEOUT_MS);
|
|
4263
|
+
}
|
|
4264
|
+
onDrain = () => {
|
|
4265
|
+
if (this.disposed) return;
|
|
4266
|
+
this.blocked = false;
|
|
4267
|
+
if (!this.ending) this.socket.setTimeout(0);
|
|
4268
|
+
try {
|
|
4269
|
+
this.pump();
|
|
4270
|
+
this.releaseIfIdle();
|
|
4271
|
+
this.finishIfDrained();
|
|
4272
|
+
} catch (error) {
|
|
4273
|
+
this.fail(error);
|
|
4274
|
+
}
|
|
4275
|
+
};
|
|
4276
|
+
pump() {
|
|
4277
|
+
while (!this.blocked && this.ring && this.ring.length > 0) this.write(this.ring.read());
|
|
4278
|
+
if (this.ring?.length === 0) this.closeSpool();
|
|
4279
|
+
}
|
|
4280
|
+
finishIfDrained() {
|
|
4281
|
+
if (this.ending && !this.ring?.length && !this.socket.writableEnded) this.socket.end(() => this.socket.destroy());
|
|
4282
|
+
}
|
|
4283
|
+
releaseObserver() {
|
|
4284
|
+
if (this.reserved) {
|
|
4285
|
+
this.reserved = false;
|
|
4286
|
+
this.budget.releaseObserver();
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
releaseIfIdle() {
|
|
4290
|
+
if (this.pendingWrites === 0 && !this.blocked && !this.ring?.length) this.releaseObserver();
|
|
4291
|
+
}
|
|
4292
|
+
closeSpool() {
|
|
4293
|
+
const ring = this.ring;
|
|
4294
|
+
this.ring = void 0;
|
|
4295
|
+
try {
|
|
4296
|
+
ring?.close();
|
|
4297
|
+
} catch (error) {
|
|
4298
|
+
this.socket.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
fail(error) {
|
|
4302
|
+
this.disposed = true;
|
|
4303
|
+
this.socket.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
4304
|
+
this.closeSpool();
|
|
4305
|
+
}
|
|
4306
|
+
};
|
|
4307
|
+
//#endregion
|
|
4308
|
+
//#region src/session/queue/ipc-server.ts
|
|
4309
|
+
const QUEUE_SOCKET_DRAIN_TIMEOUT_MS = 1e3;
|
|
4310
|
+
function makeQueueOwnerError(requestId, message, detailCode, options = {}) {
|
|
4311
|
+
return {
|
|
4312
|
+
type: "error",
|
|
4313
|
+
requestId,
|
|
4314
|
+
ownerGeneration: void 0,
|
|
4315
|
+
code: "RUNTIME",
|
|
4316
|
+
detailCode,
|
|
4317
|
+
origin: "queue",
|
|
4318
|
+
retryable: options.retryable,
|
|
4319
|
+
message
|
|
4320
|
+
};
|
|
4321
|
+
}
|
|
4322
|
+
function makeQueueOwnerErrorFromUnknown(requestId, error, detailCode, options = {}) {
|
|
4323
|
+
const normalized = normalizeOutputError(error, {
|
|
4324
|
+
defaultCode: "RUNTIME",
|
|
4325
|
+
origin: "queue",
|
|
4326
|
+
detailCode,
|
|
4327
|
+
retryable: options.retryable
|
|
4328
|
+
});
|
|
4329
|
+
return {
|
|
4330
|
+
type: "error",
|
|
4331
|
+
requestId,
|
|
4332
|
+
code: normalized.code,
|
|
4333
|
+
detailCode: normalized.detailCode,
|
|
4334
|
+
origin: normalized.origin,
|
|
4335
|
+
message: normalized.message,
|
|
4336
|
+
retryable: normalized.retryable,
|
|
4337
|
+
acp: normalized.acp
|
|
4338
|
+
};
|
|
4339
|
+
}
|
|
4340
|
+
var SessionQueueOwner = class SessionQueueOwner {
|
|
4341
|
+
server;
|
|
4342
|
+
controlHandlers;
|
|
4343
|
+
ownerGeneration;
|
|
4344
|
+
maxQueueDepth;
|
|
4345
|
+
maxRequestBytes;
|
|
4346
|
+
onQueueDepthChanged;
|
|
4347
|
+
pending = [];
|
|
4348
|
+
activeTask;
|
|
4349
|
+
waiters = [];
|
|
4350
|
+
sockets = /* @__PURE__ */ new Set();
|
|
4351
|
+
taskSockets = /* @__PURE__ */ new Set();
|
|
4352
|
+
drainingSockets = /* @__PURE__ */ new Set();
|
|
4353
|
+
outputBudget = new QueueOutputBudget();
|
|
4354
|
+
outputs = /* @__PURE__ */ new Map();
|
|
4355
|
+
closed = false;
|
|
4356
|
+
constructor(server, controlHandlers, lease, options) {
|
|
4357
|
+
this.server = server;
|
|
4358
|
+
this.controlHandlers = controlHandlers;
|
|
4359
|
+
this.ownerGeneration = lease.ownerGeneration;
|
|
4360
|
+
this.maxQueueDepth = Math.max(1, Math.round(options.maxQueueDepth));
|
|
4361
|
+
this.maxRequestBytes = queueRequestByteLimit(options.maxRequestBytes?.toString());
|
|
4362
|
+
this.onQueueDepthChanged = options.onQueueDepthChanged;
|
|
4363
|
+
}
|
|
4364
|
+
static async start(lease, controlHandlers, options = { maxQueueDepth: 16 }) {
|
|
4365
|
+
const ownerRef = { current: void 0 };
|
|
4366
|
+
const server = net.createServer((socket) => {
|
|
4367
|
+
ownerRef.current?.handleConnection(socket);
|
|
4368
|
+
});
|
|
4369
|
+
ownerRef.current = new SessionQueueOwner(server, controlHandlers, lease, options);
|
|
4370
|
+
await new Promise((resolve, reject) => {
|
|
4371
|
+
const onListening = () => {
|
|
4372
|
+
server.off("error", onError);
|
|
4373
|
+
resolve();
|
|
4374
|
+
};
|
|
4375
|
+
const onError = (error) => {
|
|
4376
|
+
server.off("listening", onListening);
|
|
4377
|
+
reject(error);
|
|
4378
|
+
};
|
|
4379
|
+
server.once("listening", onListening);
|
|
4380
|
+
server.once("error", onError);
|
|
4381
|
+
server.listen(lease.socketPath);
|
|
4382
|
+
});
|
|
4383
|
+
return ownerRef.current;
|
|
4384
|
+
}
|
|
4385
|
+
beginShutdown() {
|
|
4386
|
+
if (this.closed) return;
|
|
4387
|
+
this.closed = true;
|
|
4388
|
+
for (const waiter of this.waiters.splice(0)) waiter(void 0);
|
|
4389
|
+
for (const task of this.pending.splice(0)) {
|
|
4390
|
+
if (task.waitForCompletion) task.send(makeQueueOwnerError(task.requestId, "Queue owner shutting down before prompt execution", "QUEUE_OWNER_SHUTTING_DOWN", { retryable: true }));
|
|
4391
|
+
task.close();
|
|
4392
|
+
}
|
|
4393
|
+
for (const socket of this.sockets) if (!this.taskSockets.has(socket) && !this.drainingSockets.has(socket)) socket.destroy();
|
|
4394
|
+
this.emitQueueDepth();
|
|
4395
|
+
}
|
|
4396
|
+
async close() {
|
|
4397
|
+
this.beginShutdown();
|
|
4398
|
+
await new Promise((resolve) => {
|
|
4399
|
+
const forceCloseTimer = setTimeout(() => {
|
|
4400
|
+
for (const socket of this.sockets) socket.destroy();
|
|
4401
|
+
}, QUEUE_SOCKET_DRAIN_TIMEOUT_MS);
|
|
4402
|
+
forceCloseTimer.unref();
|
|
4403
|
+
this.server.close(() => {
|
|
4404
|
+
clearTimeout(forceCloseTimer);
|
|
4405
|
+
resolve();
|
|
4406
|
+
});
|
|
4407
|
+
});
|
|
4408
|
+
}
|
|
4409
|
+
async nextTask(timeoutMs) {
|
|
4410
|
+
if (this.pending.length > 0) {
|
|
4411
|
+
const task = this.pending.shift();
|
|
4412
|
+
this.emitQueueDepth();
|
|
4413
|
+
if (task) {
|
|
4414
|
+
this.activeTask = task;
|
|
4415
|
+
recordPerfDuration("queue.owner.wait_ms", Date.now() - task.enqueuedAt);
|
|
4416
|
+
}
|
|
4417
|
+
return task;
|
|
4418
|
+
}
|
|
4419
|
+
if (this.closed) return;
|
|
4420
|
+
return await new Promise((resolve) => {
|
|
4421
|
+
const timer = timeoutMs != null && setTimeout(() => {
|
|
4422
|
+
const index = this.waiters.indexOf(waiter);
|
|
4423
|
+
if (index >= 0) this.waiters.splice(index, 1);
|
|
4424
|
+
resolve(void 0);
|
|
4425
|
+
}, Math.max(0, timeoutMs));
|
|
4426
|
+
const waiter = (task) => {
|
|
4427
|
+
if (timer) clearTimeout(timer);
|
|
4428
|
+
resolve(task);
|
|
4429
|
+
};
|
|
4430
|
+
this.waiters.push(waiter);
|
|
4431
|
+
});
|
|
4432
|
+
}
|
|
4433
|
+
queueDepth() {
|
|
4434
|
+
return this.pending.length;
|
|
4435
|
+
}
|
|
4436
|
+
completeTask(task) {
|
|
4437
|
+
if (this.activeTask === task) this.activeTask = void 0;
|
|
4438
|
+
}
|
|
4439
|
+
async cancelPrompt(targetRequestId) {
|
|
4440
|
+
if (targetRequestId === void 0) return await this.controlHandlers.cancelPrompt();
|
|
4441
|
+
const index = this.pending.findIndex((task) => task.requestId === targetRequestId);
|
|
4442
|
+
if (index >= 0) {
|
|
4443
|
+
const [task] = this.pending.splice(index, 1);
|
|
4444
|
+
task.send(makeQueueOwnerError(task.requestId, "Prompt cancelled before execution", "QUEUE_REQUEST_CANCELLED", { retryable: false }));
|
|
4445
|
+
task.close();
|
|
4446
|
+
this.emitQueueDepth();
|
|
4447
|
+
return true;
|
|
4448
|
+
}
|
|
4449
|
+
return this.activeTask?.requestId === targetRequestId ? await this.controlHandlers.cancelPrompt() : false;
|
|
4450
|
+
}
|
|
4451
|
+
emitQueueDepth() {
|
|
4452
|
+
this.onQueueDepthChanged?.(this.pending.length);
|
|
4453
|
+
}
|
|
4454
|
+
enqueue(task) {
|
|
4455
|
+
if (this.closed) {
|
|
4456
|
+
task.send(makeQueueOwnerError(task.requestId, "Queue owner is shutting down", "QUEUE_OWNER_SHUTTING_DOWN", { retryable: true }));
|
|
4457
|
+
task.close();
|
|
4458
|
+
return false;
|
|
4459
|
+
}
|
|
4460
|
+
if (this.activeTask?.requestId === task.requestId || this.pending.some((queued) => queued.requestId === task.requestId)) {
|
|
4461
|
+
task.send(makeQueueOwnerError(task.requestId, "Request is already queued or active", "QUEUE_REQUEST_DUPLICATE", { retryable: false }));
|
|
4462
|
+
task.close();
|
|
4463
|
+
return false;
|
|
4464
|
+
}
|
|
4465
|
+
const waiter = this.waiters.shift();
|
|
4466
|
+
if (waiter) {
|
|
4467
|
+
this.activeTask = task;
|
|
4468
|
+
waiter(task);
|
|
4469
|
+
return true;
|
|
4470
|
+
}
|
|
4471
|
+
if (this.pending.length >= this.maxQueueDepth) {
|
|
4472
|
+
task.send({
|
|
4473
|
+
...makeQueueOwnerError(task.requestId, `Queue owner is overloaded (${this.pending.length}/${this.maxQueueDepth} queued)`, "QUEUE_OWNER_OVERLOADED", { retryable: true }),
|
|
4474
|
+
ownerGeneration: this.ownerGeneration
|
|
4475
|
+
});
|
|
4476
|
+
task.close();
|
|
4477
|
+
return false;
|
|
4478
|
+
}
|
|
4479
|
+
this.pending.push(task);
|
|
4480
|
+
this.emitQueueDepth();
|
|
4481
|
+
return true;
|
|
4482
|
+
}
|
|
4483
|
+
handleControlRequest(options) {
|
|
4484
|
+
this.taskSockets.add(options.socket);
|
|
4485
|
+
this.writeQueueMessage(options.socket, {
|
|
4486
|
+
type: "accepted",
|
|
4487
|
+
requestId: options.requestId,
|
|
4488
|
+
ownerGeneration: this.ownerGeneration
|
|
4489
|
+
});
|
|
4490
|
+
options.run().then((message) => {
|
|
4491
|
+
this.writeQueueMessage(options.socket, {
|
|
4492
|
+
...message,
|
|
4493
|
+
ownerGeneration: this.ownerGeneration
|
|
4494
|
+
});
|
|
4495
|
+
}).catch((error) => {
|
|
4496
|
+
this.writeQueueMessage(options.socket, {
|
|
4497
|
+
...makeQueueOwnerErrorFromUnknown(options.requestId, error, "QUEUE_CONTROL_REQUEST_FAILED"),
|
|
4498
|
+
ownerGeneration: this.ownerGeneration
|
|
4499
|
+
});
|
|
4500
|
+
}).finally(() => {
|
|
4501
|
+
this.endSocket(options.socket);
|
|
4502
|
+
});
|
|
4503
|
+
}
|
|
4504
|
+
writeQueueMessage(socket, message) {
|
|
4505
|
+
this.outputs.get(socket)?.send(message);
|
|
4506
|
+
}
|
|
4507
|
+
endSocket(socket) {
|
|
4508
|
+
if (socket.destroyed || this.drainingSockets.has(socket)) return;
|
|
4509
|
+
this.taskSockets.delete(socket);
|
|
4510
|
+
this.drainingSockets.add(socket);
|
|
4511
|
+
this.outputs.get(socket)?.end();
|
|
4512
|
+
}
|
|
4513
|
+
failRequest(socket, requestId, message, detailCode) {
|
|
4514
|
+
this.writeQueueMessage(socket, {
|
|
4515
|
+
...makeQueueOwnerError(requestId, message, detailCode, { retryable: false }),
|
|
4516
|
+
ownerGeneration: this.ownerGeneration
|
|
4517
|
+
});
|
|
4518
|
+
this.endSocket(socket);
|
|
4519
|
+
}
|
|
4520
|
+
parseRequestLine(socket, line) {
|
|
4521
|
+
let parsed;
|
|
4522
|
+
try {
|
|
4523
|
+
parsed = JSON.parse(line);
|
|
4524
|
+
} catch {
|
|
4525
|
+
this.failRequest(socket, "unknown", "Invalid queue request payload", "QUEUE_REQUEST_PAYLOAD_INVALID_JSON");
|
|
4526
|
+
return;
|
|
4527
|
+
}
|
|
4528
|
+
const request = parseQueueRequest(parsed);
|
|
4529
|
+
if (!request) this.failRequest(socket, "unknown", "Invalid queue request", "QUEUE_REQUEST_INVALID");
|
|
4530
|
+
return request ?? void 0;
|
|
4531
|
+
}
|
|
4532
|
+
rejectStaleOwnerGeneration(socket, request) {
|
|
4533
|
+
if (request.ownerGeneration === void 0 || this.ownerGeneration === void 0 || request.ownerGeneration === this.ownerGeneration) return false;
|
|
4534
|
+
this.failRequest(socket, request.requestId, "Queue request targeted a stale queue owner generation", "QUEUE_OWNER_GENERATION_MISMATCH");
|
|
4535
|
+
return true;
|
|
4536
|
+
}
|
|
4537
|
+
rejectClosedOwner(socket, request) {
|
|
4538
|
+
if (!this.closed) return false;
|
|
4539
|
+
this.writeQueueMessage(socket, {
|
|
4540
|
+
...makeQueueOwnerError(request.requestId, "Queue owner is shutting down", "QUEUE_OWNER_CLOSED", { retryable: true }),
|
|
4541
|
+
ownerGeneration: this.ownerGeneration
|
|
4542
|
+
});
|
|
4543
|
+
this.endSocket(socket);
|
|
4544
|
+
return true;
|
|
4545
|
+
}
|
|
4546
|
+
handleControlQueueRequest(socket, request) {
|
|
4547
|
+
if (request.type === "cancel_prompt") {
|
|
4548
|
+
this.handleControlRequest({
|
|
4549
|
+
socket,
|
|
4550
|
+
requestId: request.requestId,
|
|
4551
|
+
run: async () => ({
|
|
4552
|
+
type: "cancel_result",
|
|
4553
|
+
requestId: request.requestId,
|
|
4554
|
+
cancelled: await this.cancelPrompt(request.targetRequestId)
|
|
4555
|
+
})
|
|
4556
|
+
});
|
|
4557
|
+
return true;
|
|
4558
|
+
}
|
|
4559
|
+
if (request.type === "close_session") {
|
|
4560
|
+
this.handleControlRequest({
|
|
4561
|
+
socket,
|
|
4562
|
+
requestId: request.requestId,
|
|
4563
|
+
run: async () => ({
|
|
4564
|
+
type: "close_session_result",
|
|
4565
|
+
requestId: request.requestId,
|
|
4566
|
+
closed: await this.controlHandlers.closeSession(request.timeoutMs)
|
|
4567
|
+
})
|
|
4568
|
+
});
|
|
4569
|
+
return true;
|
|
4570
|
+
}
|
|
4571
|
+
return this.handleSessionControlQueueRequest(socket, request);
|
|
4572
|
+
}
|
|
4573
|
+
handleSessionControlQueueRequest(socket, request) {
|
|
4574
|
+
if (request.type === "set_mode") {
|
|
4575
|
+
this.handleControlRequest({
|
|
4576
|
+
socket,
|
|
4577
|
+
requestId: request.requestId,
|
|
4578
|
+
run: async () => {
|
|
4579
|
+
await this.controlHandlers.setSessionMode(request.modeId, request.timeoutMs);
|
|
4580
|
+
return {
|
|
4581
|
+
type: "set_mode_result",
|
|
4582
|
+
requestId: request.requestId,
|
|
4583
|
+
modeId: request.modeId
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
});
|
|
4587
|
+
return true;
|
|
4588
|
+
}
|
|
4589
|
+
if (request.type === "set_model") {
|
|
4590
|
+
this.handleControlRequest({
|
|
4591
|
+
socket,
|
|
4592
|
+
requestId: request.requestId,
|
|
4593
|
+
run: async () => {
|
|
4594
|
+
const response = await this.controlHandlers.setSessionModel(request.modelId, request.timeoutMs);
|
|
4595
|
+
return {
|
|
4596
|
+
type: "set_model_result",
|
|
4597
|
+
requestId: request.requestId,
|
|
4598
|
+
modelId: request.modelId,
|
|
4599
|
+
response
|
|
4600
|
+
};
|
|
4601
|
+
}
|
|
4602
|
+
});
|
|
4603
|
+
return true;
|
|
4604
|
+
}
|
|
4605
|
+
if (request.type === "set_config_option") {
|
|
4606
|
+
this.handleControlRequest({
|
|
4607
|
+
socket,
|
|
4608
|
+
requestId: request.requestId,
|
|
4609
|
+
run: async () => ({
|
|
4610
|
+
type: "set_config_option_result",
|
|
4611
|
+
requestId: request.requestId,
|
|
4612
|
+
response: await this.controlHandlers.setSessionConfigOption(request.configId, request.value, request.timeoutMs)
|
|
4613
|
+
})
|
|
4614
|
+
});
|
|
4615
|
+
return true;
|
|
4616
|
+
}
|
|
4617
|
+
return false;
|
|
4618
|
+
}
|
|
4619
|
+
enqueuePromptRequest(socket, request) {
|
|
4620
|
+
this.taskSockets.add(socket);
|
|
4621
|
+
const task = {
|
|
4622
|
+
requestId: request.requestId,
|
|
4623
|
+
message: request.message,
|
|
4624
|
+
prompt: request.prompt ?? textPrompt(request.message),
|
|
4625
|
+
permissionMode: request.permissionMode,
|
|
4626
|
+
resumePolicy: request.resumePolicy,
|
|
4627
|
+
nonInteractivePermissions: request.nonInteractivePermissions,
|
|
4628
|
+
permissionPolicy: request.permissionPolicy,
|
|
4629
|
+
timeoutMs: request.timeoutMs,
|
|
4630
|
+
suppressSdkConsoleErrors: request.suppressSdkConsoleErrors,
|
|
4631
|
+
promptRetries: request.promptRetries,
|
|
4632
|
+
sessionOptions: request.sessionOptions,
|
|
4633
|
+
waitForCompletion: request.waitForCompletion,
|
|
4634
|
+
reportPromptStarted: request.reportPromptStarted,
|
|
4635
|
+
enqueuedAt: Date.now(),
|
|
4636
|
+
send: (message) => {
|
|
4637
|
+
this.writeQueueMessage(socket, {
|
|
4638
|
+
...message,
|
|
4639
|
+
ownerGeneration: this.ownerGeneration
|
|
4640
|
+
});
|
|
4641
|
+
},
|
|
4642
|
+
close: () => {
|
|
4643
|
+
this.endSocket(socket);
|
|
4644
|
+
}
|
|
4645
|
+
};
|
|
4646
|
+
if (!this.enqueue(task)) return;
|
|
4647
|
+
this.writeQueueMessage(socket, {
|
|
4648
|
+
type: "accepted",
|
|
4649
|
+
requestId: request.requestId,
|
|
4650
|
+
ownerGeneration: this.ownerGeneration
|
|
4651
|
+
});
|
|
4652
|
+
if (!request.waitForCompletion) task.close();
|
|
4653
|
+
}
|
|
4654
|
+
handleConnection(socket) {
|
|
4655
|
+
this.sockets.add(socket);
|
|
4656
|
+
this.outputs.set(socket, new QueueSocketOutput(socket, this.outputBudget));
|
|
4657
|
+
socket.setEncoding("utf8");
|
|
4658
|
+
socket.on("timeout", () => socket.destroy());
|
|
4659
|
+
socket.once("close", () => {
|
|
4660
|
+
this.outputs.delete(socket);
|
|
4661
|
+
this.sockets.delete(socket);
|
|
4662
|
+
this.taskSockets.delete(socket);
|
|
4663
|
+
this.drainingSockets.delete(socket);
|
|
4664
|
+
});
|
|
4665
|
+
let buffer = "";
|
|
4666
|
+
let handled = false;
|
|
4667
|
+
const processLine = (line) => {
|
|
4668
|
+
if (handled) return;
|
|
4669
|
+
handled = true;
|
|
4670
|
+
const request = this.parseRequestLine(socket, line);
|
|
4671
|
+
if (!request || this.rejectClosedOwner(socket, request) || this.rejectStaleOwnerGeneration(socket, request)) return;
|
|
4672
|
+
if (this.handleControlQueueRequest(socket, request)) return;
|
|
4673
|
+
if (request.type !== "submit_prompt") {
|
|
4674
|
+
this.failRequest(socket, request.requestId, "Invalid queue request", "QUEUE_REQUEST_INVALID");
|
|
4675
|
+
return;
|
|
4676
|
+
}
|
|
4677
|
+
this.enqueuePromptRequest(socket, request);
|
|
4678
|
+
};
|
|
4679
|
+
socket.on("data", (chunk) => {
|
|
4680
|
+
buffer += chunk;
|
|
4681
|
+
let index = buffer.indexOf("\n");
|
|
4682
|
+
while (index >= 0) {
|
|
4683
|
+
const rawLine = buffer.slice(0, index);
|
|
4684
|
+
if (this.rejectOversizedRequest(socket, rawLine)) return;
|
|
4685
|
+
const line = rawLine.trim();
|
|
4686
|
+
buffer = buffer.slice(index + 1);
|
|
4687
|
+
if (line.length > 0) processLine(line);
|
|
4688
|
+
index = buffer.indexOf("\n");
|
|
4689
|
+
}
|
|
4690
|
+
this.rejectOversizedRequest(socket, buffer);
|
|
4691
|
+
});
|
|
4692
|
+
socket.on("error", () => {});
|
|
4693
|
+
}
|
|
4694
|
+
rejectOversizedRequest(socket, line) {
|
|
4695
|
+
if (!queueRequestExceedsLimit(line, this.maxRequestBytes)) return false;
|
|
4696
|
+
socket.destroy();
|
|
4697
|
+
return true;
|
|
4698
|
+
}
|
|
4699
|
+
};
|
|
4700
|
+
//#endregion
|
|
3394
4701
|
//#region src/session/queue/ipc.ts
|
|
3395
4702
|
const MAX_MESSAGE_BUFFER_SIZE = 10485760;
|
|
3396
4703
|
function assertOwnerGeneration(owner, message) {
|
|
@@ -3578,7 +4885,7 @@ function unexpectedQueueResponseError() {
|
|
|
3578
4885
|
}
|
|
3579
4886
|
function handleAcknowledgedSubmitMessage(message, controls, formatter) {
|
|
3580
4887
|
if (message.type === "event") {
|
|
3581
|
-
notifyObserver(() => formatter.onAcpMessage(message.message));
|
|
4888
|
+
notifyObserver(() => formatter.onAcpMessage(message.message, message.direction));
|
|
3582
4889
|
return;
|
|
3583
4890
|
}
|
|
3584
4891
|
if (message.type === "permission_escalation") {
|
|
@@ -3861,6 +5168,6 @@ async function trySetConfigOptionOnRunningOwner(sessionId, configId, value, time
|
|
|
3861
5168
|
});
|
|
3862
5169
|
}
|
|
3863
5170
|
//#endregion
|
|
3864
|
-
export {
|
|
5171
|
+
export { resetPerfMetrics as $, findSession as A, PERMISSION_POLICY_ACTIONS as At, assertPersistedKeyPolicy as B, PermissionDeniedError as Bt, mergePromptSourceWithText as C, extractAcpError as Ct, DEFAULT_HISTORY_LIMIT as D, NON_INTERACTIVE_PERMISSION_POLICIES as Dt, textPrompt as E, EXIT_CODES as Et, normalizeName as F, AgentStartupError as Ft, SESSION_JOURNAL_SCHEMA as G, SessionModelReplayError as Gt, serializeSessionRecordForDisk as H, QueueConnectionError as Ht, pruneSessions as I, AuthPolicyError as It, watchSession as J, UnsupportedPromptContentError as Jt, SessionJournalReader as K, SessionNotFoundError as Kt, readSessionRecord as L, ClaudeAcpSessionCreateTimeoutError as Lt, isoNow as M, AcpxOperationalError as Mt, listSessions as N, AgentDisconnectedError as Nt, absolutePath as O, OUTPUT_FORMATS as Ot, listSessionsForAgent as P, AgentSpawnError as Pt, measurePerf as Q, resolveSessionRecord as R, CopilotAcpUnsupportedError as Rt, getUnsupportedPromptContentMessage as S, normalizeOutputError as St, promptToDisplayText as T, AUTH_POLICIES as Tt, extractAgentSessionId as U, SessionConfigOptionReplayError as Ut, parseSessionRecord as V, PermissionPromptUnavailableError as Vt, normalizeAgentSessionId as W, SessionModeReplayError as Wt, getPerfMetricsSnapshot as X, formatPerfMetric as Y, incrementPerfCounter as Z, createLockOwner as _, isProcessAlive as _t, trySetModelOnRunningOwner as a, isSessionUpdateNotification as at, observeProcessIncarnation as b, isAcpQueryClosedBeforeResponseError as bt, probeQueueOwnerHealth as c, parsePromptStopReason as ct, releaseQueueOwnerLease as d, sessionBaseDir as dt, setPerfGauge as et, terminateProcess as f, sessionEventActivePath as ft, QueueLeaseGuardSettlementError as g, writePrivateJsonFile as gt, waitMs as h, writePrivateFile as ht, trySetModeOnRunningOwner as i, isAcpMessageObject as it, findSessionByDirectoryWalk as j, SESSION_RECORD_SCHEMA as jt, findGitRepositoryRoot as k, PERMISSION_MODES as kt, readQueueOwnerRecord as l, DEFAULT_EVENT_SEGMENT_MAX_BYTES as lt, tryAcquireQueueOwnerLease as m, sessionEventSegmentPath as mt, tryCloseSessionOnRunningOwner as n, extractSessionUpdateNotification as nt, trySubmitToRunningOwner as o, parseJsonRpcErrorMessage as ot, terminateQueueOwnerForSession as p, sessionEventLockPath as pt, SessionWatchError as q, SessionResumeRequiredError as qt, trySetConfigOptionOnRunningOwner as r, isAcpJsonRpcMessage as rt, SessionQueueOwner as s, parsePermissionNotice as st, tryCancelOnRunningOwner as t, startPerfTimer as tt, refreshQueueOwnerLease as u, defaultSessionEventLog as ut, lockOwnerPid as v, exitCodeForOutputErrorCode as vt, parsePromptSource as w, isAcpResourceNotFoundError as wt, PromptInputValidationError as x, isRetryablePromptError as xt, ProcessDescendants as y, formatErrorMessage as yt, writeSessionRecord as z, GeminiAcpStartupTimeoutError as zt };
|
|
3865
5172
|
|
|
3866
|
-
//# sourceMappingURL=ipc-
|
|
5173
|
+
//# sourceMappingURL=ipc-D0ZMQ0q9.js.map
|