acpx 0.18.0 → 0.19.0

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-Ct2yWPW7.js} +3 -2
  2. package/dist/agent-registry-Ct2yWPW7.js.map +1 -0
  3. package/dist/agent-registry.js +1 -1
  4. package/dist/{cli-8mVHeUXh.js → cli-Bmki9cj5.js} +5 -5
  5. package/dist/{cli-8mVHeUXh.js.map → cli-Bmki9cj5.js.map} +1 -1
  6. package/dist/cli.js +55 -47
  7. package/dist/cli.js.map +1 -1
  8. package/dist/{client-DeNKZZcu.d.ts → client-DzaUKazo.d.ts} +10 -4
  9. package/dist/client-DzaUKazo.d.ts.map +1 -0
  10. package/dist/{flows-WOQNzCMC.js → flows--Ewxn_fQ.js} +665 -460
  11. package/dist/flows--Ewxn_fQ.js.map +1 -0
  12. package/dist/flows.d.ts +19 -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-DRzxHZmr.js} +32 -11
  16. package/dist/invocation-options-DRzxHZmr.js.map +1 -0
  17. package/dist/{ipc-BJrvfNXr.js → ipc-fMgj5vrO.js} +540 -241
  18. package/dist/ipc-fMgj5vrO.js.map +1 -0
  19. package/dist/{output-CL18YmsG.js → output-D6kDs8BN.js} +24 -11
  20. package/dist/output-D6kDs8BN.js.map +1 -0
  21. package/dist/{queue-owner-runtime-Cr0dWJuL.js → queue-owner-runtime-CLwjMYNL.js} +549 -337
  22. package/dist/queue-owner-runtime-CLwjMYNL.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 +47 -28
  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-BfdIo-n4.js} +5 -5
  29. package/dist/session-watch-BfdIo-n4.js.map +1 -0
  30. package/dist/{watch-Dp73ph8h.js → watch-DH-zXQH2.js} +2 -2
  31. package/dist/{watch-Dp73ph8h.js.map → watch-DH-zXQH2.js.map} +1 -1
  32. package/package.json +1 -1
  33. package/skills/acpx/SKILL.md +43 -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,4 +1,4 @@
1
- import { n as AGENT_REGISTRY, r as BUILT_IN_AGENT_PACKAGES, t as AGENT_ARGV_REGISTRY } from "./agent-registry-DdKB-zia.js";
1
+ import { M as runTimedExecFile, n as AGENT_REGISTRY, r as BUILT_IN_AGENT_PACKAGES, t as AGENT_ARGV_REGISTRY } from "./agent-registry-Ct2yWPW7.js";
2
2
  import { statSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import fs$1 from "node:fs/promises";
@@ -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,7 +1721,8 @@ 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
  }
@@ -1834,42 +1762,49 @@ function isoNow() {
1834
1762
  return (/* @__PURE__ */ new Date()).toISOString();
1835
1763
  }
1836
1764
  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;
1765
+ return await collectSessionRecords();
1846
1766
  }
1847
1767
  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));
1768
+ return await collectSessionRecords(agentCommand);
1769
+ }
1770
+ async function collectSessionRecords(agentCommand) {
1771
+ const records = [];
1772
+ for await (const record of sessionRecords()) if (agentCommand === void 0 || record.agentCommand === agentCommand) records.push(record);
1773
+ return records.toSorted((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));
1850
1774
  }
1851
1775
  async function findSession(options) {
1852
1776
  const normalizedCwd = absolutePath(options.cwd);
1853
1777
  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);
1778
+ let match;
1779
+ for await (const record of sessionRecords(options.readOnly)) if (record.cwd === normalizedCwd && matchesSession(record, options.agentCommand, normalizedName, options.includeClosed) && isNewer(record, match)) match = record;
1780
+ return match;
1857
1781
  }
1858
1782
  async function findSessionByDirectoryWalk(options) {
1859
1783
  const normalizedName = normalizeName(options.name);
1784
+ const directories = walkDirectories(options);
1785
+ let match;
1786
+ let distance = Infinity;
1787
+ for await (const record of sessionRecords()) {
1788
+ const candidateDistance = directories.get(record.cwd);
1789
+ if (candidateDistance !== void 0 && matchesSession(record, options.agentCommand, normalizedName) && (candidateDistance < distance || candidateDistance === distance && isNewer(record, match))) {
1790
+ match = record;
1791
+ distance = candidateDistance;
1792
+ }
1793
+ }
1794
+ return match;
1795
+ }
1796
+ function walkDirectories(options) {
1860
1797
  const normalizedStart = absolutePath(options.cwd);
1861
1798
  const normalizedBoundary = absolutePath(options.boundary ?? normalizedStart);
1862
1799
  const walkBoundary = isWithinBoundary(normalizedBoundary, normalizedStart) ? normalizedBoundary : normalizedStart;
1863
- const sessions = (await loadSessionIndexEntries()).filter((session) => session.agentCommand === options.agentCommand);
1800
+ const directories = /* @__PURE__ */ new Map();
1801
+ const walkRoot = path.parse(normalizedStart).root;
1864
1802
  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;
1803
+ while (current) {
1804
+ directories.set(current, directories.size);
1805
+ current = nextWalkParent(current, walkBoundary, walkRoot);
1872
1806
  }
1807
+ return directories;
1873
1808
  }
