acpx 0.18.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.
Files changed (41) hide show
  1. package/dist/{agent-registry-DdKB-zia.js → agent-registry-DWlx5BQ4.js} +5 -3
  2. package/dist/agent-registry-DWlx5BQ4.js.map +1 -0
  3. package/dist/agent-registry.js +1 -1
  4. package/dist/{cli-8mVHeUXh.js → cli-BerngYEz.js} +5 -5
  5. package/dist/{cli-8mVHeUXh.js.map → cli-BerngYEz.js.map} +1 -1
  6. package/dist/cli.js +77 -53
  7. package/dist/cli.js.map +1 -1
  8. package/dist/{client-DeNKZZcu.d.ts → client-BchvQRpz.d.ts} +17 -4
  9. package/dist/client-BchvQRpz.d.ts.map +1 -0
  10. package/dist/{flows-WOQNzCMC.js → flows-DimX0it5.js} +693 -475
  11. package/dist/flows-DimX0it5.js.map +1 -0
  12. package/dist/flows.d.ts +20 -7
  13. package/dist/flows.d.ts.map +1 -1
  14. package/dist/flows.js +1 -1
  15. package/dist/{invocation-options-B6d7TI-y.js → invocation-options-vobVApfO.js} +32 -11
  16. package/dist/invocation-options-vobVApfO.js.map +1 -0
  17. package/dist/{ipc-BJrvfNXr.js → ipc-DAApJ5mi.js} +1041 -263
  18. package/dist/ipc-DAApJ5mi.js.map +1 -0
  19. package/dist/{output-CL18YmsG.js → output-Cnuji8Bo.js} +41 -20
  20. package/dist/output-Cnuji8Bo.js.map +1 -0
  21. package/dist/{queue-owner-runtime-Cr0dWJuL.js → queue-owner-runtime-C4OOosbo.js} +732 -507
  22. package/dist/queue-owner-runtime-C4OOosbo.js.map +1 -0
  23. package/dist/runtime.d.ts +19 -1
  24. package/dist/runtime.d.ts.map +1 -1
  25. package/dist/runtime.js +48 -29
  26. package/dist/runtime.js.map +1 -1
  27. package/dist/session-options-Dp6mzNSy.d.ts.map +1 -1
  28. package/dist/{session-watch-B8Q_TsGf.js → session-watch-Bj9LoYUq.js} +5 -5
  29. package/dist/session-watch-Bj9LoYUq.js.map +1 -0
  30. package/dist/{watch-Dp73ph8h.js → watch-Cz6jbrRs.js} +2 -2
  31. package/dist/{watch-Dp73ph8h.js.map → watch-Cz6jbrRs.js.map} +1 -1
  32. package/package.json +1 -1
  33. package/skills/acpx/SKILL.md +45 -23
  34. package/dist/agent-registry-DdKB-zia.js.map +0 -1
  35. package/dist/client-DeNKZZcu.d.ts.map +0 -1
  36. package/dist/flows-WOQNzCMC.js.map +0 -1
  37. package/dist/invocation-options-B6d7TI-y.js.map +0 -1
  38. package/dist/ipc-BJrvfNXr.js.map +0 -1
  39. package/dist/output-CL18YmsG.js.map +0 -1
  40. package/dist/queue-owner-runtime-Cr0dWJuL.js.map +0 -1
  41. package/dist/session-watch-B8Q_TsGf.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { n as AGENT_REGISTRY, r as BUILT_IN_AGENT_PACKAGES, t as AGENT_ARGV_REGISTRY } from "./agent-registry-DdKB-zia.js";
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";
@@ -217,6 +217,7 @@ const OUTPUT_ERROR_CODES = [
217
217
  "TIMEOUT",
218
218
  "PERMISSION_DENIED",
219
219
  "PERMISSION_PROMPT_UNAVAILABLE",
220
+ "EXEC_DISABLED",
220
221
  "RUNTIME",
221
222
  "USAGE"
222
223
  ];
@@ -230,12 +231,12 @@ const SESSION_RECORD_SCHEMA = "acpx.session.v1";
230
231
  //#endregion
231
232
  //#region src/acp/error-shapes.ts
232
233
  const RESOURCE_NOT_FOUND_ACP_CODES = /* @__PURE__ */ new Set([-32001, -32002]);
