acpx 0.19.0 → 0.19.1
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-BerngYEz.js} +5 -5
- package/dist/{cli-Bmki9cj5.js.map → cli-BerngYEz.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-DimX0it5.js} +33 -20
- package/dist/flows-DimX0it5.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-vobVApfO.js} +5 -5
- package/dist/{invocation-options-DRzxHZmr.js.map → invocation-options-vobVApfO.js.map} +1 -1
- package/dist/{ipc-fMgj5vrO.js → ipc-DAApJ5mi.js} +536 -57
- package/dist/ipc-DAApJ5mi.js.map +1 -0
- package/dist/{output-D6kDs8BN.js → output-Cnuji8Bo.js} +20 -12
- package/dist/output-Cnuji8Bo.js.map +1 -0
- package/dist/{queue-owner-runtime-CLwjMYNL.js → queue-owner-runtime-C4OOosbo.js} +253 -240
- package/dist/queue-owner-runtime-C4OOosbo.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-Bj9LoYUq.js} +4 -4
- package/dist/{session-watch-BfdIo-n4.js.map → session-watch-Bj9LoYUq.js.map} +1 -1
- package/dist/{watch-DH-zXQH2.js → watch-Cz6jbrRs.js} +2 -2
- package/dist/{watch-DH-zXQH2.js.map → watch-Cz6jbrRs.js.map} +1 -1
- 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
|
@@ -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";
|
|
@@ -1727,17 +1727,18 @@ async function readSessionRecord(sessionId) {
|
|
|
1727
1727
|
return;
|
|
1728
1728
|
}
|
|
1729
1729
|
}
|
|
1730
|
-
function
|
|
1730
|
+
function hasGitMarker(dir) {
|
|
1731
1731
|
const gitPath = path.join(dir, ".git");
|
|
1732
1732
|
try {
|
|
1733
|
-
|
|
1733
|
+
const marker = statSync(gitPath);
|
|
1734
|
+
return marker.isDirectory() || marker.isFile();
|
|
1734
1735
|
} catch {
|
|
1735
1736
|
return false;
|
|
1736
1737
|
}
|
|
1737
1738
|
}
|
|
1738
1739
|
function isWithinBoundary(boundary, target) {
|
|
1739
1740
|
const relative = path.relative(boundary, target);
|
|
1740
|
-
return relative
|
|
1741
|
+
return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
1741
1742
|
}
|
|
1742
1743
|
function absolutePath(value) {
|
|
1743
1744
|
return path.resolve(value);
|
|
@@ -1746,7 +1747,7 @@ function findGitRepositoryRoot(startDir) {
|
|
|
1746
1747
|
let current = absolutePath(startDir);
|
|
1747
1748
|
const root = path.parse(current).root;
|
|
1748
1749
|
for (;;) {
|
|
1749
|
-
if (
|
|
1750
|
+
if (hasGitMarker(current)) return current;
|
|
1750
1751
|
if (current === root) return;
|
|
1751
1752
|
const parent = path.dirname(current);
|
|
1752
1753
|
if (parent === current) return;
|
|
@@ -2052,6 +2053,39 @@ const LINUX_PROCESS_QUERY = `
|
|
|
2052
2053
|
if (error.code === 'ENOENT') return 'unsupported';
|
|
2053
2054
|
throw error;
|
|
2054
2055
|
});
|
|
2056
|
+
const prefix = async (name, size) => {
|
|
2057
|
+
const file = await fs.open(name, 'r');
|
|
2058
|
+
try {
|
|
2059
|
+
const buffer = Buffer.alloc(size);
|
|
2060
|
+
const { bytesRead } = await file.read(buffer, 0, size, 0);
|
|
2061
|
+
return buffer.subarray(0, bytesRead).toString('base64');
|
|
2062
|
+
} finally { await file.close(); }
|
|
2063
|
+
};
|
|
2064
|
+
const targets = async () => {
|
|
2065
|
+
if (targetPid !== 'all') {
|
|
2066
|
+
return { targetStat: await fs.readFile('/proc/' + targetPid + '/stat', 'utf8').catch(() => null) };
|
|
2067
|
+
}
|
|
2068
|
+
const required = new Set(JSON.parse(process.argv[3] || '[]'));
|
|
2069
|
+
const pids = new Set((await fs.readdir('/proc')).filter(pid => /^[1-9][0-9]*$/.test(pid)));
|
|
2070
|
+
for (const pid of required) pids.add(String(pid));
|
|
2071
|
+
const processStats = [];
|
|
2072
|
+
let bytes = 0;
|
|
2073
|
+
// Sequential reads keep proc I/O and retained memory bounded within this
|
|
2074
|
+
// single killable helper; never launch one helper or pending read per PID.
|
|
2075
|
+
for (const pid of [...pids].sort((a, b) => Number(a) - Number(b))) {
|
|
2076
|
+
let stat;
|
|
2077
|
+
try { stat = await fs.readFile('/proc/' + pid + '/stat', 'utf8'); }
|
|
2078
|
+
catch (error) {
|
|
2079
|
+
if (error.code === 'ENOENT' || error.code === 'ESRCH') continue;
|
|
2080
|
+
if (!required.has(Number(pid)) && (error.code === 'EACCES' || error.code === 'EPERM')) continue;
|
|
2081
|
+
throw error;
|
|
2082
|
+
}
|
|
2083
|
+
bytes += Buffer.byteLength(JSON.stringify(stat)) + 1;
|
|
2084
|
+
if (bytes > ${PROCESS_HELPER_MAX_BUFFER_BYTES} - 8192) throw new Error('Process table exceeds its byte limit');
|
|
2085
|
+
processStats.push(stat);
|
|
2086
|
+
}
|
|
2087
|
+
return { processStats, auxv: await prefix('/proc/self/auxv', 4096), elf: await prefix('/proc/self/exe', 6) };
|
|
2088
|
+
};
|
|
2055
2089
|
Promise.all([
|
|
2056
2090
|
fs.readFile('/proc/sys/kernel/random/boot_id', 'utf8'),
|
|
2057
2091
|
fs.readlink('/proc/self/ns/pid'),
|
|
@@ -2059,12 +2093,12 @@ const LINUX_PROCESS_QUERY = `
|
|
|
2059
2093
|
fs.readlink('/proc/' + observerPid + '/ns/pid'),
|
|
2060
2094
|
timeNamespace(observerPid),
|
|
2061
2095
|
fs.readFile('/proc/self/stat', 'utf8'),
|
|
2062
|
-
|
|
2096
|
+
targets(),
|
|
2063
2097
|
]).then(([bootId, pidNamespace, timeNamespace, observerPidNamespace,
|
|
2064
|
-
observerTimeNamespace, selfStat,
|
|
2098
|
+
observerTimeNamespace, selfStat, target]) => {
|
|
2065
2099
|
process.stdout.write(JSON.stringify({bootId: bootId.trim(), pidNamespace,
|
|
2066
2100
|
timeNamespace, observerPidNamespace, observerTimeNamespace,
|
|
2067
|
-
helperPid: process.pid, selfStat,
|
|
2101
|
+
helperPid: process.pid, selfStat, ...target}));
|
|
2068
2102
|
}).catch(() => { process.exitCode = 1; });
|
|
2069
2103
|
`;
|
|
2070
2104
|
const POSIX_MONTHS = [
|
|
@@ -2146,31 +2180,48 @@ function parseLinuxStat(raw) {
|
|
|
2146
2180
|
const pid = Number(prefix[1]);
|
|
2147
2181
|
const fields = raw.slice(close + 2).trim().split(/\s+/u);
|
|
2148
2182
|
const startTicks = fields[19];
|
|
2149
|
-
if (!isPositivePid(pid) || !isStartTicks(startTicks) || !/^[RSDTtKWPI]$/.test(fields[0]
|
|
2150
|
-
|
|
2183
|
+
if (!isPositivePid(pid) || !isStartTicks(startTicks) || !/^[RSDTtKWPI]$/.test(fields[0])) return;
|
|
2184
|
+
const parentPid = Number(fields[1]);
|
|
2185
|
+
const groupPid = Number(fields[2]);
|
|
2186
|
+
return validProcessLinks(parentPid, groupPid) ? {
|
|
2151
2187
|
pid,
|
|
2188
|
+
parentPid,
|
|
2189
|
+
groupPid,
|
|
2152
2190
|
startTicks
|
|
2153
|
-
};
|
|
2191
|
+
} : void 0;
|
|
2154
2192
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2193
|
+
function validProcessLinks(parentPid, groupPid) {
|
|
2194
|
+
return Number.isSafeInteger(parentPid) && parentPid >= 0 && Number.isSafeInteger(groupPid) && groupPid >= 0;
|
|
2195
|
+
}
|
|
2196
|
+
async function readLinuxQuery(pid, timeoutMs, requiredPids = []) {
|
|
2197
|
+
const args = [
|
|
2157
2198
|
"--input-type=commonjs",
|
|
2158
2199
|
"-e",
|
|
2159
2200
|
LINUX_PROCESS_QUERY,
|
|
2160
2201
|
String(pid),
|
|
2161
2202
|
String(process.pid)
|
|
2162
|
-
]
|
|
2203
|
+
];
|
|
2204
|
+
if (pid === "all") args.push(JSON.stringify(requiredPids));
|
|
2205
|
+
const output = await runTimedExecFile(process.execPath, args, {
|
|
2163
2206
|
timeoutMs,
|
|
2164
|
-
maxBufferBytes: 8192,
|
|
2207
|
+
maxBufferBytes: pid === "all" ? PROCESS_HELPER_MAX_BUFFER_BYTES : 8192,
|
|
2165
2208
|
env: {
|
|
2166
2209
|
...process.env,
|
|
2167
2210
|
NODE_OPTIONS: ""
|
|
2168
2211
|
}
|
|
2169
2212
|
});
|
|
2170
|
-
|
|
2213
|
+
return JSON.parse(output);
|
|
2214
|
+
}
|
|
2215
|
+
function verifiedLinuxScope(raw) {
|
|
2171
2216
|
const scope = parseLinuxScope(raw);
|
|
2172
2217
|
const self = parseLinuxStat(raw.selfStat);
|
|
2173
2218
|
if (!scope || !self || self.pid !== raw.helperPid || scope.pidNamespace !== raw.observerPidNamespace || scope.timeNamespace !== raw.observerTimeNamespace) return;
|
|
2219
|
+
return scope;
|
|
2220
|
+
}
|
|
2221
|
+
async function readLinuxObservation(pid, timeoutMs) {
|
|
2222
|
+
const raw = await readLinuxQuery(pid, timeoutMs);
|
|
2223
|
+
const scope = verifiedLinuxScope(raw);
|
|
2224
|
+
if (!scope) return;
|
|
2174
2225
|
const target = parseLinuxStat(raw.targetStat);
|
|
2175
2226
|
return {
|
|
2176
2227
|
scope,
|
|
@@ -2181,6 +2232,80 @@ async function readLinuxObservation(pid, timeoutMs) {
|
|
|
2181
2232
|
} } : {}
|
|
2182
2233
|
};
|
|
2183
2234
|
}
|
|
2235
|
+
function linuxAuxv(raw) {
|
|
2236
|
+
if (typeof raw.elf !== "string" || typeof raw.auxv !== "string") throw new Error("Linux process clock is unavailable");
|
|
2237
|
+
const elf = Buffer.from(raw.elf, "base64");
|
|
2238
|
+
const auxv = Buffer.from(raw.auxv, "base64");
|
|
2239
|
+
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");
|
|
2240
|
+
return {
|
|
2241
|
+
auxv,
|
|
2242
|
+
size: elf[4] === 1 ? 4 : 8,
|
|
2243
|
+
littleEndian: elf[5] === 1
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
function linuxClockTicks(raw) {
|
|
2247
|
+
const { auxv, size, littleEndian } = linuxAuxv(raw);
|
|
2248
|
+
const word = (offset) => readAuxvWord(auxv, offset, size, littleEndian);
|
|
2249
|
+
for (let offset = 0; offset + size * 2 <= auxv.length; offset += size * 2) {
|
|
2250
|
+
const key = word(offset);
|
|
2251
|
+
if (key === 0n) break;
|
|
2252
|
+
if (key === 17n && word(offset + size) > 0n) return word(offset + size);
|
|
2253
|
+
}
|
|
2254
|
+
throw new Error("Linux process clock has no AT_CLKTCK");
|
|
2255
|
+
}
|
|
2256
|
+
function readAuxvWord(buffer, offset, size, littleEndian) {
|
|
2257
|
+
if (size === 8) return littleEndian ? buffer.readBigUInt64LE(offset) : buffer.readBigUInt64BE(offset);
|
|
2258
|
+
return BigInt(littleEndian ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset));
|
|
2259
|
+
}
|
|
2260
|
+
async function readLinuxProcessTable(timeoutMs, requiredPids, pid) {
|
|
2261
|
+
const raw = await readLinuxQuery("all", timeoutMs, requiredPids);
|
|
2262
|
+
const scope = verifiedLinuxScope(raw);
|
|
2263
|
+
if (!scope || !Array.isArray(raw.processStats)) throw new Error("Linux process table scope is unverified");
|
|
2264
|
+
const clockTicksPerSecond = linuxClockTicks(raw);
|
|
2265
|
+
const table = /* @__PURE__ */ new Map();
|
|
2266
|
+
for (const value of raw.processStats) {
|
|
2267
|
+
const stat = parseLinuxStat(value);
|
|
2268
|
+
if (stat && (pid === void 0 || stat.pid === pid)) table.set(stat.pid, {
|
|
2269
|
+
pid: stat.pid,
|
|
2270
|
+
parentPid: stat.parentPid,
|
|
2271
|
+
groupPid: stat.groupPid,
|
|
2272
|
+
birth: {
|
|
2273
|
+
kind: "linux-proc",
|
|
2274
|
+
...scope,
|
|
2275
|
+
startTicks: stat.startTicks
|
|
2276
|
+
},
|
|
2277
|
+
clockTicksPerSecond
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
return table;
|
|
2281
|
+
}
|
|
2282
|
+
function readLinuxExitClock() {
|
|
2283
|
+
let descriptor;
|
|
2284
|
+
try {
|
|
2285
|
+
descriptor = fs.openSync("/proc/uptime", "r");
|
|
2286
|
+
const buffer = Buffer.alloc(64);
|
|
2287
|
+
const bytes = fs.readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
2288
|
+
const match = /^(\d+)\.(\d{2})\s/u.exec(buffer.toString("ascii", 0, bytes));
|
|
2289
|
+
return match ? {
|
|
2290
|
+
centiseconds: BigInt(match[1]) * 100n + BigInt(match[2]),
|
|
2291
|
+
timeNamespace: readOwnTimeNamespace()
|
|
2292
|
+
} : void 0;
|
|
2293
|
+
} catch {
|
|
2294
|
+
return;
|
|
2295
|
+
} finally {
|
|
2296
|
+
if (descriptor !== void 0) try {
|
|
2297
|
+
fs.closeSync(descriptor);
|
|
2298
|
+
} catch {}
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
function readOwnTimeNamespace() {
|
|
2302
|
+
try {
|
|
2303
|
+
return fs.readlinkSync("/proc/self/ns/time");
|
|
2304
|
+
} catch (error) {
|
|
2305
|
+
if (error.code === "ENOENT") return "unsupported";
|
|
2306
|
+
throw error;
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2184
2309
|
function isPositivePid(pid) {
|
|
2185
2310
|
return Number.isSafeInteger(pid) && pid > 0;
|
|
2186
2311
|
}
|
|
@@ -2218,7 +2343,7 @@ function parseProcessTable(output) {
|
|
|
2218
2343
|
pid,
|
|
2219
2344
|
parentPid: Number(match[2]),
|
|
2220
2345
|
groupPid: Number(match[3]),
|
|
2221
|
-
birth
|
|
2346
|
+
birth: processBirthIdentity(birth)
|
|
2222
2347
|
});
|
|
2223
2348
|
}
|
|
2224
2349
|
return table;
|
|
@@ -2227,7 +2352,7 @@ function processTableBirth(value) {
|
|
|
2227
2352
|
if (process.platform !== "win32") return posixBirth(value);
|
|
2228
2353
|
return isCanonicalBirth(value, "windows-creation") ? value : void 0;
|
|
2229
2354
|
}
|
|
2230
|
-
async function readProcessTable(timeoutMs, pid) {
|
|
2355
|
+
async function readProcessTable(timeoutMs, pid, requiredPids = []) {
|
|
2231
2356
|
if (pid !== void 0 && (!Number.isSafeInteger(pid) || pid <= 0)) throw new Error("Process identity requires a positive PID");
|
|
2232
2357
|
if (process.platform === "win32") return parseProcessTable(await runTimedExecFile(windowsPowerShellPath(), [
|
|
2233
2358
|
"-NoLogo",
|
|
@@ -2239,6 +2364,7 @@ async function readProcessTable(timeoutMs, pid) {
|
|
|
2239
2364
|
timeoutMs,
|
|
2240
2365
|
windowsHide: true
|
|
2241
2366
|
}));
|
|
2367
|
+
if (process.platform === "linux") return await readLinuxProcessTable(timeoutMs, requiredPids, pid);
|
|
2242
2368
|
return parseProcessTable(await runTimedExecFile("ps", [
|
|
2243
2369
|
...pid === void 0 ? ["-e"] : ["-p", String(pid)],
|
|
2244
2370
|
"-o",
|
|
@@ -2266,8 +2392,7 @@ async function probeProcessIdentity(pid, timeoutMs = IDENTITY_QUERY_TIMEOUT_MS$1
|
|
|
2266
2392
|
}
|
|
2267
2393
|
async function readNativeIdentity(pid, timeoutMs) {
|
|
2268
2394
|
if (process.platform === "linux") return (await readLinuxObservation(pid, timeoutMs))?.identity;
|
|
2269
|
-
|
|
2270
|
-
return entry ? processBirthIdentity(entry.birth) : void 0;
|
|
2395
|
+
return (await readProcessTable(timeoutMs, pid)).get(pid)?.birth;
|
|
2271
2396
|
}
|
|
2272
2397
|
function processBirthIdentity(value) {
|
|
2273
2398
|
return {
|
|
@@ -2314,6 +2439,138 @@ async function getOwnProcessIdentity() {
|
|
|
2314
2439
|
return await ownIdentityPending;
|
|
2315
2440
|
}
|
|
2316
2441
|
//#endregion
|
|
2442
|
+
//#region src/acp/process-descendants.ts
|
|
2443
|
+
function hasStaleWindowsParent(identity, parent) {
|
|
2444
|
+
return identity.birth.kind === "windows-creation" && (parent?.birth.kind !== "windows-creation" || parent.birth.value > identity.birth.value);
|
|
2445
|
+
}
|
|
2446
|
+
function includeProcessDescendants(table, owned) {
|
|
2447
|
+
let expanded;
|
|
2448
|
+
do {
|
|
2449
|
+
expanded = false;
|
|
2450
|
+
for (const identity of table.values()) {
|
|
2451
|
+
if (hasStaleWindowsParent(identity, table.get(identity.parentPid))) continue;
|
|
2452
|
+
if (owned.has(identity.parentPid) && !owned.has(identity.pid)) {
|
|
2453
|
+
owned.add(identity.pid);
|
|
2454
|
+
expanded = true;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
} while (expanded);
|
|
2458
|
+
}
|
|
2459
|
+
/** Best-effort cleanup for descendants witnessed during this child launch. */
|
|
2460
|
+
var ProcessDescendants = class {
|
|
2461
|
+
child;
|
|
2462
|
+
identities = /* @__PURE__ */ new Map();
|
|
2463
|
+
rootBirth;
|
|
2464
|
+
pending;
|
|
2465
|
+
retired = false;
|
|
2466
|
+
ownProcessGroup;
|
|
2467
|
+
captureGroupAfterExit;
|
|
2468
|
+
groupExitedAt;
|
|
2469
|
+
onRootExit = () => {
|
|
2470
|
+
this.groupExitedAt = process.platform === "linux" ? readLinuxExitClock() ?? null : Date.now();
|
|
2471
|
+
};
|
|
2472
|
+
constructor(child, options = {}) {
|
|
2473
|
+
this.child = child;
|
|
2474
|
+
this.ownProcessGroup = process.platform !== "win32" && Boolean(options.ownProcessGroup);
|
|
2475
|
+
this.captureGroupAfterExit = this.ownProcessGroup && isChildProcessRunning(child);
|
|
2476
|
+
if (this.ownProcessGroup) child.once("exit", this.onRootExit);
|
|
2477
|
+
}
|
|
2478
|
+
capture(timeoutMs = PROCESS_HELPER_TIMEOUT_MS) {
|
|
2479
|
+
if (this.retired) return Promise.resolve(true);
|
|
2480
|
+
this.pending ??= this.readSnapshot(timeoutMs).finally(() => {
|
|
2481
|
+
this.pending = void 0;
|
|
2482
|
+
});
|
|
2483
|
+
return this.pending;
|
|
2484
|
+
}
|
|
2485
|
+
async readSnapshot(timeoutMs) {
|
|
2486
|
+
const deadline = performance.now() + timeoutMs;
|
|
2487
|
+
const rootWasRunning = isChildProcessRunning(this.child);
|
|
2488
|
+
const captureRootGroup = rootWasRunning || this.captureGroupAfterExit;
|
|
2489
|
+
if (!rootWasRunning) this.captureGroupAfterExit = false;
|
|
2490
|
+
try {
|
|
2491
|
+
const table = await readProcessTable(timeoutMs, void 0, this.requiredSnapshotPids(rootWasRunning));
|
|
2492
|
+
if (!this.retired) {
|
|
2493
|
+
table.delete(1);
|
|
2494
|
+
table.delete(process.pid);
|
|
2495
|
+
this.refresh(table, captureRootGroup);
|
|
2496
|
+
if (this.captureGroupAfterExit && !isChildProcessRunning(this.child)) return await this.readSnapshot(Math.max(1, deadline - performance.now()));
|
|
2497
|
+
}
|
|
2498
|
+
return true;
|
|
2499
|
+
} catch {
|
|
2500
|
+
if (!isChildProcessRunning(this.child)) this.captureGroupAfterExit = false;
|
|
2501
|
+
return false;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
requiredSnapshotPids(rootWasRunning) {
|
|
2505
|
+
return [...this.identities.keys(), ...rootWasRunning && this.child.pid ? [this.child.pid] : []];
|
|
2506
|
+
}
|
|
2507
|
+
refresh(table, rootWasRunning) {
|
|
2508
|
+
this.verifyScope(table);
|
|
2509
|
+
const owned = /* @__PURE__ */ new Set();
|
|
2510
|
+
for (const [pid, identity] of this.identities) {
|
|
2511
|
+
const observed = table.get(pid);
|
|
2512
|
+
if (observed && compareProcessBirthIdentity(identity.birth, observed.birth) === "matching") owned.add(pid);
|
|
2513
|
+
}
|
|
2514
|
+
this.includeRoot(table, owned);
|
|
2515
|
+
if (this.ownProcessGroup) this.includeProcessGroup(table, owned, rootWasRunning);
|
|
2516
|
+
includeProcessDescendants(table, owned);
|
|
2517
|
+
owned.delete(this.child.pid ?? 0);
|
|
2518
|
+
this.identities = new Map([...table].filter(([pid]) => owned.has(pid)));
|
|
2519
|
+
}
|
|
2520
|
+
verifyScope(table) {
|
|
2521
|
+
const expected = this.rootBirth ?? this.identities.values().next().value?.birth;
|
|
2522
|
+
const observation = table.values().next().value?.birth;
|
|
2523
|
+
if (expected && observation && compareProcessBirthIdentity(expected, observation) === "unknown") throw new Error("Descendant process scope changed");
|
|
2524
|
+
}
|
|
2525
|
+
includeRoot(table, owned) {
|
|
2526
|
+
const root = this.child.pid;
|
|
2527
|
+
const rootIdentity = root && table.get(root);
|
|
2528
|
+
if (rootIdentity && isChildProcessRunning(this.child)) {
|
|
2529
|
+
this.rootBirth ??= rootIdentity.birth;
|
|
2530
|
+
if (compareProcessBirthIdentity(this.rootBirth, rootIdentity.birth) === "matching") owned.add(rootIdentity.pid);
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
includeProcessGroup(table, owned, rootWasRunning) {
|
|
2534
|
+
const root = this.child.pid;
|
|
2535
|
+
if (root && (rootWasRunning || [...owned].some((pid) => table.get(pid)?.groupPid === root))) {
|
|
2536
|
+
for (const identity of table.values()) if (identity.groupPid === root && this.startedBeforeRootExit(identity)) owned.add(identity.pid);
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
startedBeforeRootExit(identity) {
|
|
2540
|
+
const cutoff = this.groupExitedAt;
|
|
2541
|
+
if (cutoff === void 0) return true;
|
|
2542
|
+
if (cutoff === null) return false;
|
|
2543
|
+
if (identity.birth.kind !== "linux-proc") return typeof cutoff === "number" && Date.parse(identity.birth.value) <= cutoff;
|
|
2544
|
+
if (typeof cutoff === "number" || identity.birth.timeNamespace !== cutoff.timeNamespace || !identity.clockTicksPerSecond) return false;
|
|
2545
|
+
return BigInt(identity.birth.startTicks) * 100n < (cutoff.centiseconds + 1n) * identity.clockTicksPerSecond;
|
|
2546
|
+
}
|
|
2547
|
+
async signal(signal, timeoutMs) {
|
|
2548
|
+
if (!await this.capture(timeoutMs) || this.retired) return;
|
|
2549
|
+
for (const pid of this.identities.keys()) try {
|
|
2550
|
+
process.kill(pid, signal);
|
|
2551
|
+
} catch {
|
|
2552
|
+
this.identities.delete(pid);
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
hasTrackedProcesses() {
|
|
2556
|
+
return this.identities.size > 0;
|
|
2557
|
+
}
|
|
2558
|
+
async waitForExit(timeoutMs) {
|
|
2559
|
+
const deadline = performance.now() + timeoutMs;
|
|
2560
|
+
do {
|
|
2561
|
+
if (!await this.capture(Math.max(1, deadline - performance.now()))) return false;
|
|
2562
|
+
if (this.identities.size === 0) return true;
|
|
2563
|
+
await setTimeout$1(Math.min(100, Math.max(0, deadline - performance.now())));
|
|
2564
|
+
} while (performance.now() < deadline);
|
|
2565
|
+
return false;
|
|
2566
|
+
}
|
|
2567
|
+
retire() {
|
|
2568
|
+
this.retired = true;
|
|
2569
|
+
this.child.off("exit", this.onRootExit);
|
|
2570
|
+
this.identities.clear();
|
|
2571
|
+
}
|
|
2572
|
+
};
|
|
2573
|
+
//#endregion
|
|
2317
2574
|
//#region src/session/lock-owner.ts
|
|
2318
2575
|
const OBSERVATION_REUSE_MS = 1e3;
|
|
2319
2576
|
const IDENTITY_QUERY_TIMEOUT_MS = 1e3;
|
|
@@ -2377,6 +2634,127 @@ function queueSocketPath(sessionId, homeDir = os.homedir()) {
|
|
|
2377
2634
|
return path.join(queueSocketBaseDir(homeDir) ?? "/tmp", `${key}.sock`);
|
|
2378
2635
|
}
|
|
2379
2636
|
//#endregion
|
|
2637
|
+
//#region src/session/queue/retirement-receipt.ts
|
|
2638
|
+
const MAX_RETIREMENT_MEMBERS = 4096;
|
|
2639
|
+
const MAX_RETIREMENT_BYTES = 1048576;
|
|
2640
|
+
function hasQueueRetirementReceipt(value) {
|
|
2641
|
+
return value !== null && typeof value === "object" && Object.hasOwn(value, "retirement");
|
|
2642
|
+
}
|
|
2643
|
+
function parseWitness(raw) {
|
|
2644
|
+
if (!raw || typeof raw !== "object") return;
|
|
2645
|
+
const value = raw;
|
|
2646
|
+
const processIdentity = parseProcessBirthIdentity(value.processIdentity);
|
|
2647
|
+
if (typeof value.pid !== "number" || !Number.isSafeInteger(value.pid) || value.pid <= 1 || processIdentity?.kind !== "windows-creation") return;
|
|
2648
|
+
return {
|
|
2649
|
+
pid: value.pid,
|
|
2650
|
+
processIdentity
|
|
2651
|
+
};
|
|
2652
|
+
}
|
|
2653
|
+
function parseQueueRetirementReceipt(raw, owner) {
|
|
2654
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
2655
|
+
const value = raw;
|
|
2656
|
+
const root = parseReceiptRoot(value, owner);
|
|
2657
|
+
if (!root) return null;
|
|
2658
|
+
const descendants = parseDescendants(value.descendants, root.pid);
|
|
2659
|
+
if (!descendants) return null;
|
|
2660
|
+
try {
|
|
2661
|
+
if (Buffer.byteLength(JSON.stringify(raw)) > MAX_RETIREMENT_BYTES) return null;
|
|
2662
|
+
} catch {
|
|
2663
|
+
return null;
|
|
2664
|
+
}
|
|
2665
|
+
return {
|
|
2666
|
+
ownerGeneration: owner.ownerGeneration,
|
|
2667
|
+
root,
|
|
2668
|
+
descendants
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
function parseReceiptRoot(value, owner) {
|
|
2672
|
+
const root = parseWitness(value.root);
|
|
2673
|
+
if (!root || root.pid !== owner.pid || value.ownerGeneration !== owner.ownerGeneration || compareProcessBirthIdentity(owner.processIdentity, root.processIdentity) !== "matching") return;
|
|
2674
|
+
return root;
|
|
2675
|
+
}
|
|
2676
|
+
function parseDescendants(raw, rootPid) {
|
|
2677
|
+
if (!Array.isArray(raw) || raw.length >= MAX_RETIREMENT_MEMBERS) return;
|
|
2678
|
+
const pids = /* @__PURE__ */ new Set([rootPid]);
|
|
2679
|
+
const witnesses = [];
|
|
2680
|
+
for (const value of raw) {
|
|
2681
|
+
const witness = parseWitness(value);
|
|
2682
|
+
if (!witness || pids.has(witness.pid)) return;
|
|
2683
|
+
pids.add(witness.pid);
|
|
2684
|
+
witnesses.push(witness);
|
|
2685
|
+
}
|
|
2686
|
+
return witnesses;
|
|
2687
|
+
}
|
|
2688
|
+
function queueRetirementIncomplete() {
|
|
2689
|
+
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.", {
|
|
2690
|
+
detailCode: "QUEUE_OWNER_RETIREMENT_INCOMPLETE",
|
|
2691
|
+
origin: "queue",
|
|
2692
|
+
retryable: true
|
|
2693
|
+
});
|
|
2694
|
+
}
|
|
2695
|
+
function retirementTimeRemaining(deadline, maximumMs = 2e3) {
|
|
2696
|
+
const remaining = deadline - performance.now();
|
|
2697
|
+
if (remaining <= 0) throw queueRetirementIncomplete();
|
|
2698
|
+
return Math.min(maximumMs, remaining);
|
|
2699
|
+
}
|
|
2700
|
+
async function captureQueueRetirementReceipt(owner, previous, rootRunning, deadline) {
|
|
2701
|
+
const root = parseWitness({
|
|
2702
|
+
pid: owner.pid,
|
|
2703
|
+
processIdentity: owner.processIdentity
|
|
2704
|
+
});
|
|
2705
|
+
if (!root || process.platform !== "win32") throw queueRetirementIncomplete();
|
|
2706
|
+
const table = await readProcessTable(retirementTimeRemaining(deadline)).catch(() => {
|
|
2707
|
+
throw queueRetirementIncomplete();
|
|
2708
|
+
});
|
|
2709
|
+
const saved = unsettledSnapshotWitnesses(table, previous ? previous.descendants : []);
|
|
2710
|
+
const owned = matchingSnapshotPids(table, rootRunning ? [root, ...saved] : saved);
|
|
2711
|
+
if (rootRunning && !owned.has(root.pid)) throw queueRetirementIncomplete();
|
|
2712
|
+
table.delete(1);
|
|
2713
|
+
table.delete(process.pid);
|
|
2714
|
+
includeProcessDescendants(table, owned);
|
|
2715
|
+
owned.delete(root.pid);
|
|
2716
|
+
const receipt = {
|
|
2717
|
+
ownerGeneration: owner.ownerGeneration,
|
|
2718
|
+
root,
|
|
2719
|
+
descendants: mergeSnapshotDescendants(table, owned, saved)
|
|
2720
|
+
};
|
|
2721
|
+
if (!parseQueueRetirementReceipt(receipt, owner)) throw queueRetirementIncomplete();
|
|
2722
|
+
return receipt;
|
|
2723
|
+
}
|
|
2724
|
+
function unsettledSnapshotWitnesses(table, witnesses) {
|
|
2725
|
+
return witnesses.filter((witness) => {
|
|
2726
|
+
const observed = table.get(witness.pid);
|
|
2727
|
+
return !observed || compareProcessBirthIdentity(witness.processIdentity, observed.birth) !== "different";
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
function matchingSnapshotPids(table, witnesses) {
|
|
2731
|
+
const owned = /* @__PURE__ */ new Set();
|
|
2732
|
+
for (const witness of witnesses) {
|
|
2733
|
+
const observed = table.get(witness.pid);
|
|
2734
|
+
if (observed && compareProcessBirthIdentity(witness.processIdentity, observed.birth) === "matching") owned.add(witness.pid);
|
|
2735
|
+
}
|
|
2736
|
+
return owned;
|
|
2737
|
+
}
|
|
2738
|
+
function mergeSnapshotDescendants(table, owned, saved) {
|
|
2739
|
+
const descendants = new Map(saved.map((witness) => [witness.pid, witness]));
|
|
2740
|
+
for (const pid of owned) {
|
|
2741
|
+
const observed = table.get(pid);
|
|
2742
|
+
if (observed) descendants.set(pid, {
|
|
2743
|
+
pid,
|
|
2744
|
+
processIdentity: observed.birth
|
|
2745
|
+
});
|
|
2746
|
+
}
|
|
2747
|
+
return [...descendants.values()];
|
|
2748
|
+
}
|
|
2749
|
+
async function observeRetirementWitness(witness, deadline) {
|
|
2750
|
+
if (witness.pid === process.pid) throw queueRetirementIncomplete();
|
|
2751
|
+
return await observeProcessIncarnation(witness.pid, witness.processIdentity, retirementTimeRemaining(deadline));
|
|
2752
|
+
}
|
|
2753
|
+
async function assertQueueRetirementComplete(receipt, deadline) {
|
|
2754
|
+
if (receipt === null) throw queueRetirementIncomplete();
|
|
2755
|
+
for (const witness of receipt?.descendants ?? []) if (await observeRetirementWitness(witness, deadline) !== "gone") throw queueRetirementIncomplete();
|
|
2756
|
+
}
|
|
2757
|
+
//#endregion
|
|
2380
2758
|
//#region src/session/queue/lease-mutation.ts
|
|
2381
2759
|
const GUARD_WAIT_MS = 2e3;
|
|
2382
2760
|
const localMutations = /* @__PURE__ */ new Map();
|
|
@@ -2432,7 +2810,7 @@ async function rollbackReservation(lockPath, expected) {
|
|
|
2432
2810
|
} catch {
|
|
2433
2811
|
return;
|
|
2434
2812
|
}
|
|
2435
|
-
if (matchesReservation(value, expected)) await fs$1.unlink(lockPath);
|
|
2813
|
+
if (matchesReservation(value, expected) && !hasQueueRetirementReceipt(value)) await fs$1.unlink(lockPath);
|
|
2436
2814
|
} catch (error) {
|
|
2437
2815
|
if (error.code !== "ENOENT") throw error;
|
|
2438
2816
|
}
|
|
@@ -2537,7 +2915,7 @@ function parseQueueOwnerRecord(raw) {
|
|
|
2537
2915
|
const record = raw;
|
|
2538
2916
|
if (!hasValidQueueOwnerRecordFields(record)) return null;
|
|
2539
2917
|
const processIdentity = parseProcessBirthIdentity(record.processIdentity);
|
|
2540
|
-
|
|
2918
|
+
const owner = {
|
|
2541
2919
|
pid: record.pid,
|
|
2542
2920
|
sessionId: record.sessionId,
|
|
2543
2921
|
socketPath: record.socketPath,
|
|
@@ -2550,6 +2928,8 @@ function parseQueueOwnerRecord(raw) {
|
|
|
2550
2928
|
...typeof record.mcpConfigPath === "string" ? { mcpConfigPath: record.mcpConfigPath } : {},
|
|
2551
2929
|
...typeof record.mcpConfigFingerprint === "string" ? { mcpConfigFingerprint: record.mcpConfigFingerprint } : {}
|
|
2552
2930
|
};
|
|
2931
|
+
if (hasQueueRetirementReceipt(record)) owner.retirement = parseQueueRetirementReceipt(record.retirement, owner);
|
|
2932
|
+
return owner;
|
|
2553
2933
|
}
|
|
2554
2934
|
function parseQueueOwnerCapabilities(record) {
|
|
2555
2935
|
return {
|
|
@@ -2611,20 +2991,21 @@ async function removeSocketFile(socketPath) {
|
|
|
2611
2991
|
}
|
|
2612
2992
|
}
|
|
2613
2993
|
async function waitForProcessExit(pid, timeoutMs, hasExited) {
|
|
2614
|
-
const deadline =
|
|
2615
|
-
while (
|
|
2994
|
+
const deadline = performance.now() + Math.max(0, timeoutMs);
|
|
2995
|
+
while (performance.now() <= deadline) {
|
|
2616
2996
|
if (hasExited(pid)) return true;
|
|
2617
2997
|
await waitMs(PROCESS_POLL_MS);
|
|
2618
2998
|
}
|
|
2619
2999
|
return hasExited(pid);
|
|
2620
3000
|
}
|
|
2621
|
-
async function cleanupQueueOwnerFiles(sessionId, socketPath, isCurrent, capturedIdentity) {
|
|
3001
|
+
async function cleanupQueueOwnerFiles(sessionId, socketPath, isCurrent, capturedIdentity, deadline = retirementDeadline()) {
|
|
2622
3002
|
await withQueueLeaseMutation(sessionId, async () => {
|
|
2623
|
-
await cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent);
|
|
3003
|
+
await cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent, deadline);
|
|
2624
3004
|
}, { capturedIdentity });
|
|
2625
3005
|
}
|
|
2626
|
-
async function cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent) {
|
|
3006
|
+
async function cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent, deadline = retirementDeadline()) {
|
|
2627
3007
|
if (!await isCurrent()) return;
|
|
3008
|
+
await assertQueueRetirementComplete((await readQueueOwnerRecord(sessionId))?.retirement, deadline);
|
|
2628
3009
|
await removeSocketFile(socketPath).catch(() => {});
|
|
2629
3010
|
if (!await isCurrent()) return;
|
|
2630
3011
|
await fs$1.unlink(queueLockFilePath(sessionId)).catch((error) => {
|
|
@@ -2640,6 +3021,9 @@ function matchesQueueOwner(current, expected) {
|
|
|
2640
3021
|
function ownerIsAlive(owner) {
|
|
2641
3022
|
return owner.pid === process.pid || isProcessAlive(owner.pid);
|
|
2642
3023
|
}
|
|
3024
|
+
function ownerAcceptsWork(owner) {
|
|
3025
|
+
return owner.retirement === void 0 && ownerIsAlive(owner) && !isQueueOwnerHeartbeatStale(owner);
|
|
3026
|
+
}
|
|
2643
3027
|
async function readQueueOwnerRecord(sessionId) {
|
|
2644
3028
|
const lockPath = queueLockFilePath(sessionId);
|
|
2645
3029
|
try {
|
|
@@ -2665,7 +3049,7 @@ function dispatchSignal(pid, signal) {
|
|
|
2665
3049
|
return false;
|
|
2666
3050
|
}
|
|
2667
3051
|
}
|
|
2668
|
-
async function dispatchQueueOwnerSignal(pid, signal) {
|
|
3052
|
+
async function dispatchQueueOwnerSignal(pid, signal, deadline) {
|
|
2669
3053
|
if (process.platform !== "win32") return dispatchSignal(pid, signal);
|
|
2670
3054
|
const systemRoot = process.env.SystemRoot;
|
|
2671
3055
|
if (!systemRoot || !path.win32.isAbsolute(systemRoot)) throw new Error("Windows queue-owner cleanup requires an absolute SystemRoot directory");
|
|
@@ -2676,15 +3060,15 @@ async function dispatchQueueOwnerSignal(pid, signal) {
|
|
|
2676
3060
|
"/T",
|
|
2677
3061
|
"/F"
|
|
2678
3062
|
], {
|
|
2679
|
-
timeoutMs: PROCESS_SIGTERM_GRACE_MS,
|
|
3063
|
+
timeoutMs: retirementTimeRemaining(deadline, PROCESS_SIGTERM_GRACE_MS),
|
|
2680
3064
|
windowsHide: true
|
|
2681
3065
|
});
|
|
2682
3066
|
return true;
|
|
2683
3067
|
}
|
|
2684
|
-
async function terminateWithDispatch(pid, dispatch, hasExited) {
|
|
3068
|
+
async function terminateWithDispatch(pid, dispatch, hasExited, deadline = Infinity) {
|
|
2685
3069
|
for (const [signal, graceMs] of [["SIGTERM", PROCESS_SIGTERM_GRACE_MS], ["SIGKILL", PROCESS_SIGKILL_GRACE_MS]]) {
|
|
2686
3070
|
if (!await dispatch(signal)) return false;
|
|
2687
|
-
if (await waitForProcessExit(pid, graceMs, hasExited)) return true;
|
|
3071
|
+
if (await waitForProcessExit(pid, Math.min(graceMs, deadline - performance.now()), hasExited)) return true;
|
|
2688
3072
|
}
|
|
2689
3073
|
return false;
|
|
2690
3074
|
}
|
|
@@ -2692,10 +3076,10 @@ async function resolveUsableQueueOwner(sessionId, owner) {
|
|
|
2692
3076
|
await settlePendingQueueLeaseGuard(sessionId);
|
|
2693
3077
|
const observed = await readQueueOwnerRecord(sessionId);
|
|
2694
3078
|
if (!matchesQueueOwner(observed, owner)) return;
|
|
2695
|
-
if (
|
|
3079
|
+
if (ownerAcceptsWork(observed)) return observed;
|
|
2696
3080
|
await terminateQueueOwnerForSession(sessionId, observed, true);
|
|
2697
3081
|
const current = await readQueueOwnerRecord(sessionId);
|
|
2698
|
-
return matchesQueueOwner(current, observed) &&
|
|
3082
|
+
return matchesQueueOwner(current, observed) && ownerAcceptsWork(current) ? current : void 0;
|
|
2699
3083
|
}
|
|
2700
3084
|
async function tryAcquireQueueOwnerLease(sessionId, mcpConfigOrNowIsoFactory, nowIsoFactory = nowIso) {
|
|
2701
3085
|
const { mcpConfigPath, clock } = resolveLeaseArguments(mcpConfigOrNowIsoFactory, nowIsoFactory);
|
|
@@ -2806,9 +3190,13 @@ function refreshQueueOwnerLease(lease, options, nowIsoFactory = nowIso) {
|
|
|
2806
3190
|
if (lease.released) return Promise.resolve();
|
|
2807
3191
|
const update = lease.updates.then(async () => {
|
|
2808
3192
|
await withQueueLeaseMutation(lease.sessionId, async () => {
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
3193
|
+
const current = await readQueueOwnerRecord(lease.sessionId);
|
|
3194
|
+
if (!matchesQueueOwner(current, lease)) return;
|
|
3195
|
+
if (current.retirement === null) throw queueRetirementIncomplete();
|
|
3196
|
+
await replaceQueueOwnerRecord({
|
|
3197
|
+
...current,
|
|
3198
|
+
heartbeatAt: nowIsoFactory(),
|
|
3199
|
+
queueDepth: Math.max(0, Math.round(options.queueDepth))
|
|
2812
3200
|
});
|
|
2813
3201
|
}, { capturedIdentity: lease });
|
|
2814
3202
|
});
|
|
@@ -2816,8 +3204,8 @@ function refreshQueueOwnerLease(lease, options, nowIsoFactory = nowIso) {
|
|
|
2816
3204
|
return update;
|
|
2817
3205
|
}
|
|
2818
3206
|
async function stageQueueOwnerRecord(lease, queueDepth, clock, publish) {
|
|
2819
|
-
|
|
2820
|
-
pid:
|
|
3207
|
+
await stagePreparedQueueOwnerRecord({
|
|
3208
|
+
pid: lease.pid,
|
|
2821
3209
|
sessionId: lease.sessionId,
|
|
2822
3210
|
socketPath: lease.socketPath,
|
|
2823
3211
|
createdAt: lease.createdAt,
|
|
@@ -2830,9 +3218,17 @@ async function stageQueueOwnerRecord(lease, queueDepth, clock, publish) {
|
|
|
2830
3218
|
persistsControlState: true,
|
|
2831
3219
|
...lease.mcpConfigPath ? { mcpConfigPath: lease.mcpConfigPath } : {},
|
|
2832
3220
|
...lease.mcpConfigFingerprint ? { mcpConfigFingerprint: lease.mcpConfigFingerprint } : {}
|
|
2833
|
-
},
|
|
3221
|
+
}, publish);
|
|
3222
|
+
}
|
|
3223
|
+
async function replaceQueueOwnerRecord(owner) {
|
|
3224
|
+
await stagePreparedQueueOwnerRecord(owner, async (tempPath) => {
|
|
3225
|
+
await fs$1.rename(tempPath, queueLockFilePath(owner.sessionId));
|
|
3226
|
+
});
|
|
3227
|
+
}
|
|
3228
|
+
async function stagePreparedQueueOwnerRecord(owner, publish) {
|
|
3229
|
+
const payload = JSON.stringify(owner, null, 2);
|
|
2834
3230
|
await withTempFile({
|
|
2835
|
-
rootDir: path.dirname(
|
|
3231
|
+
rootDir: path.dirname(queueLockFilePath(owner.sessionId)),
|
|
2836
3232
|
prefix: "owner",
|
|
2837
3233
|
fileName: "lease"
|
|
2838
3234
|
}, async (tempPath) => {
|
|
@@ -2856,40 +3252,120 @@ function unverifiedQueueOwnerError(owner) {
|
|
|
2856
3252
|
retryable: true
|
|
2857
3253
|
});
|
|
2858
3254
|
}
|
|
3255
|
+
function retirementDeadline() {
|
|
3256
|
+
return performance.now() + PROCESS_SIGTERM_GRACE_MS + PROCESS_SIGKILL_GRACE_MS;
|
|
3257
|
+
}
|
|
3258
|
+
async function observeRetiringOwner(owner, deadline) {
|
|
3259
|
+
return await observeProcessIncarnation(owner.pid, owner.processIdentity, process.platform === "win32" ? retirementTimeRemaining(deadline) : void 0);
|
|
3260
|
+
}
|
|
3261
|
+
function retirementBlockedByHeartbeat(owner, signal, requireStale) {
|
|
3262
|
+
return requireStale && owner.retirement === void 0 && signal === "SIGTERM" && !isQueueOwnerHeartbeatStale(owner);
|
|
3263
|
+
}
|
|
3264
|
+
async function prepareWindowsRetirement(owner, deadline) {
|
|
3265
|
+
if (owner.retirement === null) throw queueRetirementIncomplete();
|
|
3266
|
+
owner.retirement = await captureQueueRetirementReceipt(owner, owner.retirement, true, deadline);
|
|
3267
|
+
await replaceQueueOwnerRecord(owner);
|
|
3268
|
+
return await observeRetirementWitness(owner.retirement.root, deadline) === "matching";
|
|
3269
|
+
}
|
|
2859
3270
|
async function dispatchVerifiedQueueOwnerSignal(owner, signal, requireStale, retirement) {
|
|
2860
3271
|
return await withQueueLeaseMutation(owner.sessionId, async () => {
|
|
2861
3272
|
const current = await readQueueOwnerRecord(owner.sessionId);
|
|
2862
3273
|
retirement.leaseReleased = current?.pid !== owner.pid;
|
|
2863
|
-
if (!matchesQueueOwner(current, owner) ||
|
|
2864
|
-
|
|
3274
|
+
if (!matchesQueueOwner(current, owner) || retirementBlockedByHeartbeat(current, signal, requireStale)) return false;
|
|
3275
|
+
if (current.retirement === null) throw queueRetirementIncomplete();
|
|
3276
|
+
const identity = await observeRetiringOwner(current, retirement.deadline);
|
|
2865
3277
|
if (identity !== "matching") {
|
|
2866
3278
|
retirement.gone = identity === "gone";
|
|
2867
3279
|
retirement.unverified = identity === "unknown";
|
|
2868
3280
|
return false;
|
|
2869
3281
|
}
|
|
2870
|
-
|
|
3282
|
+
if (process.platform === "win32" && !await prepareWindowsRetirement(current, retirement.deadline)) return false;
|
|
3283
|
+
return await dispatchQueueOwnerSignal(owner.pid, signal, retirement.deadline);
|
|
2871
3284
|
});
|
|
2872
3285
|
}
|
|
2873
|
-
async function retireQueueOwner(owner, requireStale) {
|
|
2874
|
-
const deadline = performance.now() + PROCESS_SIGTERM_GRACE_MS + PROCESS_SIGKILL_GRACE_MS;
|
|
3286
|
+
async function retireQueueOwner(owner, requireStale, deadline) {
|
|
2875
3287
|
const retirement = {
|
|
2876
3288
|
gone: false,
|
|
2877
3289
|
unverified: false,
|
|
2878
|
-
leaseReleased: false
|
|
3290
|
+
leaseReleased: false,
|
|
3291
|
+
deadline
|
|
2879
3292
|
};
|
|
2880
|
-
await terminateWithDispatch(owner.pid, (signal) => dispatchVerifiedQueueOwnerSignal(owner, signal, requireStale, retirement), isProcessDefinitelyDead);
|
|
2881
|
-
if (
|
|
2882
|
-
|
|
2883
|
-
const gone = retirement.gone || await observeProcessIncarnation(owner.pid, owner.processIdentity) === "gone";
|
|
3293
|
+
await terminateWithDispatch(owner.pid, (signal) => dispatchVerifiedQueueOwnerSignal(owner, signal, requireStale, retirement), isProcessDefinitelyDead, process.platform === "win32" ? deadline : Infinity);
|
|
3294
|
+
if (await waitForReleasedOwner(owner, requireStale, retirement)) return false;
|
|
3295
|
+
const gone = retirement.gone || await observeRetiringOwner(owner, deadline) === "gone";
|
|
2884
3296
|
if (retirement.unverified && !gone) throw unverifiedQueueOwnerError(owner);
|
|
2885
3297
|
return gone;
|
|
2886
3298
|
}
|
|
3299
|
+
async function waitForReleasedOwner(owner, requireStale, retirement) {
|
|
3300
|
+
if (retirement.unverified && requireStale) throw unverifiedQueueOwnerError(owner);
|
|
3301
|
+
if (retirement.leaseReleased || retirement.unverified) {
|
|
3302
|
+
await waitForProcessExit(owner.pid, Math.max(0, retirement.deadline - performance.now()), isProcessDefinitelyDead);
|
|
3303
|
+
if (performance.now() >= retirement.deadline) {
|
|
3304
|
+
if (retirement.unverified) throw unverifiedQueueOwnerError(owner);
|
|
3305
|
+
return true;
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
return false;
|
|
3309
|
+
}
|
|
3310
|
+
async function retireRecordedDescendants(owner, deadline) {
|
|
3311
|
+
await withQueueLeaseMutation(owner.sessionId, async () => {
|
|
3312
|
+
const current = await readQueueOwnerRecord(owner.sessionId);
|
|
3313
|
+
if (!matchesQueueOwner(current, owner) || current.retirement === void 0) return;
|
|
3314
|
+
if (current.retirement === null || process.platform !== "win32") throw queueRetirementIncomplete();
|
|
3315
|
+
if (current.retirement.descendants.length === 0) return;
|
|
3316
|
+
current.retirement = await captureQueueRetirementReceipt(current, current.retirement, false, deadline);
|
|
3317
|
+
await replaceQueueOwnerRecord(current);
|
|
3318
|
+
await signalRecordedDescendants(current.retirement, deadline);
|
|
3319
|
+
await waitForRecordedDescendants(current.retirement, deadline);
|
|
3320
|
+
});
|
|
3321
|
+
}
|
|
3322
|
+
async function hasSurvivingRetirementDescendant(receipt, deadline) {
|
|
3323
|
+
for (const witness of receipt.descendants) {
|
|
3324
|
+
const state = await observeRetirementWitness(witness, deadline);
|
|
3325
|
+
if (state === "unknown") throw queueRetirementIncomplete();
|
|
3326
|
+
if (state === "matching") return true;
|
|
3327
|
+
}
|
|
3328
|
+
return false;
|
|
3329
|
+
}
|
|
3330
|
+
async function signalRecordedDescendants(receipt, deadline) {
|
|
3331
|
+
for (const witness of receipt.descendants) {
|
|
3332
|
+
const state = await observeRetirementWitness(witness, deadline);
|
|
3333
|
+
if (state === "unknown") throw queueRetirementIncomplete();
|
|
3334
|
+
if (state === "matching") {
|
|
3335
|
+
retirementTimeRemaining(deadline);
|
|
3336
|
+
try {
|
|
3337
|
+
process.kill(witness.pid, "SIGKILL");
|
|
3338
|
+
} catch (error) {
|
|
3339
|
+
if (await observeRetirementWitness(witness, deadline) !== "gone") throw error;
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
async function waitForRecordedDescendants(receipt, deadline) {
|
|
3345
|
+
while (true) {
|
|
3346
|
+
if (!await hasSurvivingRetirementDescendant(receipt, deadline)) return;
|
|
3347
|
+
await waitMs(Math.min(1e3, retirementTimeRemaining(deadline)));
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
async function assertNoPendingRetirement(owner) {
|
|
3351
|
+
const current = await readQueueOwnerRecord(owner.sessionId);
|
|
3352
|
+
if (matchesQueueOwner(current, owner) && current.retirement !== void 0) throw queueRetirementIncomplete();
|
|
3353
|
+
}
|
|
2887
3354
|
async function terminateQueueOwnerForSession(sessionId, expectedOwner, requireStale = false) {
|
|
2888
3355
|
await settlePendingQueueLeaseGuard(sessionId);
|
|
2889
3356
|
const owner = expectedOwner ?? await readQueueOwnerRecord(sessionId);
|
|
2890
|
-
if (!owner || owner.sessionId !== sessionId
|
|
2891
|
-
if (
|
|
2892
|
-
|
|
3357
|
+
if (!owner || owner.sessionId !== sessionId) return;
|
|
3358
|
+
if (owner.pid === process.pid) {
|
|
3359
|
+
if (owner.retirement !== void 0) throw queueRetirementIncomplete();
|
|
3360
|
+
return;
|
|
3361
|
+
}
|
|
3362
|
+
const deadline = retirementDeadline();
|
|
3363
|
+
if (!await retireQueueOwner(owner, requireStale, deadline)) {
|
|
3364
|
+
await assertNoPendingRetirement(owner);
|
|
3365
|
+
return;
|
|
3366
|
+
}
|
|
3367
|
+
await retireRecordedDescendants(owner, deadline);
|
|
3368
|
+
await cleanupQueueOwnerFiles(sessionId, owner.socketPath, () => ownsQueueLease(owner), void 0, deadline);
|
|
2893
3369
|
}
|
|
2894
3370
|
async function waitMs(ms) {
|
|
2895
3371
|
await new Promise((resolve) => {
|
|
@@ -3294,9 +3770,12 @@ function queueOwnerMessageParser(type) {
|
|
|
3294
3770
|
}
|
|
3295
3771
|
function parseEventOwnerMessage(message, context) {
|
|
3296
3772
|
if (!isAcpJsonRpcMessage(message.message)) return null;
|
|
3773
|
+
const direction = message.direction;
|
|
3774
|
+
if (direction !== void 0 && direction !== "inbound" && direction !== "outbound") return null;
|
|
3297
3775
|
return {
|
|
3298
3776
|
type: "event",
|
|
3299
3777
|
...context,
|
|
3778
|
+
...direction === void 0 ? {} : { direction },
|
|
3300
3779
|
message: message.message
|
|
3301
3780
|
};
|
|
3302
3781
|
}
|
|
@@ -3578,7 +4057,7 @@ function unexpectedQueueResponseError() {
|
|
|
3578
4057
|
}
|
|
3579
4058
|
function handleAcknowledgedSubmitMessage(message, controls, formatter) {
|
|
3580
4059
|
if (message.type === "event") {
|
|
3581
|
-
notifyObserver(() => formatter.onAcpMessage(message.message));
|
|
4060
|
+
notifyObserver(() => formatter.onAcpMessage(message.message, message.direction));
|
|
3582
4061
|
return;
|
|
3583
4062
|
}
|
|
3584
4063
|
if (message.type === "permission_escalation") {
|
|
@@ -3861,6 +4340,6 @@ async function trySetConfigOptionOnRunningOwner(sessionId, configId, value, time
|
|
|
3861
4340
|
});
|
|
3862
4341
|
}
|
|
3863
4342
|
//#endregion
|
|
3864
|
-
export { measurePerf as $, findGitRepositoryRoot as A, PERMISSION_POLICY_ACTIONS as At, writeSessionRecord as B, PermissionDeniedError as Bt, getUnsupportedPromptContentMessage as C, AUTH_POLICIES as Ct, textPrompt as D, OUTPUT_ERROR_ORIGINS as Dt, promptToDisplayText as E, OUTPUT_ERROR_CODES as Et, listSessionsForAgent as F, AgentStartupError as Ft, normalizeAgentSessionId as G, SessionModelReplayError as Gt, parseSessionRecord as H, QueueConnectionError as Ht, normalizeName as I, AuthPolicyError as It, SessionWatchError as J, UnsupportedPromptContentError as Jt, SESSION_JOURNAL_SCHEMA as K, SessionNotFoundError as Kt, pruneSessions as L, ClaudeAcpSessionCreateTimeoutError as Lt, findSessionByDirectoryWalk as M, AcpxOperationalError as Mt, isoNow as N, AgentDisconnectedError as Nt, DEFAULT_HISTORY_LIMIT as O, OUTPUT_FORMATS as Ot, listSessions as P, AgentSpawnError as Pt, incrementPerfCounter as Q, readSessionRecord as R, CopilotAcpUnsupportedError as Rt, PromptInputValidationError as S, isAcpResourceNotFoundError as St, parsePromptSource as T, NON_INTERACTIVE_PERMISSION_POLICIES as Tt, serializeSessionRecordForDisk as U, SessionConfigOptionReplayError as Ut, assertPersistedKeyPolicy as V, PermissionPromptUnavailableError as Vt, extractAgentSessionId as W, SessionModeReplayError as Wt, formatPerfMetric as X, watchSession as Y, getPerfMetricsSnapshot as Z, waitMs as _, writePrivateFile as _t, trySetModelOnRunningOwner as a, isAcpJsonRpcMessage as at, lockOwnerPid as b, extractAcpError as bt, queueRequestExceedsLimit as c, parseJsonRpcErrorMessage as ct, readQueueOwnerRecord as d, DEFAULT_EVENT_SEGMENT_MAX_BYTES as dt, recordPerfDuration as et, refreshQueueOwnerLease as f, defaultSessionEventLog as ft, tryAcquireQueueOwnerLease as g, sessionEventSegmentPath as gt, terminateQueueOwnerForSession as h, sessionEventLockPath as ht, trySetModeOnRunningOwner as i, extractSessionUpdateNotification as it, findSession as j, SESSION_RECORD_SCHEMA as jt, absolutePath as k, PERMISSION_MODES as kt, parseQueueRequest as l, parsePermissionNotice as lt, terminateProcess as m, sessionEventActivePath as mt, tryCloseSessionOnRunningOwner as n, setPerfGauge as nt, trySubmitToRunningOwner as o, isAcpMessageObject as ot, releaseQueueOwnerLease as p, sessionBaseDir as pt, SessionJournalReader as q, SessionResumeRequiredError as qt, trySetConfigOptionOnRunningOwner as r, startPerfTimer as rt, queueRequestByteLimit as s, isSessionUpdateNotification as st, tryCancelOnRunningOwner as t, resetPerfMetrics as tt, probeQueueOwnerHealth as u, parsePromptStopReason as ut, QueueLeaseGuardSettlementError as v, writePrivateJsonFile as vt, mergePromptSourceWithText as w, EXIT_CODES as wt,
|
|
4343
|
+
export { measurePerf as $, findGitRepositoryRoot as A, PERMISSION_POLICY_ACTIONS as At, writeSessionRecord as B, PermissionDeniedError as Bt, getUnsupportedPromptContentMessage as C, AUTH_POLICIES as Ct, textPrompt as D, OUTPUT_ERROR_ORIGINS as Dt, promptToDisplayText as E, OUTPUT_ERROR_CODES as Et, listSessionsForAgent as F, AgentStartupError as Ft, normalizeAgentSessionId as G, SessionModelReplayError as Gt, parseSessionRecord as H, QueueConnectionError as Ht, normalizeName as I, AuthPolicyError as It, SessionWatchError as J, UnsupportedPromptContentError as Jt, SESSION_JOURNAL_SCHEMA as K, SessionNotFoundError as Kt, pruneSessions as L, ClaudeAcpSessionCreateTimeoutError as Lt, findSessionByDirectoryWalk as M, AcpxOperationalError as Mt, isoNow as N, AgentDisconnectedError as Nt, DEFAULT_HISTORY_LIMIT as O, OUTPUT_FORMATS as Ot, listSessions as P, AgentSpawnError as Pt, incrementPerfCounter as Q, readSessionRecord as R, CopilotAcpUnsupportedError as Rt, PromptInputValidationError as S, isAcpResourceNotFoundError as St, parsePromptSource as T, NON_INTERACTIVE_PERMISSION_POLICIES as Tt, serializeSessionRecordForDisk as U, SessionConfigOptionReplayError as Ut, assertPersistedKeyPolicy as V, PermissionPromptUnavailableError as Vt, extractAgentSessionId as W, SessionModeReplayError as Wt, formatPerfMetric as X, watchSession as Y, getPerfMetricsSnapshot as Z, waitMs as _, writePrivateFile as _t, trySetModelOnRunningOwner as a, isAcpJsonRpcMessage as at, lockOwnerPid as b, extractAcpError as bt, queueRequestExceedsLimit as c, parseJsonRpcErrorMessage as ct, readQueueOwnerRecord as d, DEFAULT_EVENT_SEGMENT_MAX_BYTES as dt, recordPerfDuration as et, refreshQueueOwnerLease as f, defaultSessionEventLog as ft, tryAcquireQueueOwnerLease as g, sessionEventSegmentPath as gt, terminateQueueOwnerForSession as h, sessionEventLockPath as ht, trySetModeOnRunningOwner as i, extractSessionUpdateNotification as it, findSession as j, SESSION_RECORD_SCHEMA as jt, absolutePath as k, PERMISSION_MODES as kt, parseQueueRequest as l, parsePermissionNotice as lt, terminateProcess as m, sessionEventActivePath as mt, tryCloseSessionOnRunningOwner as n, setPerfGauge as nt, trySubmitToRunningOwner as o, isAcpMessageObject as ot, releaseQueueOwnerLease as p, sessionBaseDir as pt, SessionJournalReader as q, SessionResumeRequiredError as qt, trySetConfigOptionOnRunningOwner as r, startPerfTimer as rt, queueRequestByteLimit as s, isSessionUpdateNotification as st, tryCancelOnRunningOwner as t, resetPerfMetrics as tt, probeQueueOwnerHealth as u, parsePromptStopReason as ut, QueueLeaseGuardSettlementError as v, writePrivateJsonFile as vt, mergePromptSourceWithText as w, EXIT_CODES as wt, ProcessDescendants as x, formatUnknownErrorMessage as xt, createLockOwner as y, isProcessAlive as yt, resolveSessionRecord as z, GeminiAcpStartupTimeoutError as zt };
|
|
3865
4344
|
|
|
3866
|
-
//# sourceMappingURL=ipc-
|
|
4345
|
+
//# sourceMappingURL=ipc-DAApJ5mi.js.map
|