1874
1809
  function nextWalkParent(current, walkBoundary, walkRoot) {
1875
1810
  if (current === walkBoundary || current === walkRoot) return;
@@ -1885,8 +1820,7 @@ function isSessionStreamFile(fileName, safeId) {
1885
1820
  return fileName === `${safeId}.stream.ndjson` || fileName === `${safeId}.stream.lock` || fileName.startsWith(segmentPrefix) && /^\d+\.ndjson$/.test(fileName.slice(segmentPrefix.length));
1886
1821
  }
1887
1822
  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);
1823
+ const records = await loadPrunableRecords(options.before ?? (options.olderThanMs != null ? new Date(Date.now() - options.olderThanMs) : void 0), options.agentCommand);
1890
1824
  if (options.dryRun) return {
1891
1825
  pruned: records,
1892
1826
  bytesFreed: 0,
@@ -1899,7 +1833,6 @@ async function pruneSessions(options = {}) {
1899
1833
  dirEntries = await fs$1.readdir(sessionDir);
1900
1834
  } catch {}
1901
1835
  for (const record of records) bytesFreed += await pruneSessionFiles(record, sessionDir, dirEntries, options.includeHistory === true);
1902
- await rebuildSessionIndex(sessionDir).catch(() => {});
1903
1836
  return {
1904
1837
  pruned: records,
1905
1838
  bytesFreed,
@@ -1909,14 +1842,11 @@ async function pruneSessions(options = {}) {
1909
1842
  function isPruneCandidate(record, agentCommand) {
1910
1843
  return record.closed === true && (!agentCommand || record.agentCommand === agentCommand);
1911
1844
  }
1912
- async function loadPrunableRecords(entries, cutoff, agentCommand) {
1845
+ async function loadPrunableRecords(cutoff, agentCommand) {
1913
1846
  const records = [];
1914
1847
  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;
1848
+ for await (const record of sessionRecords()) if (isPruneCandidate(record, agentCommand) && isBeforeCutoff(record, cutoffIso)) records.push(record);
1849
+ return records.toSorted((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));
1920
1850
  }
1921
1851
  function isBeforeCutoff(record, cutoffIso) {
1922
1852
  return !cutoffIso || closedAtOrLastUsedAt(record) < cutoffIso;
@@ -1974,12 +1904,12 @@ function isAudioBlock(value) {
1974
1904
  }
1975
1905
  function isResourceLinkBlock(value) {
1976
1906
  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");
1907
+ return record?.type === "resource_link" && isNonEmptyString(record.uri) && (record.title == null || typeof record.title === "string") && typeof record.name === "string";
1978
1908
  }
1979
1909
  function isResourcePayload(value) {
1980
1910
  const record = asRecord$1(value);
1981
1911
  if (!record || !isNonEmptyString(record.uri)) return false;
1982
- return record.text === void 0 || typeof record.text === "string";
1912
+ return typeof record.text === "string" || typeof record.blob === "string";
1983
1913
  }
1984
1914
  function isResourceBlock(value) {
1985
1915
  const record = asRecord$1(value);
@@ -2022,12 +1952,12 @@ function validateAudioContentBlock(record, index) {
2022
1952
  }
2023
1953
  function validateResourceLinkContentBlock(record, index) {
2024
1954
  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`;
1955
+ if (record.title != null && typeof record.title !== "string") return `prompt[${index}] resource_link block title must be a string or null when present`;
1956
+ if (typeof record.name !== "string") return `prompt[${index}] resource_link block must include a string name`;
2027
1957
  }
2028
1958
  function validateResourceContentBlock(record, index) {
2029
1959
  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`;
1960
+ 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
1961
  }
2032
1962
  function getContentBlockValidationError(value, index) {
2033
1963
  const record = asRecord$1(value);
@@ -2112,6 +2042,318 @@ function resourceBlockDisplayText(block) {
2112
2042
  return "text" in block.resource && typeof block.resource.text === "string" ? block.resource.text : block.resource.uri;
2113
2043
  }
2114
2044
  //#endregion
2045
+ //#region src/process-identity.ts
2046
+ const IDENTITY_QUERY_TIMEOUT_MS$1 = 2e3;
2047
+ const LINUX_PROCESS_QUERY = `
2048
+ const fs = require('node:fs/promises');
2049
+ const [targetPid, observerPid] = process.argv.slice(1);
2050
+ if (process.ppid !== Number(observerPid)) process.exit(1);
2051
+ const timeNamespace = (pid) => fs.readlink('/proc/' + pid + '/ns/time').catch((error) => {
2052
+ if (error.code === 'ENOENT') return 'unsupported';
2053
+ throw error;
2054
+ });
2055
+ Promise.all([
2056
+ fs.readFile('/proc/sys/kernel/random/boot_id', 'utf8'),
2057
+ fs.readlink('/proc/self/ns/pid'),
2058
+ timeNamespace('self'),
2059
+ fs.readlink('/proc/' + observerPid + '/ns/pid'),
2060
+ timeNamespace(observerPid),
2061
+ fs.readFile('/proc/self/stat', 'utf8'),
2062
+ fs.readFile('/proc/' + targetPid + '/stat', 'utf8').catch(() => null),
2063
+ ]).then(([bootId, pidNamespace, timeNamespace, observerPidNamespace,
2064
+ observerTimeNamespace, selfStat, targetStat]) => {
2065
+ process.stdout.write(JSON.stringify({bootId: bootId.trim(), pidNamespace,
2066
+ timeNamespace, observerPidNamespace, observerTimeNamespace,
2067
+ helperPid: process.pid, selfStat, targetStat}));
2068
+ }).catch(() => { process.exitCode = 1; });
2069
+ `;
2070
+ const POSIX_MONTHS = [
2071
+ "Jan",
2072
+ "Feb",
2073
+ "Mar",
2074
+ "Apr",
2075
+ "May",
2076
+ "Jun",
2077
+ "Jul",
2078
+ "Aug",
2079
+ "Sep",
2080
+ "Oct",
2081
+ "Nov",
2082
+ "Dec"
2083
+ ];
2084
+ let ownIdentity;
2085
+ let ownIdentityPending;
2086
+ function parseProcessBirthIdentity(raw) {
2087
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
2088
+ const identity = raw;
2089
+ if (identity.kind === "linux-proc") return parseLinuxIdentity(identity);
2090
+ if (identity.kind !== "posix-lstart" && identity.kind !== "windows-creation") return;
2091
+ if (!isCanonicalBirth(identity.value, identity.kind)) return;
2092
+ return {
2093
+ kind: identity.kind,
2094
+ value: identity.value
2095
+ };
2096
+ }
2097
+ function isCanonicalBirth(value, kind) {
2098
+ 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;
2099
+ const canonical = new Date(value).toISOString();
2100
+ return canonical.slice(0, 19) === value.slice(0, 19) && (kind === "posix-lstart" ? value === canonical && value.endsWith(".000Z") : /\.\d{7}Z$/.test(value));
2101
+ }
2102
+ function parseLinuxScope(raw) {
2103
+ const { bootId, pidNamespace, timeNamespace } = raw;
2104
+ 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;
2105
+ return {
2106
+ bootId,
2107
+ pidNamespace,
2108
+ timeNamespace
2109
+ };
2110
+ }
2111
+ function isTimeNamespace(value) {
2112
+ return typeof value === "string" && (value === "unsupported" || /^time:\[[1-9]\d*\]$/.test(value));
2113
+ }
2114
+ function isStartTicks(value) {
2115
+ return typeof value === "string" && /^(?:0|[1-9]\d*)$/.test(value);
2116
+ }
2117
+ function parseLinuxIdentity(raw) {
2118
+ const scope = parseLinuxScope(raw);
2119
+ if (!scope || !isStartTicks(raw.startTicks)) return;
2120
+ return {
2121
+ kind: "linux-proc",
2122
+ ...scope,
2123
+ startTicks: raw.startTicks
2124
+ };
2125
+ }
2126
+ function compareLinuxScope(expected, observed) {
2127
+ if (expected.bootId !== observed.bootId) return "different";
2128
+ if (expected.pidNamespace !== observed.pidNamespace || expected.timeNamespace !== observed.timeNamespace) return "unknown";
2129
+ return "matching";
2130
+ }
2131
+ function compareLinuxIdentity(expected, observed) {
2132
+ const scope = compareLinuxScope(expected, observed);
2133
+ return scope === "matching" ? expected.startTicks === observed.startTicks ? "matching" : "different" : scope;
2134
+ }
2135
+ function compareProcessBirthIdentity(expected, observed) {
2136
+ if (!expected || expected.kind !== observed.kind) return "unknown";
2137
+ if (expected.kind === "linux-proc" && observed.kind === "linux-proc") return compareLinuxIdentity(expected, observed);
2138
+ if ("value" in expected && "value" in observed) return expected.value === observed.value ? "matching" : "different";
2139
+ return "unknown";
2140
+ }
2141
+ function parseLinuxStat(raw) {
2142
+ if (typeof raw !== "string") return;
2143
+ const prefix = /^(\d+) \(/.exec(raw);
2144
+ const close = raw.lastIndexOf(")");
2145
+ if (!prefix || close < 0) return;
2146
+ const pid = Number(prefix[1]);
2147
+ const fields = raw.slice(close + 2).trim().split(/\s+/u);
2148
+ const startTicks = fields[19];
2149
+ if (!isPositivePid(pid) || !isStartTicks(startTicks) || !/^[RSDTtKWPI]$/.test(fields[0] ?? "")) return;
2150
+ return {
2151
+ pid,
2152
+ startTicks
2153
+ };
2154
+ }
2155
+ async function readLinuxObservation(pid, timeoutMs) {
2156
+ const output = await runTimedExecFile(process.execPath, [
2157
+ "--input-type=commonjs",
2158
+ "-e",
2159
+ LINUX_PROCESS_QUERY,
2160
+ String(pid),
2161
+ String(process.pid)
2162
+ ], {
2163
+ timeoutMs,
2164
+ maxBufferBytes: 8192,
2165
+ env: {
2166
+ ...process.env,
2167
+ NODE_OPTIONS: ""
2168
+ }
2169
+ });
2170
+ const raw = JSON.parse(output);
2171
+ const scope = parseLinuxScope(raw);
2172
+ const self = parseLinuxStat(raw.selfStat);
2173
+ if (!scope || !self || self.pid !== raw.helperPid || scope.pidNamespace !== raw.observerPidNamespace || scope.timeNamespace !== raw.observerTimeNamespace) return;
2174
+ const target = parseLinuxStat(raw.targetStat);
2175
+ return {
2176
+ scope,
2177
+ ...target?.pid === pid ? { identity: {
2178
+ kind: "linux-proc",
2179
+ ...scope,
2180
+ startTicks: target.startTicks
2181
+ } } : {}
2182
+ };
2183
+ }
2184
+ function isPositivePid(pid) {
2185
+ return Number.isSafeInteger(pid) && pid > 0;
2186
+ }
2187
+ function windowsPowerShellPath() {
2188
+ const systemRoot = process.env.SystemRoot;
2189
+ if (!systemRoot || !path.win32.isAbsolute(systemRoot)) throw new Error("Windows system directory is unavailable");
2190
+ return path.win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
2191
+ }
2192
+ function windowsProcessQuery(pid) {
2193
+ return [
2194
+ "$ErrorActionPreference = 'Stop'",
2195
+ `Get-CimInstance -ClassName Win32_Process -Property ProcessId,ParentProcessId,CreationDate${pid === void 0 ? "" : ` -Filter 'ProcessId = ${pid}'`} | ForEach-Object {`,
2196
+ "if ($null -ne $_.CreationDate) {",
2197
+ "'{0} {1} 0 S {2}' -f $_.ProcessId,$_.ParentProcessId,$_.CreationDate.ToUniversalTime().ToString('o')",
2198
+ "}",
2199
+ "}"
2200
+ ].join("\n");
2201
+ }
2202
+ function posixBirth(value) {
2203
+ const match = /^\w{3}\s+(\w{3})\s+(\d{1,2})\s+(\d{2}):(\d{2}):(\d{2})\s+(\d{4})$/.exec(value);
2204
+ if (!match) return;
2205
+ const month = POSIX_MONTHS.indexOf(match[1]);
2206
+ if (month < 0) return;
2207
+ const valueUtc = `${match[6]}-${String(month + 1).padStart(2, "0")}-${match[2].padStart(2, "0")}T${match[3]}:${match[4]}:${match[5]}.000Z`;
2208
+ return isCanonicalBirth(valueUtc, "posix-lstart") ? valueUtc : void 0;
2209
+ }
2210
+ function parseProcessTable(output) {
2211
+ const table = /* @__PURE__ */ new Map();
2212
+ for (const line of output.split("\n")) {
2213
+ const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(line);
2214
+ if (!match || match[4].startsWith("Z")) continue;
2215
+ const pid = Number(match[1]);
2216
+ const birth = processTableBirth(match[5]);
2217
+ if (Number.isSafeInteger(pid) && pid > 0 && birth) table.set(pid, {
2218
+ pid,
2219
+ parentPid: Number(match[2]),
2220
+ groupPid: Number(match[3]),
2221
+ birth
2222
+ });
2223
+ }
2224
+ return table;
2225
+ }
2226
+ function processTableBirth(value) {
2227
+ if (process.platform !== "win32") return posixBirth(value);
2228
+ return isCanonicalBirth(value, "windows-creation") ? value : void 0;
2229
+ }
2230
+ async function readProcessTable(timeoutMs, pid) {
2231
+ if (pid !== void 0 && (!Number.isSafeInteger(pid) || pid <= 0)) throw new Error("Process identity requires a positive PID");
2232
+ if (process.platform === "win32") return parseProcessTable(await runTimedExecFile(windowsPowerShellPath(), [
2233
+ "-NoLogo",
2234
+ "-NoProfile",
2235
+ "-NonInteractive",
2236
+ "-Command",
2237
+ windowsProcessQuery(pid)
2238
+ ], {
2239
+ timeoutMs,
2240
+ windowsHide: true
2241
+ }));
2242
+ return parseProcessTable(await runTimedExecFile("ps", [
2243
+ ...pid === void 0 ? ["-e"] : ["-p", String(pid)],
2244
+ "-o",
2245
+ "pid=,ppid=,pgid=,stat=,lstart="
2246
+ ], {
2247
+ timeoutMs,
2248
+ env: {
2249
+ ...process.env,
2250
+ LC_ALL: "C",
2251
+ TZ: "UTC"
2252
+ }
2253
+ }));
2254
+ }
2255
+ async function probeProcessIdentity(pid, timeoutMs = IDENTITY_QUERY_TIMEOUT_MS$1) {
2256
+ if (!isPositivePid(pid)) return { state: "unknown" };
2257
+ if (isProcessDefinitelyDead(pid)) return { state: "dead" };
2258
+ try {
2259
+ const identity = await readNativeIdentity(pid, timeoutMs);
2260
+ if (identity) return {
2261
+ state: "alive",
2262
+ identity
2263
+ };
2264
+ } catch {}
2265
+ return { state: isProcessDefinitelyDead(pid) ? "dead" : "unknown" };
2266
+ }
2267
+ async function readNativeIdentity(pid, timeoutMs) {
2268
+ if (process.platform === "linux") return (await readLinuxObservation(pid, timeoutMs))?.identity;
2269
+ const entry = (await readProcessTable(timeoutMs, pid)).get(pid);
2270
+ return entry ? processBirthIdentity(entry.birth) : void 0;
2271
+ }
2272
+ function processBirthIdentity(value) {
2273
+ return {
2274
+ kind: process.platform === "win32" ? "windows-creation" : "posix-lstart",
2275
+ value
2276
+ };
2277
+ }
2278
+ function nativeIdentityKind() {
2279
+ if (process.platform === "linux") return "linux-proc";
2280
+ return process.platform === "win32" ? "windows-creation" : "posix-lstart";
2281
+ }
2282
+ function comparisonToIncarnation(comparison) {
2283
+ return comparison === "different" ? "gone" : comparison;
2284
+ }
2285
+ async function observeLinuxIncarnation(pid, expected, timeoutMs) {
2286
+ const observed = await readLinuxObservation(pid, timeoutMs).catch(() => void 0);
2287
+ if (!observed) return "unknown";
2288
+ const scope = compareLinuxScope(expected, observed.scope);
2289
+ if (scope !== "matching") return comparisonToIncarnation(scope);
2290
+ if (isProcessDefinitelyDead(pid)) return "gone";
2291
+ return observed.identity ? comparisonToIncarnation(compareProcessBirthIdentity(expected, observed.identity)) : "unknown";
2292
+ }
2293
+ async function observeProcessIncarnation(pid, expected, timeoutMs = IDENTITY_QUERY_TIMEOUT_MS$1) {
2294
+ if (!isPositivePid(pid)) return "unknown";
2295
+ if (expected?.kind === "linux-proc") return process.platform === "linux" ? await observeLinuxIncarnation(pid, expected, timeoutMs) : "unknown";
2296
+ if (expected && expected.kind !== nativeIdentityKind()) return "unknown";
2297
+ return await observeTimestampIncarnation(pid, expected, timeoutMs);
2298
+ }
2299
+ async function observeTimestampIncarnation(pid, expected, timeoutMs) {
2300
+ if (isProcessDefinitelyDead(pid)) return "gone";
2301
+ if (!expected) return "unknown";
2302
+ const observed = await probeProcessIdentity(pid, timeoutMs);
2303
+ if (observed.state === "dead") return "gone";
2304
+ return observed.state === "alive" ? comparisonToIncarnation(compareProcessBirthIdentity(expected, observed.identity)) : "unknown";
2305
+ }
2306
+ async function getOwnProcessIdentity() {
2307
+ if (ownIdentity) return ownIdentity;
2308
+ ownIdentityPending ??= probeProcessIdentity(process.pid).then((probe) => {
2309
+ if (probe.state === "alive") ownIdentity = probe.identity;
2310
+ return ownIdentity;
2311
+ }).finally(() => {
2312
+ ownIdentityPending = void 0;
2313
+ });
2314
+ return await ownIdentityPending;
2315
+ }
2316
+ //#endregion
2317
+ //#region src/session/lock-owner.ts
2318
+ const OBSERVATION_REUSE_MS = 1e3;
2319
+ const IDENTITY_QUERY_TIMEOUT_MS = 1e3;
2320
+ function lockOwnerPid(value) {
2321
+ if (!value || typeof value !== "object" || !("pid" in value)) return;
2322
+ const pid = value.pid;
2323
+ return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
2324
+ }
2325
+ function canReuseObservation(preserved, pid, identity, fresh) {
2326
+ return !fresh && preserved?.pid === pid && compareProcessBirthIdentity(preserved.identity, identity) === "matching" && performance.now() - preserved.checkedAt < OBSERVATION_REUSE_MS;
2327
+ }
2328
+ async function createLockOwner(options = {}) {
2329
+ const { signal, capturedIdentity } = options;
2330
+ signal?.throwIfAborted();
2331
+ const processIdentity = capturedIdentity === void 0 ? await getOwnProcessIdentity() : capturedIdentity.processIdentity;
2332
+ signal?.throwIfAborted();
2333
+ let preserved;
2334
+ return {
2335
+ payload: {
2336
+ pid: process.pid,
2337
+ processIdentity
2338
+ },
2339
+ async hasExited(payload, fresh, signal) {
2340
+ signal?.throwIfAborted();
2341
+ const pid = lockOwnerPid(payload);
2342
+ if (!pid) return false;
2343
+ const identity = parseProcessBirthIdentity(payload.processIdentity);
2344
+ if (identity && canReuseObservation(preserved, pid, identity, fresh)) return false;
2345
+ const incarnation = await observeProcessIncarnation(pid, identity, IDENTITY_QUERY_TIMEOUT_MS);
2346
+ signal?.throwIfAborted();
2347
+ if (identity && incarnation !== "gone") preserved = {
2348
+ pid,
2349
+ identity,
2350
+ checkedAt: performance.now()
2351
+ };
2352
+ return incarnation === "gone";
2353
+ }
2354
+ };
2355
+ }
2356
+ //#endregion
2115
2357
  //#region src/session/queue/paths.ts
2116
2358
  function shortHash(value, length) {
2117
2359
  return createHash("sha256").update(value).digest("hex").slice(0, length);
@@ -2157,10 +2399,7 @@ async function canonicalLeasePath(sessionId) {
2157
2399
  throw error;
2158
2400
  }
2159
2401
  }
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) {
2402
+ async function acquireGuard(lockPath, owner) {
2164
2403
  return await acquireFileLock(lockPath, {
2165
2404
  managerKey: "acpx.queue-lease-mutation",
2166
2405
  lockPath: `${lockPath}.guard`,
@@ -2173,9 +2412,9 @@ async function acquireGuard(lockPath) {
2173
2412
  randomize: false
2174
2413
  },
2175
2414
  staleRecovery: "remove-if-unchanged",
2176
- payload: () => ({ pid: process.pid }),
2177
- shouldReclaim: guardOwnerHasExited,
2178
- shouldRemoveStaleLock: guardOwnerHasExited
2415
+ payload: () => owner.payload,
2416
+ shouldReclaim: ({ payload }) => owner.hasExited(payload),
2417
+ shouldRemoveStaleLock: ({ payload }) => owner.hasExited(payload, true)
2179
2418
  });
2180
2419
  }
2181
2420
  function matchesReservation(value, expected) {
@@ -2202,7 +2441,7 @@ async function settleReceipt(lockPath, receipt) {
2202
2441
  if (receipt.reservation) {
2203
2442
  if (!await receipt.guard.verifyStillHeld()) {
2204
2443
  await receipt.guard.release();
2205
- receipt.guard = await acquireGuard(lockPath);
2444
+ receipt.guard = await acquireGuard(lockPath, receipt.owner);
2206
2445
  }
2207
2446
  await rollbackReservation(lockPath, receipt.reservation);
2208
2447
  receipt.reservation = void 0;
@@ -2241,7 +2480,8 @@ async function settlePendingQueueLeaseGuard(sessionId) {
2241
2480
  if (!localMutations.has(lockPath)) return;
2242
2481
  await locallySerialized(lockPath, async (state) => await settleFailedGuard(lockPath, state));
2243
2482
  }
2244
- async function withQueueLeaseMutation(sessionId, mutate, reservation) {
2483
+ async function withQueueLeaseMutation(sessionId, mutate, options = {}) {
2484
+ const { reservation, capturedIdentity } = options;
2245
2485
  await fs$1.mkdir(path.dirname(queueLockFilePath(sessionId)), {
2246
2486
  recursive: true,
2247
2487
  mode: 448
@@ -2255,7 +2495,8 @@ async function withQueueLeaseMutation(sessionId, mutate, reservation) {
2255
2495
  const publishedReservation = () => reservation?.published ? expected : void 0;
2256
2496
  return await locallySerialized(lockPath, async (state) => {
2257
2497
  await settleFailedGuard(lockPath, state);
2258
- const guard = await acquireGuard(lockPath);
2498
+ const owner = await createLockOwner({ capturedIdentity });
2499
+ const guard = await acquireGuard(lockPath, owner);
2259
2500
  let outcome;
2260
2501
  try {
2261
2502
  outcome = {
@@ -2271,6 +2512,7 @@ async function withQueueLeaseMutation(sessionId, mutate, reservation) {
2271
2512
  const published = publishedReservation();
2272
2513
  const receipt = {
2273
2514
  guard,
2515
+ owner,
2274
2516
  reservation: outcome.ok ? void 0 : published
2275
2517
  };
2276
2518
  try {
@@ -2294,6 +2536,7 @@ function parseQueueOwnerRecord(raw) {
2294
2536
  if (!raw || typeof raw !== "object") return null;
2295
2537
  const record = raw;
2296
2538
  if (!hasValidQueueOwnerRecordFields(record)) return null;
2539
+ const processIdentity = parseProcessBirthIdentity(record.processIdentity);
2297
2540
  return {
2298
2541
  pid: record.pid,
2299
2542
  sessionId: record.sessionId,
@@ -2302,6 +2545,7 @@ function parseQueueOwnerRecord(raw) {
2302
2545
  heartbeatAt: record.heartbeatAt,
2303
2546
  ownerGeneration: record.ownerGeneration,
2304
2547
  queueDepth: record.queueDepth,
2548
+ ...processIdentity ? { processIdentity } : {},
2305
2549
  ...parseQueueOwnerCapabilities(record),
2306
2550
  ...typeof record.mcpConfigPath === "string" ? { mcpConfigPath: record.mcpConfigPath } : {},
2307
2551
  ...typeof record.mcpConfigFingerprint === "string" ? { mcpConfigFingerprint: record.mcpConfigFingerprint } : {}
@@ -2374,10 +2618,10 @@ async function waitForProcessExit(pid, timeoutMs, hasExited) {
2374
2618
  }
2375
2619
  return hasExited(pid);
2376
2620
  }
2377
- async function cleanupQueueOwnerFiles(sessionId, socketPath, isCurrent) {
2621
+ async function cleanupQueueOwnerFiles(sessionId, socketPath, isCurrent, capturedIdentity) {
2378
2622
  await withQueueLeaseMutation(sessionId, async () => {
2379
2623
  await cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent);
2380
- });
2624
+ }, { capturedIdentity });
2381
2625
  }
2382
2626
  async function cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent) {
2383
2627
  if (!await isCurrent()) return;
@@ -2387,9 +2631,8 @@ async function cleanupGuardedQueueOwnerFiles(sessionId, socketPath, isCurrent) {
2387
2631
  if (error.code !== "ENOENT") throw error;
2388
2632
  });
2389
2633
  }
2390
- async function ownsQueueLease(owner, requireStale = false) {
2391
- const current = await readQueueOwnerRecord(owner.sessionId);
2392
- return matchesQueueOwner(current, owner) && (!requireStale || isQueueOwnerHeartbeatStale(current));
2634
+ async function ownsQueueLease(owner) {
2635
+ return matchesQueueOwner(await readQueueOwnerRecord(owner.sessionId), owner);
2393
2636
  }
2394
2637
  function matchesQueueOwner(current, expected) {
2395
2638
  return current?.sessionId === expected.sessionId && current.pid === expected.pid && current.ownerGeneration === expected.ownerGeneration;
@@ -2422,6 +2665,22 @@ function dispatchSignal(pid, signal) {
2422
2665
  return false;
2423
2666
  }
2424
2667
  }
2668
+ async function dispatchQueueOwnerSignal(pid, signal) {
2669
+ if (process.platform !== "win32") return dispatchSignal(pid, signal);
2670
+ const systemRoot = process.env.SystemRoot;
2671
+ if (!systemRoot || !path.win32.isAbsolute(systemRoot)) throw new Error("Windows queue-owner cleanup requires an absolute SystemRoot directory");
2672
+ const taskkill = path.win32.join(systemRoot, "System32", "taskkill.exe");
2673
+ await runTimedExecFile(taskkill, [
2674
+ "/pid",
2675
+ String(pid),
2676
+ "/T",
2677
+ "/F"
2678
+ ], {
2679
+ timeoutMs: PROCESS_SIGTERM_GRACE_MS,
2680
+ windowsHide: true
2681
+ });
2682
+ return true;
2683
+ }
2425
2684
  async function terminateWithDispatch(pid, dispatch, hasExited) {
2426
2685
  for (const [signal, graceMs] of [["SIGTERM", PROCESS_SIGTERM_GRACE_MS], ["SIGKILL", PROCESS_SIGKILL_GRACE_MS]]) {
2427
2686
  if (!await dispatch(signal)) return false;
@@ -2441,6 +2700,7 @@ async function resolveUsableQueueOwner(sessionId, owner) {
2441
2700
  async function tryAcquireQueueOwnerLease(sessionId, mcpConfigOrNowIsoFactory, nowIsoFactory = nowIso) {
2442
2701
  const { mcpConfigPath, clock } = resolveLeaseArguments(mcpConfigOrNowIsoFactory, nowIsoFactory);
2443
2702
  const mcpConfigMetadata = createMcpConfigMetadata(mcpConfigPath, readMcpConfigFingerprint(mcpConfigOrNowIsoFactory));
2703
+ const processIdentity = await getOwnProcessIdentity();
2444
2704
  await ensureQueueDir();
2445
2705
  const lockPath = queueLockFilePath(sessionId);
2446
2706
  const socketPath = queueSocketPath(sessionId);
@@ -2453,6 +2713,7 @@ async function tryAcquireQueueOwnerLease(sessionId, mcpConfigOrNowIsoFactory, no
2453
2713
  socketPath,
2454
2714
  createdAt,
2455
2715
  ownerGeneration,
2716
+ ...processIdentity ? { processIdentity } : {},
2456
2717
  ...mcpConfigMetadata,
2457
2718
  updates: Promise.resolve(),
2458
2719
  released: false
@@ -2480,7 +2741,10 @@ async function tryAcquireQueueOwnerLease(sessionId, mcpConfigOrNowIsoFactory, no
2480
2741
  });
2481
2742
  await removeSocketFile(socketPath).catch(() => {});
2482
2743
  return lease;
2483
- }, reservation);
2744
+ }, {
2745
+ reservation,
2746
+ capturedIdentity: lease
2747
+ });
2484
2748
  } catch (error) {
2485
2749
  return await handleLeaseCollision(sessionId, error);
2486
2750
  }
@@ -2546,7 +2810,7 @@ function refreshQueueOwnerLease(lease, options, nowIsoFactory = nowIso) {
2546
2810
  await stageQueueOwnerRecord(lease, options.queueDepth, nowIsoFactory, async (tempPath) => {
2547
2811
  await fs$1.rename(tempPath, lease.lockPath);
2548
2812
  });
2549
- });
2813
+ }, { capturedIdentity: lease });
2550
2814
  });
2551
2815
  lease.updates = update.catch(() => {});
2552
2816
  return update;
@@ -2559,6 +2823,7 @@ async function stageQueueOwnerRecord(lease, queueDepth, clock, publish) {
2559
2823
  createdAt: lease.createdAt,
2560
2824
  heartbeatAt: clock(),
2561
2825
  ownerGeneration: lease.ownerGeneration,
2826
+ ...lease.processIdentity ? { processIdentity: lease.processIdentity } : {},
2562
2827
  queueDepth: Math.max(0, Math.round(queueDepth)),
2563
2828
  sharedRuntime: true,
2564
2829
  sessionWatch: true,
@@ -2582,18 +2847,49 @@ async function stageQueueOwnerRecord(lease, queueDepth, clock, publish) {
2582
2847
  async function releaseQueueOwnerLease(lease) {
2583
2848
  lease.released = true;
2584
2849
  await lease.updates;
2585
- await cleanupQueueOwnerFiles(lease.sessionId, lease.socketPath, () => ownsQueueLease(lease));
2850
+ await cleanupQueueOwnerFiles(lease.sessionId, lease.socketPath, () => ownsQueueLease(lease), lease);
2851
+ }
2852
+ function unverifiedQueueOwnerError(owner) {
2853
+ 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.`, {
2854
+ detailCode: "QUEUE_OWNER_IDENTITY_UNVERIFIED",
2855
+ origin: "queue",
2856
+ retryable: true
2857
+ });
2858
+ }
2859
+ async function dispatchVerifiedQueueOwnerSignal(owner, signal, requireStale, retirement) {
2860
+ return await withQueueLeaseMutation(owner.sessionId, async () => {
2861
+ const current = await readQueueOwnerRecord(owner.sessionId);
2862
+ retirement.leaseReleased = current?.pid !== owner.pid;
2863
+ if (!matchesQueueOwner(current, owner) || requireStale && signal === "SIGTERM" && !isQueueOwnerHeartbeatStale(current)) return false;
2864
+ const identity = await observeProcessIncarnation(current.pid, current.processIdentity);
2865
+ if (identity !== "matching") {
2866
+ retirement.gone = identity === "gone";
2867
+ retirement.unverified = identity === "unknown";
2868
+ return false;
2869
+ }
2870
+ return await dispatchQueueOwnerSignal(owner.pid, signal);
2871
+ });
2872
+ }
2873
+ async function retireQueueOwner(owner, requireStale) {
2874
+ const deadline = performance.now() + PROCESS_SIGTERM_GRACE_MS + PROCESS_SIGKILL_GRACE_MS;
2875
+ const retirement = {
2876
+ gone: false,
2877
+ unverified: false,
2878
+ leaseReleased: false
2879
+ };
2880
+ await terminateWithDispatch(owner.pid, (signal) => dispatchVerifiedQueueOwnerSignal(owner, signal, requireStale, retirement), isProcessDefinitelyDead);
2881
+ if (retirement.unverified && requireStale) throw unverifiedQueueOwnerError(owner);
2882
+ if (retirement.leaseReleased || retirement.unverified) await waitForProcessExit(owner.pid, Math.max(0, deadline - performance.now()), isProcessDefinitelyDead);
2883
+ const gone = retirement.gone || await observeProcessIncarnation(owner.pid, owner.processIdentity) === "gone";
2884
+ if (retirement.unverified && !gone) throw unverifiedQueueOwnerError(owner);
2885
+ return gone;
2586
2886
  }
2587
2887
  async function terminateQueueOwnerForSession(sessionId, expectedOwner, requireStale = false) {
2588
2888
  await settlePendingQueueLeaseGuard(sessionId);
2589
2889
  const owner = expectedOwner ?? await readQueueOwnerRecord(sessionId);
2590
- 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));
2890
+ if (!owner || owner.sessionId !== sessionId || owner.pid === process.pid) return;
2891
+ if (!await retireQueueOwner(owner, requireStale)) return;
2892
+ await cleanupQueueOwnerFiles(sessionId, owner.socketPath, () => ownsQueueLease(owner));
2597
2893
  }
2598
2894
  async function waitMs(ms) {
2599
2895
  await new Promise((resolve) => {
@@ -3514,7 +3810,10 @@ async function tryCloseSessionOnRunningOwner(options) {
3514
3810
  verbose: options.verbose,
3515
3811
  requestName: "close_session",
3516
3812
  logPrefix: "[acpx] requested session/close on active owner pid",
3517
- submit: (owner) => submitCloseSessionToQueueOwner(owner, options.timeoutMs)
3813
+ submit: (owner) => {
3814
+ options.onOwnerSelected?.(owner);
3815
+ return submitCloseSessionToQueueOwner(owner, options.timeoutMs);
3816
+ }
3518
3817
  });
3519
3818
  }
3520
3819
  async function tryCancelOnRunningOwner(options) {
@@ -3562,6 +3861,6 @@ async function trySetConfigOptionOnRunningOwner(sessionId, configId, value, time
3562
3861
  });
3563
3862
  }
3564
3863
  //#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 };
3864
+ export { measurePerf as $, findGitRepositoryRoot as A, PERMISSION_POLICY_ACTIONS as At, writeSessionRecord as B, PermissionDeniedError as Bt, getUnsupportedPromptContentMessage as C, AUTH_POLICIES as Ct, textPrompt as D, OUTPUT_ERROR_ORIGINS as Dt, promptToDisplayText as E, OUTPUT_ERROR_CODES as Et, listSessionsForAgent as F, AgentStartupError as Ft, normalizeAgentSessionId as G, SessionModelReplayError as Gt, parseSessionRecord as H, QueueConnectionError as Ht, normalizeName as I, AuthPolicyError as It, SessionWatchError as J, UnsupportedPromptContentError as Jt, SESSION_JOURNAL_SCHEMA as K, SessionNotFoundError as Kt, pruneSessions as L, ClaudeAcpSessionCreateTimeoutError as Lt, findSessionByDirectoryWalk as M, AcpxOperationalError as Mt, isoNow as N, AgentDisconnectedError as Nt, DEFAULT_HISTORY_LIMIT as O, OUTPUT_FORMATS as Ot, listSessions as P, AgentSpawnError as Pt, incrementPerfCounter as Q, readSessionRecord as R, CopilotAcpUnsupportedError as Rt, PromptInputValidationError as S, isAcpResourceNotFoundError as St, parsePromptSource as T, NON_INTERACTIVE_PERMISSION_POLICIES as Tt, serializeSessionRecordForDisk as U, SessionConfigOptionReplayError as Ut, assertPersistedKeyPolicy as V, PermissionPromptUnavailableError as Vt, extractAgentSessionId as W, SessionModeReplayError as Wt, formatPerfMetric as X, watchSession as Y, getPerfMetricsSnapshot as Z, waitMs as _, writePrivateFile as _t, trySetModelOnRunningOwner as a, isAcpJsonRpcMessage as at, lockOwnerPid as b, extractAcpError as bt, queueRequestExceedsLimit as c, parseJsonRpcErrorMessage as ct, readQueueOwnerRecord as d, DEFAULT_EVENT_SEGMENT_MAX_BYTES as dt, recordPerfDuration as et, refreshQueueOwnerLease as f, defaultSessionEventLog as ft, tryAcquireQueueOwnerLease as g, sessionEventSegmentPath as gt, terminateQueueOwnerForSession as h, sessionEventLockPath as ht, trySetModeOnRunningOwner as i, extractSessionUpdateNotification as it, findSession as j, SESSION_RECORD_SCHEMA as jt, absolutePath as k, PERMISSION_MODES as kt, parseQueueRequest as l, parsePermissionNotice as lt, terminateProcess as m, sessionEventActivePath as mt, tryCloseSessionOnRunningOwner as n, setPerfGauge as nt, trySubmitToRunningOwner as o, isAcpMessageObject as ot, releaseQueueOwnerLease as p, sessionBaseDir as pt, SessionJournalReader as q, SessionResumeRequiredError as qt, trySetConfigOptionOnRunningOwner as r, startPerfTimer as rt, queueRequestByteLimit as s, isSessionUpdateNotification as st, tryCancelOnRunningOwner as t, resetPerfMetrics as tt, probeQueueOwnerHealth as u, parsePromptStopReason as ut, QueueLeaseGuardSettlementError as v, writePrivateJsonFile as vt, mergePromptSourceWithText as w, EXIT_CODES as wt, readProcessTable as x, formatUnknownErrorMessage as xt, createLockOwner as y, isProcessAlive as yt, resolveSessionRecord as z, GeminiAcpStartupTimeoutError as zt };
3566
3865
 
3567
- //# sourceMappingURL=ipc-BJrvfNXr.js.map
3866
+ //# sourceMappingURL=ipc-fMgj5vrO.js.map