233
- function asRecord$5(value) {
234
+ function asRecord$4(value) {
234
235
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
235
236
  return value;
236
237
  }
237
238
  function toAcpErrorPayload(value) {
238
- const record = asRecord$5(value);
239
+ const record = asRecord$4(value);
239
240
  if (!record) return;
240
241
  if (typeof record.code !== "number" || !Number.isFinite(record.code)) return;
241
242
  if (typeof record.message !== "string" || record.message.length === 0) return;
@@ -249,7 +250,7 @@ function extractAcpErrorInternal(value, depth) {
249
250
  if (depth > 5) return;
250
251
  const direct = toAcpErrorPayload(value);
251
252
  if (direct) return direct;
252
- const record = asRecord$5(value);
253
+ const record = asRecord$4(value);
253
254
  if (!record) return;
254
255
  return extractNestedAcpError(record, depth);
255
256
  }
@@ -284,7 +285,7 @@ function hasSessionNotFoundHint(value, depth = 0) {
284
285
  if (depth > 4) return false;
285
286
  if (isSessionNotFoundText(value)) return true;
286
287
  if (Array.isArray(value)) return value.some((entry) => hasSessionNotFoundHint(entry, depth + 1));
287
- const record = asRecord$5(value);
288
+ const record = asRecord$4(value);
288
289
  if (!record) return false;
289
290
  return Object.values(record).some((entry) => hasSessionNotFoundHint(entry, depth + 1));
290
291
  }
@@ -440,18 +441,18 @@ function defaultSessionEventLog(sessionId) {
440
441
  }
441
442
  //#endregion
442
443
  //#region src/acp/jsonrpc.ts
443
- function asRecord$4(value) {
444
+ function asRecord$3(value) {
444
445
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
445
446
  return value;
446
447
  }
447
448
  function isAcpMessageObject(value) {
448
- return asRecord$4(value) !== null;
449
+ return asRecord$3(value) !== null;
449
450
  }
450
451
  function hasValidId(value) {
451
452
  return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
452
453
  }
453
454
  function isErrorObject(value) {
454
- const record = asRecord$4(value);
455
+ const record = asRecord$3(value);
455
456
  return !!record && typeof record.code === "number" && Number.isFinite(record.code) && typeof record.message === "string";
456
457
  }
457
458
  function hasResultOrError(value) {
@@ -476,7 +477,7 @@ function isJsonRpcResponse(value) {
476
477
  return hasResultOrError(value);
477
478
  }
478
479
  function isAcpJsonRpcMessage(value) {
479
- const record = asRecord$4(value);
480
+ const record = asRecord$3(value);
480
481
  if (!record || record.jsonrpc !== "2.0") return false;
481
482
  return isJsonRpcNotificationRecord(record) || isJsonRpcRequest(record) || isJsonRpcResponse(record);
482
483
  }
@@ -486,14 +487,29 @@ function isJsonRpcNotification(message) {
486
487
  function isSessionUpdateNotification(message) {
487
488
  return isJsonRpcNotification(message) && message.method === "session/update";
488
489
  }
490
+ function sessionUpdateHasRequiredFields(update) {
491
+ const kind = update.sessionUpdate;
492
+ if (typeof kind !== "string") return false;
493
+ if (kind === "agent_message_chunk" || kind === "agent_thought_chunk" || kind === "user_message_chunk") return isChunkContent(update.content);
494
+ if (kind === "plan") return Array.isArray(update.entries) && update.entries.every(isPlanEntry);
495
+ return true;
496
+ }
497
+ function isChunkContent(value) {
498
+ const content = asRecord$3(value);
499
+ return content !== null && typeof content.type === "string" && (content.type !== "text" || typeof content.text === "string");
500
+ }
501
+ function isPlanEntry(value) {
502
+ const entry = asRecord$3(value);
503
+ return entry !== null && typeof entry.status === "string" && typeof entry.content === "string";
504
+ }
489
505
  function extractSessionUpdateNotification(message) {
490
506
  if (!isSessionUpdateNotification(message)) return;
491
- const params = asRecord$4(message.params);
507
+ const params = asRecord$3(message.params);
492
508
  if (!params) return;
493
509
  const sessionId = typeof params.sessionId === "string" ? params.sessionId : null;
494
510
  if (!sessionId) return;
495
- const update = asRecord$4(params.update);
496
- if (!update || typeof update.sessionUpdate !== "string") return;
511
+ const update = asRecord$3(params.update);
512
+ if (!update || !sessionUpdateHasRequiredFields(update)) return;
497
513
  return {
498
514
  sessionId,
499
515
  update
@@ -501,18 +517,18 @@ function extractSessionUpdateNotification(message) {
501
517
  }
502
518
  function parsePromptStopReason(message) {
503
519
  if (!Object.hasOwn(message, "id") || !Object.hasOwn(message, "result")) return;
504
- const record = asRecord$4(message.result);
520
+ const record = asRecord$3(message.result);
505
521
  if (!record) return;
506
522
  return typeof record.stopReason === "string" ? record.stopReason : void 0;
507
523
  }
508
524
  function parsePermissionNotice(message) {
509
525
  if (!Object.hasOwn(message, "id") || !Object.hasOwn(message, "result")) return;
510
- const notice = asRecord$4(asRecord$4(asRecord$4(message.result)?._meta)?.acpx)?.permissionNotice;
526
+ const notice = asRecord$3(asRecord$3(asRecord$3(message.result)?._meta)?.acpx)?.permissionNotice;
511
527
  return typeof notice === "string" ? notice : void 0;
512
528
  }
513
529
  function parseJsonRpcErrorMessage(message) {
514
530
  if (!Object.hasOwn(message, "error")) return;
515
- const errorRecord = asRecord$4(message.error);
531
+ const errorRecord = asRecord$3(message.error);
516
532
  if (!errorRecord || typeof errorRecord.message !== "string") return;
517
533
  return errorRecord.message;
518
534
  }
@@ -1108,7 +1124,7 @@ function resolveAgentArgvForCommand(agentCommand) {
1108
1124
  }
1109
1125
  //#endregion
1110
1126
  //#region src/session/persistence/parse.ts
1111
- function asRecord$3(value) {
1127
+ function asRecord$2(value) {
1112
1128
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
1113
1129
  return value;
1114
1130
  }
@@ -1127,12 +1143,12 @@ function parsePersistedAgentArgv(record) {
1127
1143
  function hasModelConfigOption(options) {
1128
1144
  if (!Array.isArray(options)) return false;
1129
1145
  return options.some((entry) => {
1130
- const option = asRecord$3(entry);
1146
+ const option = asRecord$2(entry);
1131
1147
  return option?.category === "model" || option?.id === "model";
1132
1148
  });
1133
1149
  }
1134
1150
  function parseConfigOptions(raw) {
1135
- if (!Array.isArray(raw) || !raw.every((entry) => asRecord$3(entry) !== void 0)) return;
1151
+ if (!Array.isArray(raw) || !raw.every((entry) => asRecord$2(entry) !== void 0)) return;
1136
1152
  return raw;
1137
1153
  }
1138
1154
  function parseAvailableCommand(raw) {
@@ -1140,7 +1156,7 @@ function parseAvailableCommand(raw) {
1140
1156
  const name = raw.trim();
1141
1157
  return name ? { name } : void 0;
1142
1158
  }
1143
- const record = asRecord$3(raw);
1159
+ const record = asRecord$2(raw);
1144
1160
  if (!record) return;
1145
1161
  const name = parseNonEmptyString$1(record.name);
1146
1162
  if (!name) return;
@@ -1163,7 +1179,7 @@ function parseNonEmptyString$1(value) {
1163
1179
  }
1164
1180
  function parseTokenUsage(raw) {
1165
1181
  if (raw === void 0 || raw === null) return;
1166
- const record = asRecord$3(raw);
1182
+ const record = asRecord$2(raw);
1167
1183
  if (!record) return null;
1168
1184
  const usage = {};
1169
1185
  for (const field of [
@@ -1186,7 +1202,7 @@ function isNonNegativeFiniteNumber(value) {
1186
1202
  }
1187
1203
  function parseUsageCost(raw) {
1188
1204
  if (raw === void 0 || raw === null) return;
1189
- const record = asRecord$3(raw);
1205
+ const record = asRecord$2(raw);
1190
1206
  if (!record) return null;
1191
1207
  return parseUsageCostRecord(record);
1192
1208
  }
@@ -1212,7 +1228,7 @@ function parseCostCurrency(value) {
1212
1228
  }
1213
1229
  function parseRequestTokenUsage(raw) {
1214
1230
  if (raw === void 0 || raw === null) return;
1215
- const record = asRecord$3(raw);
1231
+ const record = asRecord$2(raw);
1216
1232
  if (!record) return null;
1217
1233
  const usage = {};
1218
1234
  for (const [key, value] of Object.entries(record)) {
@@ -1223,25 +1239,25 @@ function parseRequestTokenUsage(raw) {
1223
1239
  return usage;
1224
1240
  }
1225
1241
  function isSessionMessageImage(raw) {
1226
- const record = asRecord$3(raw);
1242
+ const record = asRecord$2(raw);
1227
1243
  if (!record || typeof record.source !== "string") return false;
1228
1244
  if (record.size === void 0 || record.size === null) return true;
1229
- const size = asRecord$3(record.size);
1245
+ const size = asRecord$2(record.size);
1230
1246
  return !!size && isFiniteNumber(size.width) && isFiniteNumber(size.height);
1231
1247
  }
1232
1248
  function isSessionMessageAudio(raw) {
1233
- const record = asRecord$3(raw);
1249
+ const record = asRecord$2(raw);
1234
1250
  return !!record && typeof record.source === "string" && typeof record.mime_type === "string";
1235
1251
  }
1236
1252
  function isFiniteNumber(value) {
1237
1253
  return typeof value === "number" && Number.isFinite(value);
1238
1254
  }
1239
1255
  function isUserContent(raw) {
1240
- const record = asRecord$3(raw);
1256
+ const record = asRecord$2(raw);
1241
1257
  if (!record) return false;
1242
1258
  if (typeof record.Text === "string") return true;
1243
1259
  if (record.Mention !== void 0) {
1244
- const mention = asRecord$3(record.Mention);
1260
+ const mention = asRecord$2(record.Mention);
1245
1261
  return !!mention && typeof mention.uri === "string" && typeof mention.content === "string";
1246
1262
  }
1247
1263
  if (record.Image !== void 0) return isSessionMessageImage(record.Image);
@@ -1249,7 +1265,7 @@ function isUserContent(raw) {
1249
1265
  return false;
1250
1266
  }
1251
1267
  function isToolUse(raw) {
1252
- const record = asRecord$3(raw);
1268
+ const record = asRecord$2(raw);
1253
1269
  return !!record && hasStringFields$1(record, [
1254
1270
  "id",
1255
1271
  "name",
@@ -1263,18 +1279,18 @@ function isOptionalString(value) {
1263
1279
  return value === void 0 || value === null || typeof value === "string";
1264
1280
  }
1265
1281
  function isToolResultContent(raw) {
1266
- const record = asRecord$3(raw);
1282
+ const record = asRecord$2(raw);
1267
1283
  if (!record) return false;
1268
1284
  if (typeof record.Text === "string") return true;
1269
1285
  if (record.Image !== void 0) return isSessionMessageImage(record.Image);
1270
1286
  return false;
1271
1287
  }
1272
1288
  function isToolResult(raw) {
1273
- const record = asRecord$3(raw);
1289
+ const record = asRecord$2(raw);
1274
1290
  return !!record && typeof record.tool_use_id === "string" && typeof record.tool_name === "string" && typeof record.is_error === "boolean" && isToolResultContent(record.content);
1275
1291
  }
1276
1292
  function isAgentContent(raw) {
1277
- const record = asRecord$3(raw);
1293
+ const record = asRecord$2(raw);
1278
1294
  if (!record) return false;
1279
1295
  if (typeof record.Text === "string") return true;
1280
1296
  if (record.Thinking !== void 0) return isThinkingContent(record.Thinking);
@@ -1283,21 +1299,21 @@ function isAgentContent(raw) {
1283
1299
  return false;
1284
1300
  }
1285
1301
  function isThinkingContent(raw) {
1286
- const thinking = asRecord$3(raw);
1302
+ const thinking = asRecord$2(raw);
1287
1303
  return !!thinking && typeof thinking.text === "string" && isOptionalString(thinking.signature);
1288
1304
  }
1289
1305
  function isUserMessage(raw) {
1290
- const record = asRecord$3(raw);
1306
+ const record = asRecord$2(raw);
1291
1307
  if (!record || record.User === void 0) return false;
1292
- const user = asRecord$3(record.User);
1308
+ const user = asRecord$2(record.User);
1293
1309
  return !!user && typeof user.id === "string" && Array.isArray(user.content) && user.content.every((entry) => isUserContent(entry));
1294
1310
  }
1295
1311
  function isAgentMessage(raw) {
1296
- const record = asRecord$3(raw);
1312
+ const record = asRecord$2(raw);
1297
1313
  if (!record || record.Agent === void 0) return false;
1298
- const agent = asRecord$3(record.Agent);
1314
+ const agent = asRecord$2(record.Agent);
1299
1315
  if (!agent || !Array.isArray(agent.content) || !agent.content.every(isAgentContent)) return false;
1300
- const toolResults = asRecord$3(agent.tool_results);
1316
+ const toolResults = asRecord$2(agent.tool_results);
1301
1317
  if (!toolResults) return false;
1302
1318
  return Object.values(toolResults).every(isToolResult);
1303
1319
  }
@@ -1330,7 +1346,7 @@ function hasValidConversationCore(record) {
1330
1346
  return Array.isArray(record.messages) && record.messages.every(isConversationMessage) && typeof record.updated_at === "string";
1331
1347
  }
1332
1348
  function parseAcpxState(raw) {
1333
- const record = asRecord$3(raw);
1349
+ const record = asRecord$2(raw);
1334
1350
  if (!record) return;
1335
1351
  const state = {};
1336
1352
  if (record.reset_on_next_ensure === true) state.reset_on_next_ensure = true;
@@ -1358,13 +1374,13 @@ function assignStringState(state, key, value) {
1358
1374
  if (typeof value === "string") state[key] = value;
1359
1375
  }
1360
1376
  function parseStringMap(raw, preserveEmpty = false) {
1361
- const record = asRecord$3(raw);
1377
+ const record = asRecord$2(raw);
1362
1378
  if (!record) return;
1363
1379
  const parsed = Object.fromEntries(Object.entries(record).filter((entry) => typeof entry[1] === "string"));
1364
1380
  return Object.keys(parsed).length > 0 || preserveEmpty ? parsed : void 0;
1365
1381
  }
1366
1382
  function assignParsedSessionOptions(state, raw) {
1367
- const sessionOptions = asRecord$3(raw);
1383
+ const sessionOptions = asRecord$2(raw);
1368
1384
  if (!sessionOptions) return;
1369
1385
  const parsedSessionOptions = {};
1370
1386
  if (typeof sessionOptions.model === "string") parsedSessionOptions.model = sessionOptions.model;
@@ -1380,11 +1396,11 @@ function assignSessionOptionSystemPrompt(options, value) {
1380
1396
  options.system_prompt = value;
1381
1397
  return;
1382
1398
  }
1383
- const appendRecord = asRecord$3(value);
1399
+ const appendRecord = asRecord$2(value);
1384
1400
  if (appendRecord && typeof appendRecord.append === "string" && appendRecord.append.length > 0) options.system_prompt = { append: appendRecord.append };
1385
1401
  }
1386
1402
  function parseEventLog(raw, sessionId) {
1387
- const record = asRecord$3(raw);
1403
+ const record = asRecord$2(raw);
1388
1404
  if (!record || !hasValidEventLogCore(record)) return defaultSessionEventLog(sessionId);
1389
1405
  return {
1390
1406
  active_path: record.active_path,
@@ -1403,7 +1419,7 @@ function isPositiveInteger$1(value) {
1403
1419
  }
1404
1420
  function parseImportedFrom(raw) {
1405
1421
  if (raw == null) return;
1406
- const record = asRecord$3(raw);
1422
+ const record = asRecord$2(raw);
1407
1423
  if (!record || typeof record.record_id !== "string" || typeof record.cwd_original !== "string" || typeof record.exported_by !== "string" || typeof record.exported_at !== "string") return null;
1408
1424
  return {
1409
1425
  recordId: record.record_id,
@@ -1453,7 +1469,7 @@ function normalizeOptionalSignal(value) {
1453
1469
  return Symbol("invalid");
1454
1470
  }
1455
1471
  function parseSessionRecord(raw) {
1456
- const record = asRecord$3(raw);
1472
+ const record = asRecord$2(raw);
1457
1473
  if (!record) return null;
1458
1474
  if (record.schema !== "acpx.session.v1") return null;
1459
1475
  const optionals = validSessionOptionals({
@@ -1498,7 +1514,7 @@ function parseSessionRecord(raw) {
1498
1514
  lastAgentExitAt: optionals.lastAgentExitAt,
1499
1515
  lastAgentDisconnectReason: optionals.lastAgentDisconnectReason,
1500
1516
  protocolVersion: typeof record.protocol_version === "number" ? record.protocol_version : void 0,
1501
- agentCapabilities: asRecord$3(record.agent_capabilities),
1517
+ agentCapabilities: asRecord$2(record.agent_capabilities),
1502
1518
  title: conversation.title,
1503
1519
  messages: conversation.messages,
1504
1520
  updated_at: conversation.updated_at,
@@ -1611,111 +1627,28 @@ function assertPersistedKeyPolicy(value) {
1611
1627
  throw new Error(`Persisted key policy violation (expected snake_case keys): ${violations.join(", ")}`);
1612
1628
  }
1613
1629
  //#endregion
1614
- //#region src/session/persistence/index.ts
1615
- const SESSION_INDEX_SCHEMA = "acpx.session-index.v1";
1616
- function asRecord$2(value) {
1617
- if (!value || typeof value !== "object" || Array.isArray(value)) return;
1618
- return value;
1619
- }
1620
- function parseIndexEntry(raw) {
1621
- const record = asRecord$2(raw);
1622
- if (!record) return;
1623
- if (!hasRequiredIndexEntryFields(record)) return;
1624
- if (record.name !== void 0 && typeof record.name !== "string") return;
1625
- return {
1626
- file: record.file,
1627
- acpxRecordId: record.acpxRecordId,
1628
- acpSessionId: record.acpSessionId,
1629
- agentCommand: record.agentCommand,
1630
- cwd: record.cwd,
1631
- name: record.name,
1632
- closed: record.closed,
1633
- lastUsedAt: record.lastUsedAt
1634
- };
1635
- }
1636
- function hasRequiredIndexEntryFields(record) {
1637
- return [
1638
- "file",
1639
- "acpxRecordId",
1640
- "acpSessionId",
1641
- "agentCommand",
1642
- "cwd",
1643
- "lastUsedAt"
1644
- ].every((key) => typeof record[key] === "string") && typeof record.closed === "boolean";
1645
- }
1646
- function sessionIndexPath(sessionDir) {
1647
- return path.join(sessionDir, "index.json");
1648
- }
1649
- function toSessionIndexEntry(record, fileName) {
1650
- return {
1651
- file: fileName,
1652
- acpxRecordId: record.acpxRecordId,
1653
- acpSessionId: record.acpSessionId,
1654
- agentCommand: record.agentCommand,
1655
- cwd: record.cwd,
1656
- name: record.name,
1657
- closed: record.closed === true,
1658
- lastUsedAt: record.lastUsedAt
1659
- };
1630
+ //#region src/session/persistence/discovery.ts
1631
+ const DISCOVERY_BATCH_SIZE = 4;
1632
+ /** Select and return the same canonical snapshot; cached metadata cannot exclude records. */
1633
+ async function* scanSessionRecords(sessionDir) {
1634
+ const files = (await fs$1.readdir(sessionDir, { withFileTypes: true }).catch((error) => {
1635
+ if (error.code === "ENOENT") return [];
1636
+ throw error;
1637
+ })).filter((entry) => entry.isFile() && entry.name.endsWith(".json") && entry.name !== "index.json").map((entry) => entry.name).toSorted();
1638
+ for (let offset = 0; offset < files.length; offset += DISCOVERY_BATCH_SIZE) {
1639
+ const records = await Promise.all(files.slice(offset, offset + DISCOVERY_BATCH_SIZE).map((file) => readRecord(path.join(sessionDir, file))));
1640
+ for (const record of records) if (record) yield record;
1641
+ }
1660
1642
  }
1661
- async function readSessionIndex(sessionDir) {
1662
- const filePath = sessionIndexPath(sessionDir);
1643
+ async function readRecord(file) {
1663
1644
  try {
1664
- const payload = await fs$1.readFile(filePath, "utf8");
1665
- const record = asRecord$2(JSON.parse(payload));
1666
- if (!record || record.schema !== SESSION_INDEX_SCHEMA || !Array.isArray(record.files)) return;
1667
- const files = record.files.filter((entry) => typeof entry === "string");
1668
- if (files.length !== record.files.length || !Array.isArray(record.entries)) return;
1669
- const entries = record.entries.map((entry) => parseIndexEntry(entry)).filter((entry) => Boolean(entry));
1670
- if (entries.length !== record.entries.length) return;
1671
- return {
1672
- schema: SESSION_INDEX_SCHEMA,
1673
- files,
1674
- entries
1675
- };
1645
+ const payload = await fs$1.readFile(file, "utf8");
1646
+ const record = parseSessionRecord(JSON.parse(payload));
1647
+ return record && path.basename(file) === `${safeSessionId(record.acpxRecordId)}.json` ? record : void 0;
1676
1648
  } catch {
1677
1649
  return;
1678
1650
  }
1679
1651
  }
1680
- async function writeSessionIndex(sessionDir, index) {
1681
- await writePrivateJsonFile(sessionIndexPath(sessionDir), {
1682
- schema: SESSION_INDEX_SCHEMA,
1683
- files: index.files.toSorted(),
1684
- entries: index.entries.toSorted((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt))
1685
- });
1686
- }
1687
- async function rebuildSessionIndex(sessionDir) {
1688
- const index = await scanSessionIndex(sessionDir);
1689
- await writeSessionIndex(sessionDir, index);
1690
- return index;
1691
- }
1692
- async function scanSessionIndex(sessionDir) {
1693
- const files = await listSessionFiles(sessionDir).catch((error) => {
1694
- if (error.code === "ENOENT") return [];
1695
- throw error;
1696
- });
1697
- const indexEntries = [];
1698
- for (const file of files) try {
1699
- const payload = await fs$1.readFile(path.join(sessionDir, file), "utf8");
1700
- const parsed = parseSessionRecord(JSON.parse(payload));
1701
- if (!parsed) continue;
1702
- indexEntries.push(toSessionIndexEntry(parsed, file));
1703
- } catch {}
1704
- return {
1705
- schema: SESSION_INDEX_SCHEMA,
1706
- files,
1707
- entries: indexEntries.toSorted((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt))
1708
- };
1709
- }
1710
- async function loadOrRebuildSessionIndex(sessionDir) {
1711
- const files = await listSessionFiles(sessionDir);
1712
- const existing = await readSessionIndex(sessionDir);
1713
- if (existing && existing.files.length === files.length && existing.files.every((file, index) => file === files[index])) return existing;
1714
- return await rebuildSessionIndex(sessionDir);
1715
- }
1716
- async function listSessionFiles(sessionDir) {
1717
- return (await fs$1.readdir(sessionDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".json") && entry.name !== "index.json").map((entry) => entry.name).toSorted();
1718
- }
1719
1652
  //#endregion
1720
1653
  //#region src/session/persistence/repository.ts
1721
1654
  const DEFAULT_HISTORY_LIMIT = 20;
@@ -1729,62 +1662,56 @@ async function ensureSessionDir() {
1729
1662
  mode: 448
1730
1663
  });
1731
1664
  }
1732
- async function loadRecordFromIndexEntry(entry) {
1733
- try {
1734
- const payload = await fs$1.readFile(path.join(sessionBaseDir(), entry.file), "utf8");
1735
- return parseSessionRecord(JSON.parse(payload)) ?? void 0;
1736
- } catch {
1737
- return;
1738
- }
1739
- }
1740
- async function loadSessionIndexEntries(readOnly = false) {
1665
+ async function* sessionRecords(readOnly = false) {
1741
1666
  if (!readOnly) await ensureSessionDir();
1742
- return (await measurePerf("session.index_load", async () => {
1743
- return await (readOnly ? scanSessionIndex : loadOrRebuildSessionIndex)(sessionBaseDir());
1744
- })).entries;
1667
+ yield* scanSessionRecords(sessionBaseDir());
1745
1668
  }
1746
- function matchesSessionEntry(session, normalizedCwd, normalizedName, includeClosed = false) {
1747
- if (session.cwd !== normalizedCwd) return false;
1669
+ function matchesSession(session, agentCommand, normalizedName, includeClosed = false) {
1670
+ if (session.agentCommand !== agentCommand) return false;
1748
1671
  if (!includeClosed && session.closed) return false;
1749
1672
  if (normalizedName == null) return session.name == null;
1750
1673
  return session.name === normalizedName;
1751
1674
  }
1675
+ function isNewer(record, previous) {
1676
+ return !previous || record.lastUsedAt.localeCompare(previous.lastUsedAt) > 0;
1677
+ }
1752
1678
  async function writeSessionRecord(record) {
1753
1679
  await measurePerf("session.write_record", async () => {
1754
1680
  const persisted = serializeSessionRecordForDisk(record);
1755
1681
  assertPersistedKeyPolicy(persisted);
1756
- const file = sessionFilePath(record.acpxRecordId);
1757
- await writePrivateJsonFile(file, persisted);
1758
- const sessionDir = sessionBaseDir();
1759
- const index = await loadOrRebuildSessionIndex(sessionDir);
1760
- const fileName = path.basename(file);
1761
- const entries = index.entries.filter((entry) => entry.file !== fileName);
1762
- entries.push(toSessionIndexEntry(record, fileName));
1763
- await writeSessionIndex(sessionDir, {
1764
- files: [.../* @__PURE__ */ new Set([...index.files.filter((entry) => entry !== fileName), fileName])],
1765
- entries
1766
- });
1682
+ await writePrivateJsonFile(sessionFilePath(record.acpxRecordId), persisted);
1767
1683
  });
1768
1684
  }
1769
1685
  async function resolveSessionRecord(sessionId) {
1770
1686
  await ensureSessionDir();
1771
1687
  try {
1772
1688
  const directRecord = await measurePerf("session.resolve_direct", async () => await readSessionRecord(sessionId));
1773
- if (directRecord) return directRecord;
1689
+ if (directRecord?.acpxRecordId === sessionId) return directRecord;
1774
1690
  } catch {}
1775
- const entries = await loadSessionIndexEntries();
1776
- const exactEntries = entries.filter((entry) => entry.acpxRecordId === sessionId || entry.acpSessionId === sessionId);
1777
- const exactRecords = (await Promise.all(exactEntries.map((entry) => loadRecordFromIndexEntry(entry)))).filter((entry) => Boolean(entry));
1691
+ const { exactRecords, suffixRecords } = await findSessionIdMatches(sessionId);
1778
1692
  if (exactRecords.length === 1) return exactRecords[0];
1779
1693
  if (exactRecords.length > 1) throw new SessionResolutionError(`Multiple sessions match id: ${sessionId}`);
1780
- const suffixEntries = entries.filter((entry) => entry.acpxRecordId.endsWith(sessionId) || entry.acpSessionId.endsWith(sessionId));
1781
- const suffixRecords = (await Promise.all(suffixEntries.map((entry) => loadRecordFromIndexEntry(entry)))).filter((entry) => Boolean(entry));
1782
1694
  if (suffixRecords.length === 1) return suffixRecords[0];
1783
1695
  if (suffixRecords.length > 1) throw new SessionResolutionError(`Session id is ambiguous: ${sessionId}`);
1784
1696
  incrementPerfCounter("session.resolve_miss");
1785
1697
  throw new SessionNotFoundError(sessionId);
1786
1698
  }
1787
- /** Reads a canonical record without creating directories or rebuilding the lookup index. */
1699
+ async function findSessionIdMatches(sessionId) {
1700
+ const exactRecords = [];
1701
+ const suffixRecords = [];
1702
+ for await (const record of sessionRecords()) {
1703
+ if (record.acpxRecordId === sessionId || record.acpSessionId === sessionId) retainMatch(exactRecords, record);
1704
+ if (record.acpxRecordId.endsWith(sessionId) || record.acpSessionId.endsWith(sessionId)) retainMatch(suffixRecords, record);
1705
+ }
1706
+ return {
1707
+ exactRecords,
1708
+ suffixRecords
1709
+ };
1710
+ }
1711
+ function retainMatch(matches, record) {
1712
+ if (matches.length < 2) matches.push(record);
1713
+ }
1714
+ /** Reads one canonical record without creating directories or scanning other sessions. */
1788
1715
  async function readSessionRecord(sessionId) {
1789
1716
  let payload;
1790
1717
  try {
@@ -1794,22 +1721,24 @@ async function readSessionRecord(sessionId) {
1794
1721
  throw error;
1795
1722
  }
1796
1723
  try {
1797
- return parseSessionRecord(JSON.parse(payload)) ?? void 0;
1724
+ const record = parseSessionRecord(JSON.parse(payload));
1725
+ return record?.acpxRecordId === sessionId ? record : void 0;
1798
1726
  } catch {
1799
1727
  return;
1800
1728
  }
1801
1729
  }
1802
- function hasGitDirectory(dir) {
1730
+ function hasGitMarker(dir) {
1803
1731
  const gitPath = path.join(dir, ".git");
1804
1732
  try {
1805
- return statSync(gitPath).isDirectory();
1733
+ const marker = statSync(gitPath);
1734
+ return marker.isDirectory() || marker.isFile();
1806
1735
  } catch {
1807
1736
  return false;
1808
1737
  }
1809
1738
  }
1810
1739
  function isWithinBoundary(boundary, target) {
1811
1740
  const relative = path.relative(boundary, target);
1812
- return relative.length === 0 || !relative.startsWith("..") && !path.isAbsolute(relative);
1741
+ return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
1813
1742
  }
1814
1743
  function absolutePath(value) {
1815
1744
  return path.resolve(value);
@@ -1818,7 +1747,7 @@ function findGitRepositoryRoot(startDir) {
1818
1747
  let current = absolutePath(startDir);
1819
1748
  const root = path.parse(current).root;
1820
1749
  for (;;) {
1821
- if (hasGitDirectory(current)) return current;
1750
+ if (hasGitMarker(current)) return current;
1822
1751
  if (current === root) return;
1823
1752
  const parent = path.dirname(current);
1824
1753
  if (parent === current) return;
@@ -1834,42 +1763,49 @@ function isoNow() {
1834
1763
  return (/* @__PURE__ */ new Date()).toISOString();
1835
1764
  }
1836
1765
  async function listSessions() {
1837
- await ensureSessionDir();
1838
- const entries = await loadSessionIndexEntries();
1839
- const records = [];
1840
- for (const entry of entries) {
1841
- const parsed = await loadRecordFromIndexEntry(entry);
1842
- if (parsed) records.push(parsed);
1843
- }
1844
- records.sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));
1845
- return records;
1766
+ return await collectSessionRecords();
1846
1767
  }
1847
1768
  async function listSessionsForAgent(agentCommand) {
1848
- const entries = (await loadSessionIndexEntries()).filter((session) => session.agentCommand === agentCommand);
1849
- return (await Promise.all(entries.map((entry) => loadRecordFromIndexEntry(entry)))).filter((entry) => Boolean(entry)).toSorted((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));
1769
+ return await collectSessionRecords(agentCommand);
1770
+ }
1771
+ async function collectSessionRecords(agentCommand) {
1772
+ const records = [];
1773
+ for await (const record of sessionRecords()) if (agentCommand === void 0 || record.agentCommand === agentCommand) records.push(record);
1774
+ return records.toSorted((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));
1850
1775
  }
1851
1776
  async function findSession(options) {
1852
1777
  const normalizedCwd = absolutePath(options.cwd);
1853
1778
  const normalizedName = normalizeName(options.name);
1854
- const match = (await loadSessionIndexEntries(options.readOnly)).find((session) => session.agentCommand === options.agentCommand && matchesSessionEntry(session, normalizedCwd, normalizedName, options.includeClosed));
1855
- if (!match) return;
1856
- return await loadRecordFromIndexEntry(match);
1779
+ let match;
1780
+ for await (const record of sessionRecords(options.readOnly)) if (record.cwd === normalizedCwd && matchesSession(record, options.agentCommand, normalizedName, options.includeClosed) && isNewer(record, match)) match = record;
1781
+ return match;
1857
1782
  }
1858
1783
  async function findSessionByDirectoryWalk(options) {
1859
1784
  const normalizedName = normalizeName(options.name);
1785
+ const directories = walkDirectories(options);
1786
+ let match;
1787
+ let distance = Infinity;
1788
+ for await (const record of sessionRecords()) {
1789
+ const candidateDistance = directories.get(record.cwd);
1790
+ if (candidateDistance !== void 0 && matchesSession(record, options.agentCommand, normalizedName) && (candidateDistance < distance || candidateDistance === distance && isNewer(record, match))) {
1791
+ match = record;
1792
+ distance = candidateDistance;
1793
+ }
1794
+ }
1795
+ return match;
1796
+ }
1797
+ function walkDirectories(options) {
1860
1798
  const normalizedStart = absolutePath(options.cwd);
1861
1799
  const normalizedBoundary = absolutePath(options.boundary ?? normalizedStart);
1862
1800
  const walkBoundary = isWithinBoundary(normalizedBoundary, normalizedStart) ? normalizedBoundary : normalizedStart;
1863
- const sessions = (await loadSessionIndexEntries()).filter((session) => session.agentCommand === options.agentCommand);
1801
+ const directories = /* @__PURE__ */ new Map();
1802
+ const walkRoot = path.parse(normalizedStart).root;
1864
1803
  let current = normalizedStart;
1865
- const walkRoot = path.parse(current).root;
1866
- for (;;) {
1867
- const match = sessions.find((session) => matchesSessionEntry(session, current, normalizedName));
1868
- if (match) return await loadRecordFromIndexEntry(match);
1869
- const parent = nextWalkParent(current, walkBoundary, walkRoot);
1870
- if (!parent) return;
1871
- current = parent;
1804
+ while (current) {
1805
+ directories.set(current, directories.size);
1806
+ current = nextWalkParent(current, walkBoundary, walkRoot);
1872
1807
  }
1808
+ return directories;
1873
1809
  }
1874
1810
  function nextWalkParent(current, walkBoundary, walkRoot) {
1875
1811
  if (current === walkBoundary || current === walkRoot) return;
@@ -1885,8 +1821,7 @@ function isSessionStreamFile(fileName, safeId) {
1885
1821
  return fileName === `${safeId}.stream.ndjson` || fileName === `${safeId}.stream.lock` || fileName.startsWith(segmentPrefix) && /^\d+\.ndjson$/.test(fileName.slice(segmentPrefix.length));
1886
1822
  }
1887
1823
  async function pruneSessions(options = {}) {
1888
- await ensureSessionDir();
1889
- const records = await loadPrunableRecords((await loadSessionIndexEntries()).filter((entry) => isPruneCandidate(entry, options.agentCommand)), options.before ?? (options.olderThanMs != null ? new Date(Date.now() - options.olderThanMs) : void 0), options.agentCommand);
1824
+ const records = await loadPrunableRecords(options.before ?? (options.olderThanMs != null ? new Date(Date.now() - options.olderThanMs) : void 0), options.agentCommand);
1890
1825
  if (options.dryRun) return {
1891
1826
  pruned: records,
1892
1827
  bytesFreed: 0,
@@ -1899,7 +1834,6 @@ async function pruneSessions(options = {}) {
1899
1834
  dirEntries = await fs$1.readdir(sessionDir);
1900
1835
  } catch {}
1901
1836
  for (const record of records) bytesFreed += await pruneSessionFiles(record, sessionDir, dirEntries, options.includeHistory === true);
1902
- await rebuildSessionIndex(sessionDir).catch(() => {});
1903
1837
  return {
1904
1838
  pruned: records,
1905
1839
  bytesFreed,
@@ -1909,14 +1843,11 @@ async function pruneSessions(options = {}) {
1909
1843
  function isPruneCandidate(record, agentCommand) {
1910
1844
  return record.closed === true && (!agentCommand || record.agentCommand === agentCommand);
1911
1845
  }
1912
- async function loadPrunableRecords(entries, cutoff, agentCommand) {
1846
+ async function loadPrunableRecords(cutoff, agentCommand) {
1913
1847
  const records = [];
1914
1848
  const cutoffIso = cutoff?.toISOString();
1915
- for (const entry of entries) {
1916
- const record = await loadRecordFromIndexEntry(entry);
1917
- if (record && isPruneCandidate(record, agentCommand) && isBeforeCutoff(record, cutoffIso)) records.push(record);
1918
- }
1919
- return records;
1849
+ for await (const record of sessionRecords()) if (isPruneCandidate(record, agentCommand) && isBeforeCutoff(record, cutoffIso)) records.push(record);
1850
+ return records.toSorted((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));
1920
1851
  }
1921
1852
  function isBeforeCutoff(record, cutoffIso) {
1922
1853
  return !cutoffIso || closedAtOrLastUsedAt(record) < cutoffIso;
@@ -1974,12 +1905,12 @@ function isAudioBlock(value) {
1974
1905
  }
1975
1906
  function isResourceLinkBlock(value) {
1976
1907
  const record = asRecord$1(value);
1977
- return record?.type === "resource_link" && isNonEmptyString(record.uri) && (record.title === void 0 || typeof record.title === "string") && (record.name === void 0 || typeof record.name === "string");
1908
+ return record?.type === "resource_link" && isNonEmptyString(record.uri) && (record.title == null || typeof record.title === "string") && typeof record.name === "string";
1978
1909
  }
1979
1910
  function isResourcePayload(value) {
1980
1911
  const record = asRecord$1(value);
1981
1912
  if (!record || !isNonEmptyString(record.uri)) return false;
1982
- return record.text === void 0 || typeof record.text === "string";
1913
+ return typeof record.text === "string" || typeof record.blob === "string";
1983
1914
  }
1984
1915
  function isResourceBlock(value) {
1985
1916
  const record = asRecord$1(value);
@@ -2022,12 +1953,12 @@ function validateAudioContentBlock(record, index) {
2022
1953
  }
2023
1954
  function validateResourceLinkContentBlock(record, index) {
2024
1955
  if (!isNonEmptyString(record.uri)) return `prompt[${index}] resource_link block must include a non-empty uri`;
2025
- if (record.title !== void 0 && typeof record.title !== "string") return `prompt[${index}] resource_link block title must be a string when present`;
2026
- if (record.name !== void 0 && typeof record.name !== "string") return `prompt[${index}] resource_link block name must be a string when present`;
1956
+ if (record.title != null && typeof record.title !== "string") return `prompt[${index}] resource_link block title must be a string or null when present`;
1957
+ if (typeof record.name !== "string") return `prompt[${index}] resource_link block must include a string name`;
2027
1958
  }
2028
1959
  function validateResourceContentBlock(record, index) {
2029
1960
  if (!asRecord$1(record.resource)) return `prompt[${index}] resource block must include a resource object`;
2030
- return isResourcePayload(record.resource) ? void 0 : `prompt[${index}] resource block resource must include a non-empty uri and optional text`;
1961
+ return isResourcePayload(record.resource) ? void 0 : `prompt[${index}] resource block resource must include a non-empty uri and a string text or blob field`;
2031
1962
  }
2032
1963
  function getContentBlockValidationError(value, index) {
2033
1964
  const record = asRecord$1(value);
@@ -2112,6 +2043,574 @@ function resourceBlockDisplayText(block) {
2112
2043
  return "text" in block.resource && typeof block.resource.text === "string" ? block.resource.text : block.resource.uri;
2113
2044
  }
2114
2045
  //#endregion
2046
+ //#region src/process-identity.ts
2047
+ const IDENTITY_QUERY_TIMEOUT_MS$1 = 2e3;
2048
+ const LINUX_PROCESS_QUERY = `
2049
+ const fs = require('node:fs/promises');
2050
+ const [targetPid, observerPid] = process.argv.slice(1);
2051
+ if (process.ppid !== Number(observerPid)) process.exit(1);
2052
+ const timeNamespace = (pid) => fs.readlink('/proc/' + pid + '/ns/time').catch((error) => {
2053
+ if (error.code === 'ENOENT') return 'unsupported';
2054
+ throw error;
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
+ };
2089
+ Promise.all([
2090
+ fs.readFile('/proc/sys/kernel/random/boot_id', 'utf8'),
2091
+ fs.readlink('/proc/self/ns/pid'),
2092
+ timeNamespace('self'),
2093
+ fs.readlink('/proc/' + observerPid + '/ns/pid'),
2094
+ timeNamespace(observerPid),
2095
+ fs.readFile('/proc/self/stat', 'utf8'),
2096
+ targets(),
2097
+ ]).then(([bootId, pidNamespace, timeNamespace, observerPidNamespace,
2098
+ observerTimeNamespace, selfStat, target]) => {
2099
+ process.stdout.write(JSON.stringify({bootId: bootId.trim(), pidNamespace,
2100
+ timeNamespace, observerPidNamespace, observerTimeNamespace,
2101
+ helperPid: process.pid, selfStat, ...target}));
2102
+ }).catch(() => { process.exitCode = 1; });
2103
+ `;
2104
+ const POSIX_MONTHS = [
2105
+ "Jan",
2106
+ "Feb",
2107
+ "Mar",
2108
+ "Apr",
2109
+ "May",
2110
+ "Jun",
2111
+ "Jul",
2112
+ "Aug",
2113
+ "Sep",
2114
+ "Oct",
2115
+ "Nov",
2116
+ "Dec"
2117
+ ];
2118
+ let ownIdentity;
2119
+ let ownIdentityPending;
2120
+ function parseProcessBirthIdentity(raw) {
2121
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
2122
+ const identity = raw;
2123
+ if (identity.kind === "linux-proc") return parseLinuxIdentity(identity);
2124
+ if (identity.kind !== "posix-lstart" && identity.kind !== "windows-creation") return;
2125
+ if (!isCanonicalBirth(identity.value, identity.kind)) return;
2126
+ return {
2127
+ kind: identity.kind,
2128
+ value: identity.value
2129
+ };
2130
+ }
2131
+ function isCanonicalBirth(value, kind) {
2132
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3,7}Z$/.test(value) || !Number.isFinite(Date.parse(value))) return false;
2133
+ const canonical = new Date(value).toISOString();
2134
+ return canonical.slice(0, 19) === value.slice(0, 19) && (kind === "posix-lstart" ? value === canonical && value.endsWith(".000Z") : /\.\d{7}Z$/.test(value));
2135
+ }
2136
+ function parseLinuxScope(raw) {
2137
+ const { bootId, pidNamespace, timeNamespace } = raw;
2138
+ if (typeof bootId !== "string" || !/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/.test(bootId) || typeof pidNamespace !== "string" || !/^pid:\[[1-9]\d*\]$/.test(pidNamespace) || !isTimeNamespace(timeNamespace)) return;
2139
+ return {
2140
+ bootId,
2141
+ pidNamespace,
2142
+ timeNamespace
2143
+ };
2144
+ }
2145
+ function isTimeNamespace(value) {
2146
+ return typeof value === "string" && (value === "unsupported" || /^time:\[[1-9]\d*\]$/.test(value));
2147
+ }
2148
+ function isStartTicks(value) {
2149
+ return typeof value === "string" && /^(?:0|[1-9]\d*)$/.test(value);
2150
+ }
2151
+ function parseLinuxIdentity(raw) {
2152
+ const scope = parseLinuxScope(raw);
2153
+ if (!scope || !isStartTicks(raw.startTicks)) return;
2154
+ return {
2155
+ kind: "linux-proc",
2156
+ ...scope,
2157
+ startTicks: raw.startTicks
2158
+ };
2159
+ }
2160
+ function compareLinuxScope(expected, observed) {
2161
+ if (expected.bootId !== observed.bootId) return "different";
2162
+ if (expected.pidNamespace !== observed.pidNamespace || expected.timeNamespace !== observed.timeNamespace) return "unknown";
2163
+ return "matching";
2164
+ }
2165
+ function compareLinuxIdentity(expected, observed) {
2166
+ const scope = compareLinuxScope(expected, observed);
2167
+ return scope === "matching" ? expected.startTicks === observed.startTicks ? "matching" : "different" : scope;
2168
+ }
2169
+ function compareProcessBirthIdentity(expected, observed) {
2170
+ if (!expected || expected.kind !== observed.kind) return "unknown";
2171
+ if (expected.kind === "linux-proc" && observed.kind === "linux-proc") return compareLinuxIdentity(expected, observed);
2172
+ if ("value" in expected && "value" in observed) return expected.value === observed.value ? "matching" : "different";
2173
+ return "unknown";
2174
+ }
2175
+ function parseLinuxStat(raw) {
2176
+ if (typeof raw !== "string") return;
2177
+ const prefix = /^(\d+) \(/.exec(raw);
2178
+ const close = raw.lastIndexOf(")");
2179
+ if (!prefix || close < 0) return;
2180
+ const pid = Number(prefix[1]);
2181
+ const fields = raw.slice(close + 2).trim().split(/\s+/u);
2182
+ const startTicks = fields[19];
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) ? {
2187
+ pid,
2188
+ parentPid,
2189
+ groupPid,
2190
+ startTicks
2191
+ } : void 0;
2192
+ }
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 = [
2198
+ "--input-type=commonjs",
2199
+ "-e",
2200
+ LINUX_PROCESS_QUERY,
2201
+ String(pid),
2202
+ String(process.pid)
2203
+ ];
2204
+ if (pid === "all") args.push(JSON.stringify(requiredPids));
2205
+ const output = await runTimedExecFile(process.execPath, args, {
2206
+ timeoutMs,
2207
+ maxBufferBytes: pid === "all" ? PROCESS_HELPER_MAX_BUFFER_BYTES : 8192,
2208
+ env: {
2209
+ ...process.env,
2210
+ NODE_OPTIONS: ""
2211
+ }
2212
+ });
2213
+ return JSON.parse(output);
2214
+ }
2215
+ function verifiedLinuxScope(raw) {
2216
+ const scope = parseLinuxScope(raw);
2217
+ const self = parseLinuxStat(raw.selfStat);
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;
2225
+ const target = parseLinuxStat(raw.targetStat);
2226
+ return {
2227
+ scope,
2228
+ ...target?.pid === pid ? { identity: {
2229
+ kind: "linux-proc",
2230
+ ...scope,
2231
+ startTicks: target.startTicks
2232
+ } } : {}
2233
+ };
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
+ }
2309
+ function isPositivePid(pid) {
2310
+ return Number.isSafeInteger(pid) && pid > 0;
2311
+ }
2312
+ function windowsPowerShellPath() {
2313
+ const systemRoot = process.env.SystemRoot;
2314
+ if (!systemRoot || !path.win32.isAbsolute(systemRoot)) throw new Error("Windows system directory is unavailable");
2315
+ return path.win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
2316
+ }
2317
+ function windowsProcessQuery(pid) {
2318
+ return [
2319
+ "$ErrorActionPreference = 'Stop'",
2320
+ `Get-CimInstance -ClassName Win32_Process -Property ProcessId,ParentProcessId,CreationDate${pid === void 0 ? "" : ` -Filter 'ProcessId = ${pid}'`} | ForEach-Object {`,
2321
+ "if ($null -ne $_.CreationDate) {",
2322
+ "'{0} {1} 0 S {2}' -f $_.ProcessId,$_.ParentProcessId,$_.CreationDate.ToUniversalTime().ToString('o')",
2323
+ "}",
2324
+ "}"
2325
+ ].join("\n");
2326
+ }
2327
+ function posixBirth(value) {
2328
+ const match = /^\w{3}\s+(\w{3})\s+(\d{1,2})\s+(\d{2}):(\d{2}):(\d{2})\s+(\d{4})$/.exec(value);
2329
+ if (!match) return;
2330
+ const month = POSIX_MONTHS.indexOf(match[1]);
2331
+ if (month < 0) return;
2332
+ const valueUtc = `${match[6]}-${String(month + 1).padStart(2, "0")}-${match[2].padStart(2, "0")}T${match[3]}:${match[4]}:${match[5]}.000Z`;
2333
+ return isCanonicalBirth(valueUtc, "posix-lstart") ? valueUtc : void 0;
2334
+ }
2335
+ function parseProcessTable(output) {
2336
+ const table = /* @__PURE__ */ new Map();
2337
+ for (const line of output.split("\n")) {
2338
+ const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(line);
2339
+ if (!match || match[4].startsWith("Z")) continue;
2340
+ const pid = Number(match[1]);
2341
+ const birth = processTableBirth(match[5]);
2342
+ if (Number.isSafeInteger(pid) && pid > 0 && birth) table.set(pid, {
2343
+ pid,
2344
+ parentPid: Number(match[2]),
2345
+ groupPid: Number(match[3]),
2346
+ birth: processBirthIdentity(birth)
2347
+ });
2348
+ }
2349
+ return table;
2350
+ }
2351
+ function processTableBirth(value) {
2352
+ if (process.platform !== "win32") return posixBirth(value);
2353
+ return isCanonicalBirth(value, "windows-creation") ? value : void 0;
2354
+ }
2355
+ async function readProcessTable(timeoutMs, pid, requiredPids = []) {
2356
+ if (pid !== void 0 && (!Number.isSafeInteger(pid) || pid <= 0)) throw new Error("Process identity requires a positive PID");
2357
+ if (process.platform === "win32") return parseProcessTable(await runTimedExecFile(windowsPowerShellPath(), [
2358
+ "-NoLogo",
2359
+ "-NoProfile",
2360
+ "-NonInteractive",
2361
+ "-Command",
2362
+ windowsProcessQuery(pid)
2363
+ ], {
2364
+ timeoutMs,
2365
+ windowsHide: true
2366
+ }));
2367
+ if (process.platform === "linux") return await readLinuxProcessTable(timeoutMs, requiredPids, pid);
2368
+ return parseProcessTable(await runTimedExecFile("ps", [
2369
+ ...pid === void 0 ? ["-e"] : ["-p", String(pid)],
2370
+ "-o",
2371
+ "pid=,ppid=,pgid=,stat=,lstart="
2372
+ ], {
2373
+ timeoutMs,
2374
+ env: {
2375
+ ...process.env,
2376
+ LC_ALL: "C",
2377
+ TZ: "UTC"
2378
+ }
2379
+ }));
2380
+ }
2381
+ async function probeProcessIdentity(pid, timeoutMs = IDENTITY_QUERY_TIMEOUT_MS$1) {
2382
+ if (!isPositivePid(pid)) return { state: "unknown" };
2383
+ if (isProcessDefinitelyDead(pid)) return { state: "dead" };
2384
+ try {
2385
+ const identity = await readNativeIdentity(pid, timeoutMs);
2386
+ if (identity) return {
2387
+ state: "alive",
2388
+ identity
2389
+ };
2390
+ } catch {}
2391
+ return { state: isProcessDefinitelyDead(pid) ? "dead" : "unknown" };
2392
+ }
2393
+ async function readNativeIdentity(pid, timeoutMs) {
2394
+ if (process.platform === "linux") return (await readLinuxObservation(pid, timeoutMs))?.identity;
2395
+ return (await readProcessTable(timeoutMs, pid)).get(pid)?.birth;
2396
+ }
2397
+ function processBirthIdentity(value) {
2398
+ return {
2399
+ kind: process.platform === "win32" ? "windows-creation" : "posix-lstart",
2400
+ value
2401
+ };
2402
+ }
2403
+ function nativeIdentityKind() {
2404
+ if (process.platform === "linux") return "linux-proc";
2405
+ return process.platform === "win32" ? "windows-creation" : "posix-lstart";
2406
+ }
2407
+ function comparisonToIncarnation(comparison) {
2408
+ return comparison === "different" ? "gone" : comparison;
2409
+ }
2410
+ async function observeLinuxIncarnation(pid, expected, timeoutMs) {
2411
+ const observed = await readLinuxObservation(pid, timeoutMs).catch(() => void 0);
2412
+ if (!observed) return "unknown";
2413
+ const scope = compareLinuxScope(expected, observed.scope);
2414
+ if (scope !== "matching") return comparisonToIncarnation(scope);
2415
+ if (isProcessDefinitelyDead(pid)) return "gone";
2416
+ return observed.identity ? comparisonToIncarnation(compareProcessBirthIdentity(expected, observed.identity)) : "unknown";
2417
+ }
2418
+ async function observeProcessIncarnation(pid, expected, timeoutMs = IDENTITY_QUERY_TIMEOUT_MS$1) {
2419
+ if (!isPositivePid(pid)) return "unknown";
2420
+ if (expected?.kind === "linux-proc") return process.platform === "linux" ? await observeLinuxIncarnation(pid, expected, timeoutMs) : "unknown";
2421
+ if (expected && expected.kind !== nativeIdentityKind()) return "unknown";
2422
+ return await observeTimestampIncarnation(pid, expected, timeoutMs);
2423
+ }
2424
+ async function observeTimestampIncarnation(pid, expected, timeoutMs) {
2425
+ if (isProcessDefinitelyDead(pid)) return "gone";
2426
+ if (!expected) return "unknown";
2427
+ const observed = await probeProcessIdentity(pid, timeoutMs);
2428
+ if (observed.state === "dead") return "gone";
2429
+ return observed.state === "alive" ? comparisonToIncarnation(compareProcessBirthIdentity(expected, observed.identity)) : "unknown";
2430
+ }
2431
+ async function getOwnProcessIdentity() {
2432
+ if (ownIdentity) return ownIdentity;
2433
+ ownIdentityPending ??= probeProcessIdentity(process.pid).then((probe) => {
2434
+ if (probe.state === "alive") ownIdentity = probe.identity;
2435
+ return ownIdentity;
2436
+ }).finally(() => {
2437
+ ownIdentityPending = void 0;
2438
+ });
2439
+ return await ownIdentityPending;
2440
+ }
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
2574
+ //#region src/session/lock-owner.ts
2575
+ const OBSERVATION_REUSE_MS = 1e3;
2576
+ const IDENTITY_QUERY_TIMEOUT_MS = 1e3;
2577
+ function lockOwnerPid(value) {
2578
+ if (!value || typeof value !== "object" || !("pid" in value)) return;
2579
+ const pid = value.pid;
2580
+ return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
2581
+ }
2582
+ function canReuseObservation(preserved, pid, identity, fresh) {
2583
+ return !fresh && preserved?.pid === pid && compareProcessBirthIdentity(preserved.identity, identity) === "matching" && performance.now() - preserved.checkedAt < OBSERVATION_REUSE_MS;
2584
+ }
2585
+ async function createLockOwner(options = {}) {
2586
+ const { signal, capturedIdentity } = options;
2587
+ signal?.throwIfAborted();
2588
+ const processIdentity = capturedIdentity === void 0 ? await getOwnProcessIdentity() : capturedIdentity.processIdentity;
2589
+ signal?.throwIfAborted();
2590
+ let preserved;
2591
+ return {
2592
+ payload: {
2593
+ pid: process.pid,
2594
+ processIdentity
2595
+ },
2596
+ async hasExited(payload, fresh, signal) {
2597
+ signal?.throwIfAborted();
2598
+ const pid = lockOwnerPid(payload);
2599
+ if (!pid) return false;
2600
+ const identity = parseProcessBirthIdentity(payload.processIdentity);
2601
+ if (identity && canReuseObservation(preserved, pid, identity, fresh)) return false;
2602
+ const incarnation = await observeProcessIncarnation(pid, identity, IDENTITY_QUERY_TIMEOUT_MS);
2603
+ signal?.throwIfAborted();
2604
+ if (identity && incarnation !== "gone") preserved = {
2605
+ pid,
2606
+ identity,
2607
+ checkedAt: performance.now()
2608
+ };
2609
+ return incarnation === "gone";
2610
+ }
2611
+ };
2612
+ }
2613
+ //#endregion
2115
2614
  //#region src/session/queue/paths.ts
2116
2615
  function shortHash(value, length) {
2117
2616
  return createHash("sha256").update(value).digest("hex").slice(0, length);
@@ -2135,6 +2634,127 @@ function queueSocketPath(sessionId, homeDir = os.homedir()) {
2135
2634
  return path.join(queueSocketBaseDir(homeDir) ?? "/tmp", `${key}.sock`);
2136
2635
  }
2137
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
2138
2758
  //#region src/session/queue/lease-mutation.ts
2139
2759
  const GUARD_WAIT_MS = 2e3;
2140
2760
  const localMutations = /* @__PURE__ */ new Map();
@@ -2157,10 +2777,7 @@ async function canonicalLeasePath(sessionId) {
2157
2777
  throw error;
2158
2778
  }
2159
2779
  }
2160
- function guardOwnerHasExited({ payload }) {
2161
- return payload !== null && typeof payload === "object" && "pid" in payload && typeof payload.pid === "number" && isProcessDefinitelyDead(payload.pid);
2162
- }
2163
- async function acquireGuard(lockPath) {
2780
+ async function acquireGuard(lockPath, owner) {
2164
2781
  return await acquireFileLock(lockPath, {
2165
2782
  managerKey: "acpx.queue-lease-mutation",
2166
2783
  lockPath: `${lockPath}.guard`,
@@ -2173,9 +2790,9 @@ async function acquireGuard(lockPath) {
2173
2790
  randomize: false
2174
2791
  },
2175
2792
  staleRecovery: "remove-if-unchanged",
2176
- payload: () => ({ pid: process.pid }),
2177
- shouldReclaim: guardOwnerHasExited,
2178
- shouldRemoveStaleLock: guardOwnerHasExited
2793
+ payload: () => owner.payload,
2794
+ shouldReclaim: ({ payload }) => owner.hasExited(payload),
2795
+ shouldRemoveStaleLock: ({ payload }) => owner.hasExited(payload, true)
2179
2796
  });
2180
2797
  }
2181
2798
  function matchesReservation(value, expected) {
@@ -2193,7 +2810,7 @@ async function rollbackReservation(lockPath, expected) {
2193
2810
  } catch {
2194
2811
  return;
2195
2812
  }
2196
- if (matchesReservation(value, expected)) await fs$1.unlink(lockPath);
2813
+ if (matchesReservation(value, expected) && !hasQueueRetirementReceipt(value)) await fs$1.unlink(lockPath);
2197
2814
  } catch (error) {
2198
2815
  if (error.code !== "ENOENT") throw error;
2199
2816
  }
@@ -2202,7 +2819,7 @@ async function settleReceipt(lockPath, receipt) {
2202
2819
  if (receipt.reservation) {
2203
2820
  if (!await receipt.guard.verifyStillHeld()) {
2204
2821
  await receipt.guard.release();
2205
- receipt.guard = await acquireGuard(lockPath);
2822
+ receipt.guard = await acquireGuard(lockPath, receipt.owner);
2206
2823
  }
2207
2824
  await rollbackReservation(lockPath, receipt.reservation);
2208
2825
  receipt.reservation = void 0;
@@ -2241,7 +2858,8 @@ async function settlePendingQueueLeaseGuard(sessionId) {
2241
2858
  if (!localMutations.has(lockPath)) return;
2242
2859
  await locallySerialized(lockPath, async (state) => await settleFailedGuard(lockPath, state));
2243
2860
  }
2244
- async function withQueueLeaseMutation(sessionId, mutate, reservation) {
2861
+ async function withQueueLeaseMutation(sessionId, mutate, options = {}) {
2862
+ const { reservation, capturedIdentity } = options;
2245
2863
  await fs$1.mkdir(path.dirname(queueLockFilePath(sessionId)), {
2246
2864
  recursive: true,
2247
2865
  mode: 448
@@ -2255,7 +2873,8 @@ async function withQueueLeaseMutation(sessionId, mutate, reservation) {
2255
2873
  const publishedReservation = () => reservation?.published ? expected : void 0;
2256
2874
  return await locallySerialized(lockPath, async (state) => {
2257
2875
  await settleFailedGuard(lockPath, state);
2258
- const guard = await acquireGuard(lockPath);
2876
+ const owner = await createLockOwner({ capturedIdentity });
2877
+ const guard = await acquireGuard(lockPath, owner);
2259
2878
  let outcome;
2260
2879
  try {
2261
2880
  outcome = {
@@ -2271,6 +2890,7 @@ async function withQueueLeaseMutation(sessionId, mutate, reservation) {
2271
2890
  const published = publishedReservation();
2272
2891
  const receipt = {
2273
2892
  guard,
2893
+ owner,
2274
2894
  reservation: outcome.ok ? void 0 : published
2275
2895
  };
2276
2896
  try {
@@ -2294,7 +2914,8 @@ function parseQueueOwnerRecord(raw) {
2294
2914
  if (!raw || typeof raw !== "object") return null;
2295
2915
  const record = raw;
2296
2916
  if (!hasValidQueueOwnerRecordFields(record)) return null;
2297
- return {
2917
+ const processIdentity = parseProcessBirthIdentity(record.processIdentity);
2918
+ const owner = {
2298
2919
  pid: record.pid,
2299
2920
  sessionId: record.sessionId,
2300
2921
  socketPath: record.socketPath,
@@ -2302,10 +2923,13 @@ function parseQueueOwnerRecord(raw) {
2302
2923
  heartbeatAt: record.heartbeatAt,
2303
2924
  ownerGeneration: record.ownerGeneration,
2304
2925
  queueDepth: record.queueDepth,
2926
+ ...processIdentity ? { processIdentity } : {},
2305
2927
  ...parseQueueOwnerCapabilities(record),
2306
2928
  ...typeof record.mcpConfigPath === "string" ? { mcpConfigPath: record.mcpConfigPath } : {},
2307
2929
  ...typeof record.mcpConfigFingerprint === "string" ? { mcpConfigFingerprint: record.mcpConfigFingerprint } : {}
2308
2930
  };
2931
+ if (hasQueueRetirementReceipt(record)) owner.retirement = parseQueueRetirementReceipt(record.retirement, owner);
2932
+ return owner;
2309
2933
  }
2310
2934
  function parseQueueOwnerCapabilities(record) {
2311
2935
  return {
@@ -2367,29 +2991,29 @@ async function removeSocketFile(socketPath) {
2367
2991
  }
2368
2992
  }
2369
2993
  async function waitForProcessExit(pid, timeoutMs, hasExited) {
2370
- const deadline = Date.now() + Math.max(0, timeoutMs);
2371
- while (Date.now() <= deadline) {
2994
+ const deadline = performance.now() + Math.max(0, timeoutMs);
2995
+ while (performance.now() <= deadline) {
2372
2996
  if (hasExited(pid)) return true;
2373
2997
  await waitMs(PROCESS_POLL_MS);
2374
2998
  }
2375
2999
  return hasExited(pid);
2376
3000
  }
2377
- async function cleanupQueueOwnerFiles(sessionId, socketPath, isCurrent) {
3001
+ async function cleanupQueueOwnerFiles(sessionId, socketPath, isCurrent, capturedIdentity, deadline = retirementDeadline()) {
2378
3002
  await withQueueLeaseMutation(sessionId, async () => {
2379
- await cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent);
2380
- });
3003
+ await cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent, deadline);
3004
+ }, { capturedIdentity });
2381
3005
  }
2382
- async function cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent) {
3006
+ async function cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent, deadline = retirementDeadline()) {
2383
3007
  if (!await isCurrent()) return;
3008
+ await assertQueueRetirementComplete((await readQueueOwnerRecord(sessionId))?.retirement, deadline);
2384
3009
  await removeSocketFile(socketPath).catch(() => {});
2385
3010
  if (!await isCurrent()) return;
2386
3011
  await fs$1.unlink(queueLockFilePath(sessionId)).catch((error) => {
2387
3012
  if (error.code !== "ENOENT") throw error;
2388
3013
  });
2389
3014
  }
2390
- async function ownsQueueLease(owner, requireStale = false) {
2391
- const current = await readQueueOwnerRecord(owner.sessionId);
2392
- return matchesQueueOwner(current, owner) && (!requireStale || isQueueOwnerHeartbeatStale(current));
3015
+ async function ownsQueueLease(owner) {
3016
+ return matchesQueueOwner(await readQueueOwnerRecord(owner.sessionId), owner);
2393
3017
  }
2394
3018
  function matchesQueueOwner(current, expected) {
2395
3019
  return current?.sessionId === expected.sessionId && current.pid === expected.pid && current.ownerGeneration === expected.ownerGeneration;
@@ -2397,6 +3021,9 @@ function matchesQueueOwner(current, expected) {
2397
3021
  function ownerIsAlive(owner) {
2398
3022
  return owner.pid === process.pid || isProcessAlive(owner.pid);
2399
3023
  }
3024
+ function ownerAcceptsWork(owner) {
3025
+ return owner.retirement === void 0 && ownerIsAlive(owner) && !isQueueOwnerHeartbeatStale(owner);
3026
+ }
2400
3027
  async function readQueueOwnerRecord(sessionId) {
2401
3028
  const lockPath = queueLockFilePath(sessionId);
2402
3029
  try {
@@ -2422,10 +3049,26 @@ function dispatchSignal(pid, signal) {
2422
3049
  return false;
2423
3050
  }
2424
3051
  }
2425
- async function terminateWithDispatch(pid, dispatch, hasExited) {
3052
+ async function dispatchQueueOwnerSignal(pid, signal, deadline) {
3053
+ if (process.platform !== "win32") return dispatchSignal(pid, signal);
3054
+ const systemRoot = process.env.SystemRoot;
3055
+ if (!systemRoot || !path.win32.isAbsolute(systemRoot)) throw new Error("Windows queue-owner cleanup requires an absolute SystemRoot directory");
3056
+ const taskkill = path.win32.join(systemRoot, "System32", "taskkill.exe");
3057
+ await runTimedExecFile(taskkill, [
3058
+ "/pid",
3059
+ String(pid),
3060
+ "/T",
3061
+ "/F"
3062
+ ], {
3063
+ timeoutMs: retirementTimeRemaining(deadline, PROCESS_SIGTERM_GRACE_MS),
3064
+ windowsHide: true
3065
+ });
3066
+ return true;
3067
+ }
3068
+ async function terminateWithDispatch(pid, dispatch, hasExited, deadline = Infinity) {
2426
3069
  for (const [signal, graceMs] of [["SIGTERM", PROCESS_SIGTERM_GRACE_MS], ["SIGKILL", PROCESS_SIGKILL_GRACE_MS]]) {
2427
3070
  if (!await dispatch(signal)) return false;
2428
- if (await waitForProcessExit(pid, graceMs, hasExited)) return true;
3071
+ if (await waitForProcessExit(pid, Math.min(graceMs, deadline - performance.now()), hasExited)) return true;
2429
3072
  }
2430
3073
  return false;
2431
3074
  }
@@ -2433,14 +3076,15 @@ async function resolveUsableQueueOwner(sessionId, owner) {
2433
3076
  await settlePendingQueueLeaseGuard(sessionId);
2434
3077
  const observed = await readQueueOwnerRecord(sessionId);
2435
3078
  if (!matchesQueueOwner(observed, owner)) return;
2436
- if (ownerIsAlive(observed) && !isQueueOwnerHeartbeatStale(observed)) return observed;
3079
+ if (ownerAcceptsWork(observed)) return observed;
2437
3080
  await terminateQueueOwnerForSession(sessionId, observed, true);
2438
3081
  const current = await readQueueOwnerRecord(sessionId);
2439
- return matchesQueueOwner(current, observed) && ownerIsAlive(current) && !isQueueOwnerHeartbeatStale(current) ? current : void 0;
3082
+ return matchesQueueOwner(current, observed) && ownerAcceptsWork(current) ? current : void 0;
2440
3083
  }
2441
3084
  async function tryAcquireQueueOwnerLease(sessionId, mcpConfigOrNowIsoFactory, nowIsoFactory = nowIso) {
2442
3085
  const { mcpConfigPath, clock } = resolveLeaseArguments(mcpConfigOrNowIsoFactory, nowIsoFactory);
2443
3086
  const mcpConfigMetadata = createMcpConfigMetadata(mcpConfigPath, readMcpConfigFingerprint(mcpConfigOrNowIsoFactory));
3087
+ const processIdentity = await getOwnProcessIdentity();
2444
3088
  await ensureQueueDir();
2445
3089
  const lockPath = queueLockFilePath(sessionId);
2446
3090
  const socketPath = queueSocketPath(sessionId);
@@ -2453,6 +3097,7 @@ async function tryAcquireQueueOwnerLease(sessionId, mcpConfigOrNowIsoFactory, no
2453
3097
  socketPath,
2454
3098
  createdAt,
2455
3099
  ownerGeneration,
3100
+ ...processIdentity ? { processIdentity } : {},
2456
3101
  ...mcpConfigMetadata,
2457
3102
  updates: Promise.resolve(),
2458
3103
  released: false
@@ -2480,7 +3125,10 @@ async function tryAcquireQueueOwnerLease(sessionId, mcpConfigOrNowIsoFactory, no
2480
3125
  });
2481
3126
  await removeSocketFile(socketPath).catch(() => {});
2482
3127
  return lease;
2483
- }, reservation);
3128
+ }, {
3129
+ reservation,
3130
+ capturedIdentity: lease
3131
+ });
2484
3132
  } catch (error) {
2485
3133
  return await handleLeaseCollision(sessionId, error);
2486
3134
  }
@@ -2542,32 +3190,45 @@ function refreshQueueOwnerLease(lease, options, nowIsoFactory = nowIso) {
2542
3190
  if (lease.released) return Promise.resolve();
2543
3191
  const update = lease.updates.then(async () => {
2544
3192
  await withQueueLeaseMutation(lease.sessionId, async () => {
2545
- if (!await ownsQueueLease(lease)) return;
2546
- await stageQueueOwnerRecord(lease, options.queueDepth, nowIsoFactory, async (tempPath) => {
2547
- await fs$1.rename(tempPath, lease.lockPath);
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))
2548
3200
  });
2549
- });
3201
+ }, { capturedIdentity: lease });
2550
3202
  });
2551
3203
  lease.updates = update.catch(() => {});
2552
3204
  return update;
2553
3205
  }
2554
3206
  async function stageQueueOwnerRecord(lease, queueDepth, clock, publish) {
2555
- const payload = JSON.stringify({
2556
- pid: process.pid,
3207
+ await stagePreparedQueueOwnerRecord({
3208
+ pid: lease.pid,
2557
3209
  sessionId: lease.sessionId,
2558
3210
  socketPath: lease.socketPath,
2559
3211
  createdAt: lease.createdAt,
2560
3212
  heartbeatAt: clock(),
2561
3213
  ownerGeneration: lease.ownerGeneration,
3214
+ ...lease.processIdentity ? { processIdentity: lease.processIdentity } : {},
2562
3215
  queueDepth: Math.max(0, Math.round(queueDepth)),
2563
3216
  sharedRuntime: true,
2564
3217
  sessionWatch: true,
2565
3218
  persistsControlState: true,
2566
3219
  ...lease.mcpConfigPath ? { mcpConfigPath: lease.mcpConfigPath } : {},
2567
3220
  ...lease.mcpConfigFingerprint ? { mcpConfigFingerprint: lease.mcpConfigFingerprint } : {}
2568
- }, null, 2);
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);
2569
3230
  await withTempFile({
2570
- rootDir: path.dirname(lease.lockPath),
3231
+ rootDir: path.dirname(queueLockFilePath(owner.sessionId)),
2571
3232
  prefix: "owner",
2572
3233
  fileName: "lease"
2573
3234
  }, async (tempPath) => {
@@ -2582,18 +3243,129 @@ async function stageQueueOwnerRecord(lease, queueDepth, clock, publish) {
2582
3243
  async function releaseQueueOwnerLease(lease) {
2583
3244
  lease.released = true;
2584
3245
  await lease.updates;
2585
- await cleanupQueueOwnerFiles(lease.sessionId, lease.socketPath, () => ownsQueueLease(lease));
3246
+ await cleanupQueueOwnerFiles(lease.sessionId, lease.socketPath, () => ownsQueueLease(lease), lease);
3247
+ }
3248
+ function unverifiedQueueOwnerError(owner) {
3249
+ return new QueueConnectionError(`Cannot safely retire queue owner pid ${owner.pid}: its process birth identity is unverified. Its lease was retained. Retry from the same local process namespace with process-query access, or let the owner finish normal shutdown or idle expiry.`, {
3250
+ detailCode: "QUEUE_OWNER_IDENTITY_UNVERIFIED",
3251
+ origin: "queue",
3252
+ retryable: true
3253
+ });
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
+ }
3270
+ async function dispatchVerifiedQueueOwnerSignal(owner, signal, requireStale, retirement) {
3271
+ return await withQueueLeaseMutation(owner.sessionId, async () => {
3272
+ const current = await readQueueOwnerRecord(owner.sessionId);
3273
+ retirement.leaseReleased = current?.pid !== owner.pid;
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);
3277
+ if (identity !== "matching") {
3278
+ retirement.gone = identity === "gone";
3279
+ retirement.unverified = identity === "unknown";
3280
+ return false;
3281
+ }
3282
+ if (process.platform === "win32" && !await prepareWindowsRetirement(current, retirement.deadline)) return false;
3283
+ return await dispatchQueueOwnerSignal(owner.pid, signal, retirement.deadline);
3284
+ });
3285
+ }
3286
+ async function retireQueueOwner(owner, requireStale, deadline) {
3287
+ const retirement = {
3288
+ gone: false,
3289
+ unverified: false,
3290
+ leaseReleased: false,
3291
+ deadline
3292
+ };
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";
3296
+ if (retirement.unverified && !gone) throw unverifiedQueueOwnerError(owner);
3297
+ return gone;
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();
2586
3353
  }
2587
3354
  async function terminateQueueOwnerForSession(sessionId, expectedOwner, requireStale = false) {
2588
3355
  await settlePendingQueueLeaseGuard(sessionId);
2589
3356
  const owner = expectedOwner ?? await readQueueOwnerRecord(sessionId);
2590
3357
  if (!owner || owner.sessionId !== sessionId) return;
2591
- if (owner.pid !== process.pid && isProcessAlive(owner.pid)) await terminateWithDispatch(owner.pid, (signal) => withQueueLeaseMutation(sessionId, async () => {
2592
- if (!await ownsQueueLease(owner, requireStale && signal === "SIGTERM")) return false;
2593
- return dispatchSignal(owner.pid, signal);
2594
- }), isProcessDefinitelyDead);
2595
- if (!isProcessDefinitelyDead(owner.pid)) return;
2596
- await cleanupQueueOwnerFiles(sessionId, owner.socketPath, async () => isProcessDefinitelyDead(owner.pid) && await ownsQueueLease(owner));
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);
2597
3369
  }
2598
3370
  async function waitMs(ms) {
2599
3371
  await new Promise((resolve) => {
@@ -2998,9 +3770,12 @@ function queueOwnerMessageParser(type) {
2998
3770
  }
2999
3771
  function parseEventOwnerMessage(message, context) {
3000
3772
  if (!isAcpJsonRpcMessage(message.message)) return null;
3773
+ const direction = message.direction;
3774
+ if (direction !== void 0 && direction !== "inbound" && direction !== "outbound") return null;
3001
3775
  return {
3002
3776
  type: "event",
3003
3777
  ...context,
3778
+ ...direction === void 0 ? {} : { direction },
3004
3779
  message: message.message
3005
3780
  };
3006
3781
  }
@@ -3282,7 +4057,7 @@ function unexpectedQueueResponseError() {
3282
4057
  }
3283
4058
  function handleAcknowledgedSubmitMessage(message, controls, formatter) {
3284
4059
  if (message.type === "event") {
3285
- notifyObserver(() => formatter.onAcpMessage(message.message));
4060
+ notifyObserver(() => formatter.onAcpMessage(message.message, message.direction));
3286
4061
  return;
3287
4062
  }
3288
4063
  if (message.type === "permission_escalation") {
@@ -3514,7 +4289,10 @@ async function tryCloseSessionOnRunningOwner(options) {
3514
4289
  verbose: options.verbose,
3515
4290
  requestName: "close_session",
3516
4291
  logPrefix: "[acpx] requested session/close on active owner pid",
3517
- submit: (owner) => submitCloseSessionToQueueOwner(owner, options.timeoutMs)
4292
+ submit: (owner) => {
4293
+ options.onOwnerSelected?.(owner);
4294
+ return submitCloseSessionToQueueOwner(owner, options.timeoutMs);
4295
+ }
3518
4296
  });
3519
4297
  }
3520
4298
  async function tryCancelOnRunningOwner(options) {
@@ -3562,6 +4340,6 @@ async function trySetConfigOptionOnRunningOwner(sessionId, configId, value, time
3562
4340
  });
3563
4341
  }
3564
4342
  //#endregion
3565
- export { setPerfGauge as $, isoNow as A, AcpxOperationalError as At, serializeSessionRecordForDisk as B, QueueConnectionError as Bt, promptToDisplayText as C, NON_INTERACTIVE_PERMISSION_POLICIES as Ct, findGitRepositoryRoot as D, PERMISSION_MODES as Dt, absolutePath as E, OUTPUT_FORMATS as Et, readSessionRecord as F, ClaudeAcpSessionCreateTimeoutError as Ft, SessionWatchError as G, SessionResumeRequiredError as Gt, normalizeAgentSessionId as H, SessionModeReplayError as Ht, resolveSessionRecord as I, CopilotAcpUnsupportedError as It, getPerfMetricsSnapshot as J, watchSession as K, UnsupportedPromptContentError as Kt, writeSessionRecord as L, GeminiAcpStartupTimeoutError as Lt, listSessionsForAgent as M, AgentSpawnError as Mt, normalizeName as N, AgentStartupError as Nt, findSession as O, PERMISSION_POLICY_ACTIONS as Ot, pruneSessions as P, AuthPolicyError as Pt, resetPerfMetrics as Q, assertPersistedKeyPolicy as R, PermissionDeniedError as Rt, parsePromptSource as S, EXIT_CODES as St, DEFAULT_HISTORY_LIMIT as T, OUTPUT_ERROR_ORIGINS as Tt, SESSION_JOURNAL_SCHEMA as U, SessionModelReplayError as Ut, extractAgentSessionId as V, SessionConfigOptionReplayError as Vt, SessionJournalReader as W, SessionNotFoundError as Wt, measurePerf as X, incrementPerfCounter as Y, recordPerfDuration as Z, waitMs as _, isProcessDefinitelyDead as _t, trySetModelOnRunningOwner as a, parseJsonRpcErrorMessage as at, getUnsupportedPromptContentMessage as b, isAcpResourceNotFoundError as bt, queueRequestExceedsLimit as c, DEFAULT_EVENT_SEGMENT_MAX_BYTES as ct, readQueueOwnerRecord as d, sessionEventActivePath as dt, startPerfTimer as et, refreshQueueOwnerLease as f, sessionEventLockPath as ft, tryAcquireQueueOwnerLease as g, isProcessAlive as gt, terminateQueueOwnerForSession as h, writePrivateJsonFile as ht, trySetModeOnRunningOwner as i, isSessionUpdateNotification as it, listSessions as j, AgentDisconnectedError as jt, findSessionByDirectoryWalk as k, SESSION_RECORD_SCHEMA as kt, parseQueueRequest as l, defaultSessionEventLog as lt, terminateProcess as m, writePrivateFile as mt, tryCloseSessionOnRunningOwner as n, isAcpJsonRpcMessage as nt, trySubmitToRunningOwner as o, parsePermissionNotice as ot, releaseQueueOwnerLease as p, sessionEventSegmentPath as pt, formatPerfMetric as q, trySetConfigOptionOnRunningOwner as r, isAcpMessageObject as rt, queueRequestByteLimit as s, parsePromptStopReason as st, tryCancelOnRunningOwner as t, extractSessionUpdateNotification as tt, probeQueueOwnerHealth as u, sessionBaseDir as ut, QueueLeaseGuardSettlementError as v, extractAcpError as vt, textPrompt as w, OUTPUT_ERROR_CODES as wt, mergePromptSourceWithText as x, AUTH_POLICIES as xt, PromptInputValidationError as y, formatUnknownErrorMessage as yt, parseSessionRecord as z, PermissionPromptUnavailableError as zt };
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 };
3566
4344
 
3567
- //# sourceMappingURL=ipc-BJrvfNXr.js.map
4345
+ //# sourceMappingURL=ipc-DAApJ5mi.js.map