acpx 0.19.1 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,7 @@ import { createHash, randomInt, randomUUID } from "node:crypto";
11
11
  import { isHardlinkFallbackError } from "@openclaw/fs-safe/durability";
12
12
  import { acquireFileLock } from "@openclaw/fs-safe/file-lock";
13
13
  import net from "node:net";
14
+ import { resolveSecureTempRoot } from "@openclaw/fs-safe/secure-temp-root";
14
15
  //#region src/errors.ts
15
16
  var AcpxOperationalError = class extends Error {
16
17
  outputCode;
@@ -231,12 +232,12 @@ const SESSION_RECORD_SCHEMA = "acpx.session.v1";
231
232
  //#endregion
232
233
  //#region src/acp/error-shapes.ts
233
234
  const RESOURCE_NOT_FOUND_ACP_CODES = /* @__PURE__ */ new Set([-32001, -32002]);
234
- function asRecord$4(value) {
235
+ function asRecord$5(value) {
235
236
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
236
237
  return value;
237
238
  }
238
239
  function toAcpErrorPayload(value) {
239
- const record = asRecord$4(value);
240
+ const record = asRecord$5(value);
240
241
  if (!record) return;
241
242
  if (typeof record.code !== "number" || !Number.isFinite(record.code)) return;
242
243
  if (typeof record.message !== "string" || record.message.length === 0) return;
@@ -250,7 +251,7 @@ function extractAcpErrorInternal(value, depth) {
250
251
  if (depth > 5) return;
251
252
  const direct = toAcpErrorPayload(value);
252
253
  if (direct) return direct;
253
- const record = asRecord$4(value);
254
+ const record = asRecord$5(value);
254
255
  if (!record) return;
255
256
  return extractNestedAcpError(record, depth);
256
257
  }
@@ -285,7 +286,7 @@ function hasSessionNotFoundHint(value, depth = 0) {
285
286
  if (depth > 4) return false;
286
287
  if (isSessionNotFoundText(value)) return true;
287
288
  if (Array.isArray(value)) return value.some((entry) => hasSessionNotFoundHint(entry, depth + 1));
288
- const record = asRecord$4(value);
289
+ const record = asRecord$5(value);
289
290
  if (!record) return false;
290
291
  return Object.values(record).some((entry) => hasSessionNotFoundHint(entry, depth + 1));
291
292
  }
@@ -302,6 +303,136 @@ function isAcpResourceNotFoundError(error) {
302
303
  return isSessionNotFoundText(formatUnknownErrorMessage(error));
303
304
  }
304
305
  //#endregion
306
+ //#region src/acp/error-normalization.ts
307
+ const AUTH_REQUIRED_ACP_CODES = /* @__PURE__ */ new Set([-32e3]);
308
+ const QUERY_CLOSED_BEFORE_RESPONSE_DETAIL = "query closed before response received";
309
+ function asRecord$4(value) {
310
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
311
+ return value;
312
+ }
313
+ function isAuthRequiredMessage(value) {
314
+ if (!value) return false;
315
+ const normalized = value.toLowerCase();
316
+ return [
317
+ "auth required",
318
+ "authentication required",
319
+ "authorization required",
320
+ "credential required",
321
+ "credentials required",
322
+ "token required",
323
+ "login required"
324
+ ].some((needle) => normalized.includes(needle));
325
+ }
326
+ function isAcpAuthRequiredPayload(acp) {
327
+ if (!acp) return false;
328
+ if (!AUTH_REQUIRED_ACP_CODES.has(acp.code)) return false;
329
+ if (isAuthRequiredMessage(acp.message)) return true;
330
+ const data = asRecord$4(acp.data);
331
+ if (!data) return false;
332
+ return hasAuthRequiredData(data);
333
+ }
334
+ function hasAuthRequiredData(data) {
335
+ return data.authRequired === true || hasNonEmptyString(data.methodId) || hasNonEmptyArray(data.methods);
336
+ }
337
+ function hasNonEmptyString(value) {
338
+ return typeof value === "string" && value.trim().length > 0;
339
+ }
340
+ function hasNonEmptyArray(value) {
341
+ return Array.isArray(value) && value.length > 0;
342
+ }
343
+ function isOutputErrorCode$1(value) {
344
+ return typeof value === "string" && OUTPUT_ERROR_CODES.includes(value);
345
+ }
346
+ function isOutputErrorOrigin$1(value) {
347
+ return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
348
+ }
349
+ function readOutputErrorMeta(error) {
350
+ const record = asRecord$4(error);
351
+ if (!record) return {};
352
+ return {
353
+ outputCode: isOutputErrorCode$1(record.outputCode) ? record.outputCode : void 0,
354
+ detailCode: typeof record.detailCode === "string" && record.detailCode.trim().length > 0 ? record.detailCode : void 0,
355
+ origin: isOutputErrorOrigin$1(record.origin) ? record.origin : void 0,
356
+ retryable: typeof record.retryable === "boolean" ? record.retryable : void 0,
357
+ acp: extractAcpError(record.acp)
358
+ };
359
+ }
360
+ function isTimeoutLike(error) {
361
+ return error instanceof Error && error.name === "TimeoutError";
362
+ }
363
+ function isNoSessionLike(error) {
364
+ return error instanceof Error && error.name === "NoSessionError";
365
+ }
366
+ function isUsageLike(error) {
367
+ if (!(error instanceof Error)) return false;
368
+ return error.name === "CommanderError" || error.name === "InvalidArgumentError" || asRecord$4(error)?.code === "commander.invalidArgument";
369
+ }
370
+ function formatErrorMessage(error) {
371
+ return formatUnknownErrorMessage(error);
372
+ }
373
+ function isAcpQueryClosedBeforeResponseError(error) {
374
+ const acp = extractAcpError(error);
375
+ if (!acp || acp.code !== -32603) return false;
376
+ const details = asRecord$4(acp.data)?.details;
377
+ if (typeof details !== "string") return false;
378
+ return details.toLowerCase().includes(QUERY_CLOSED_BEFORE_RESPONSE_DETAIL);
379
+ }
380
+ function mapErrorCode(error) {
381
+ if (error instanceof PermissionPromptUnavailableError) return "PERMISSION_PROMPT_UNAVAILABLE";
382
+ if (error instanceof PermissionDeniedError) return "PERMISSION_DENIED";
383
+ if (isTimeoutLike(error)) return "TIMEOUT";
384
+ if (isNoSessionLike(error) || isAcpResourceNotFoundError(error)) return "NO_SESSION";
385
+ if (isUsageLike(error)) return "USAGE";
386
+ }
387
+ function normalizeOutputError(error, options = {}) {
388
+ const meta = readOutputErrorMeta(error);
389
+ const code = resolveOutputErrorCode(error, options, meta);
390
+ const acp = options.acp ?? meta.acp ?? extractAcpError(error);
391
+ return {
392
+ code,
393
+ message: formatErrorMessage(error),
394
+ detailCode: resolveDetailCode(error, acp, options, meta),
395
+ origin: meta.origin ?? options.origin,
396
+ retryable: meta.retryable ?? options.retryable,
397
+ acp
398
+ };
399
+ }
400
+ function resolveOutputErrorCode(error, options, meta) {
401
+ const code = meta.outputCode ?? mapErrorCode(error) ?? options.defaultCode ?? "RUNTIME";
402
+ if (code === "RUNTIME" && isAcpResourceNotFoundError(error)) return "NO_SESSION";
403
+ return code;
404
+ }
405
+ function resolveDetailCode(error, acp, options, meta) {
406
+ return meta.detailCode ?? options.detailCode ?? (error instanceof AuthPolicyError || isAcpAuthRequiredPayload(acp) ? "AUTH_REQUIRED" : void 0);
407
+ }
408
+ /**
409
+ * Returns true when an error from `client.prompt()` looks transient and
410
+ * can reasonably be retried (e.g. model-API 400/500, network hiccups that
411
+ * surface as ACP internal errors).
412
+ *
413
+ * Errors that are definitively non-recoverable (auth, missing session,
414
+ * invalid params, timeout, permission) return false.
415
+ */
416
+ function isRetryablePromptError(error) {
417
+ if (isNonRetryablePromptError(error)) return false;
418
+ const acp = extractAcpError(error);
419
+ if (!acp) return false;
420
+ return acp.code === -32603 || acp.code === -32700;
421
+ }
422
+ function isNonRetryablePromptError(error) {
423
+ return error instanceof PermissionDeniedError || error instanceof PermissionPromptUnavailableError || isTimeoutLike(error) || isNoSessionLike(error) || isUsageLike(error);
424
+ }
425
+ function exitCodeForOutputErrorCode(code) {
426
+ switch (code) {
427
+ case "USAGE": return EXIT_CODES.USAGE;
428
+ case "TIMEOUT": return EXIT_CODES.TIMEOUT;
429
+ case "NO_SESSION": return EXIT_CODES.NO_SESSION;
430
+ case "PERMISSION_DENIED":
431
+ case "PERMISSION_PROMPT_UNAVAILABLE": return EXIT_CODES.PERMISSION_DENIED;
432
+ default: return EXIT_CODES.ERROR;
433
+ }
434
+ }
435
+ //#endregion
305
436
  //#region src/process-liveness.ts
306
437
  function isProcessAlive(pid) {
307
438
  if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
@@ -686,7 +817,15 @@ function parseCursor(recordId, cursor) {
686
817
  return parsed.data[1];
687
818
  }
688
819
  function fileIdentity(stat) {
689
- return `${stat.dev}:${stat.ino}:${stat.birthtimeMs}`;
820
+ return `${stat.dev}:${stat.ino}:${stat.birthtimeNs}`;
821
+ }
822
+ async function pathIdentity(filePath) {
823
+ try {
824
+ return fileIdentity(await fs$1.lstat(filePath, { bigint: true }));
825
+ } catch (error) {
826
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return;
827
+ throw error;
828
+ }
690
829
  }
691
830
  function emptySegment() {
692
831
  return {
@@ -718,12 +857,9 @@ var SessionJournalReader = class {
718
857
  const segments = [];
719
858
  try {
720
859
  const paths = await this.openPresentSegments(segments);
721
- const current = await Promise.all(paths.map((filePath) => statRegularFile(filePath)));
860
+ const current = await Promise.all(paths.map(pathIdentity));
722
861
  const identities = new Map(segments.map((entry) => [entry.filePath, entry.identity]));
723
- if (paths.every((filePath, index) => {
724
- const entry = current[index];
725
- return (entry.missing ? void 0 : fileIdentity(entry.stat)) === identities.get(filePath);
726
- })) return segments;
862
+ if (paths.every((filePath, index) => current[index] === identities.get(filePath))) return segments;
727
863
  } catch (error) {
728
864
  if (!retryableSnapshotError(error)) {
729
865
  await this.closeSnapshot(segments);
@@ -744,11 +880,16 @@ var SessionJournalReader = class {
744
880
  symlinks: "reject",
745
881
  nonBlockingRead: true
746
882
  });
747
- return {
748
- filePath,
749
- opened,
750
- identity: fileIdentity(opened.stat)
751
- };
883
+ try {
884
+ return {
885
+ filePath,
886
+ opened,
887
+ identity: fileIdentity(await opened.handle.stat({ bigint: true }))
888
+ };
889
+ } catch (error) {
890
+ await opened[Symbol.asyncDispose]();
891
+ throw error;
892
+ }
752
893
  }));
753
894
  let failure;
754
895
  for (const result of results) if (result.status === "fulfilled") segments.push(result.value);
@@ -978,6 +1119,7 @@ async function* iterateJournal(record, options) {
978
1119
  let after = options.cursor === void 0 ? -1 : parseCursor(record.acpxRecordId, options.cursor);
979
1120
  const reader = new SessionJournalReader(record);
980
1121
  let first = true;
1122
+ const observation = {};
981
1123
  for (;;) {
982
1124
  if (signal.aborted) return;
983
1125
  const snapshot = await settleUnlessAborted(reader.read(signal, {
@@ -992,16 +1134,34 @@ async function* iterateJournal(record, options) {
992
1134
  after = sequence;
993
1135
  yield event;
994
1136
  }
995
- if (!await continueAfterPage(record, snapshot, options)) return;
1137
+ if (!await continueAfterPage(record, snapshot, options, observation)) return;
996
1138
  }
997
1139
  }
998
- async function continueAfterPage(record, snapshot, options) {
1140
+ async function continueAfterPage(record, snapshot, options, observation) {
1141
+ if (options.signal.aborted || confirmsTerminalObservation(snapshot, observation)) return false;
999
1142
  if (!snapshot.hasMore && options.continueWatching) {
1000
- if (!await settleUnlessAborted(options.continueWatching(record, snapshot.requestId), options.signal)) return false;
1143
+ const decision = await settleUnlessAborted(options.continueWatching(record, snapshot.requestId), options.signal).catch((error) => {
1144
+ if (error instanceof SessionWatchError && error.code === "WATCH_OUTCOME_UNKNOWN") return error;
1145
+ throw error;
1146
+ });
1147
+ if (decision !== true) {
1148
+ observation.terminal = {
1149
+ requestId: snapshot.requestId,
1150
+ error: decision instanceof SessionWatchError ? decision : void 0
1151
+ };
1152
+ return !options.signal.aborted;
1153
+ }
1001
1154
  }
1002
1155
  await settleUnlessAborted(setTimeout$1(snapshot.hasMore ? 0 : 100, void 0, { signal: options.signal }), options.signal);
1003
1156
  return !options.signal.aborted;
1004
1157
  }
1158
+ function confirmsTerminalObservation(snapshot, observation) {
1159
+ const terminal = observation.terminal;
1160
+ delete observation.terminal;
1161
+ if (!terminal || snapshot.events.length > 0 || snapshot.hasMore || snapshot.requestId !== terminal.requestId) return false;
1162
+ if (terminal.error) throw terminal.error;
1163
+ return true;
1164
+ }
1005
1165
  function assertRetainedCursor(after, snapshot, first) {
1006
1166
  if (after < 0) return;
1007
1167
  if (after > snapshot.sequence) throw new SessionWatchError(first ? "WATCH_CURSOR_FUTURE" : "WATCH_CURSOR_EXPIRED", "Cursor is beyond the available session journal");
@@ -2697,16 +2857,30 @@ function retirementTimeRemaining(deadline, maximumMs = 2e3) {
2697
2857
  if (remaining <= 0) throw queueRetirementIncomplete();
2698
2858
  return Math.min(maximumMs, remaining);
2699
2859
  }
2700
- async function captureQueueRetirementReceipt(owner, previous, rootRunning, deadline) {
2860
+ async function readRetirementSnapshot(deadline) {
2861
+ if (process.platform !== "win32") throw queueRetirementIncomplete();
2862
+ return await readProcessTable(retirementTimeRemaining(deadline)).catch(() => {
2863
+ throw queueRetirementIncomplete();
2864
+ });
2865
+ }
2866
+ function observeRetirementSnapshot(witness, table) {
2867
+ if (process.platform !== "win32" || !isWindowsRetirementIdentity(witness.processIdentity)) return "unknown";
2868
+ const observed = table?.get(witness.pid);
2869
+ const comparison = observed ? compareProcessBirthIdentity(witness.processIdentity, observed.birth) : "unknown";
2870
+ if (comparison === "different" || isProcessDefinitelyDead(witness.pid)) return "gone";
2871
+ return witness.pid === process.pid ? "unknown" : comparison;
2872
+ }
2873
+ function isWindowsRetirementIdentity(identity) {
2874
+ return identity === void 0 || identity.kind === "windows-creation";
2875
+ }
2876
+ function captureQueueRetirementReceipt(owner, previous, rootRunning, snapshot) {
2701
2877
  const root = parseWitness({
2702
2878
  pid: owner.pid,
2703
2879
  processIdentity: owner.processIdentity
2704
2880
  });
2705
2881
  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 : []);
2882
+ const saved = previous ? previous.descendants : [];
2883
+ const table = new Map(snapshot);
2710
2884
  const owned = matchingSnapshotPids(table, rootRunning ? [root, ...saved] : saved);
2711
2885
  if (rootRunning && !owned.has(root.pid)) throw queueRetirementIncomplete();
2712
2886
  table.delete(1);
@@ -2716,7 +2890,7 @@ async function captureQueueRetirementReceipt(owner, previous, rootRunning, deadl
2716
2890
  const receipt = {
2717
2891
  ownerGeneration: owner.ownerGeneration,
2718
2892
  root,
2719
- descendants: mergeSnapshotDescendants(table, owned, saved)
2893
+ descendants: unsettledSnapshotWitnesses(snapshot, mergeSnapshotDescendants(table, owned, saved))
2720
2894
  };
2721
2895
  if (!parseQueueRetirementReceipt(receipt, owner)) throw queueRetirementIncomplete();
2722
2896
  return receipt;
@@ -2752,7 +2926,11 @@ async function observeRetirementWitness(witness, deadline) {
2752
2926
  }
2753
2927
  async function assertQueueRetirementComplete(receipt, deadline) {
2754
2928
  if (receipt === null) throw queueRetirementIncomplete();
2755
- for (const witness of receipt?.descendants ?? []) if (await observeRetirementWitness(witness, deadline) !== "gone") throw queueRetirementIncomplete();
2929
+ const witnesses = receipt?.descendants ?? [];
2930
+ if (witnesses.length === 0) return;
2931
+ const live = witnesses.filter((witness) => observeRetirementSnapshot(witness, void 0) !== "gone");
2932
+ const table = live.length > 0 ? await readRetirementSnapshot(deadline) : void 0;
2933
+ for (const witness of live) if (observeRetirementSnapshot(witness, table) !== "gone") throw queueRetirementIncomplete();
2756
2934
  }
2757
2935
  //#endregion
2758
2936
  //#region src/session/queue/lease-mutation.ts
@@ -3255,17 +3433,37 @@ function unverifiedQueueOwnerError(owner) {
3255
3433
  function retirementDeadline() {
3256
3434
  return performance.now() + PROCESS_SIGTERM_GRACE_MS + PROCESS_SIGKILL_GRACE_MS;
3257
3435
  }
3436
+ async function createQueueOwnerRetirement(deadline) {
3437
+ const snapshot = process.platform === "win32" ? await readRetirementSnapshot(deadline).catch(() => void 0) : void 0;
3438
+ return {
3439
+ gone: false,
3440
+ unverified: false,
3441
+ leaseReleased: false,
3442
+ deadline,
3443
+ snapshot,
3444
+ capturedIdentity: process.platform === "win32" ? { processIdentity: snapshot?.get(process.pid)?.birth } : void 0
3445
+ };
3446
+ }
3258
3447
  async function observeRetiringOwner(owner, deadline) {
3259
3448
  return await observeProcessIncarnation(owner.pid, owner.processIdentity, process.platform === "win32" ? retirementTimeRemaining(deadline) : void 0);
3260
3449
  }
3261
3450
  function retirementBlockedByHeartbeat(owner, signal, requireStale) {
3262
3451
  return requireStale && owner.retirement === void 0 && signal === "SIGTERM" && !isQueueOwnerHeartbeatStale(owner);
3263
3452
  }
3264
- async function prepareWindowsRetirement(owner, deadline) {
3265
- if (owner.retirement === null) throw queueRetirementIncomplete();
3266
- owner.retirement = await captureQueueRetirementReceipt(owner, owner.retirement, true, deadline);
3453
+ async function prepareWindowsRetirement(owner, table, deadline) {
3454
+ if (owner.retirement === null || !table) throw queueRetirementIncomplete();
3455
+ owner.retirement = captureQueueRetirementReceipt(owner, owner.retirement, true, table);
3267
3456
  await replaceQueueOwnerRecord(owner);
3268
- return await observeRetirementWitness(owner.retirement.root, deadline) === "matching";
3457
+ return await observeRetirementWitness(owner.retirement.root, deadline);
3458
+ }
3459
+ async function prepareRetiringOwner(owner, signal, retirement) {
3460
+ const windows = process.platform === "win32";
3461
+ const table = windows && signal !== "SIGTERM" ? await readRetirementSnapshot(retirement.deadline) : retirement.snapshot;
3462
+ let identity = windows ? observeRetirementSnapshot(owner, table) : await observeRetiringOwner(owner, retirement.deadline);
3463
+ if (identity === "matching" && windows) identity = await prepareWindowsRetirement(owner, table, retirement.deadline);
3464
+ retirement.gone = identity === "gone";
3465
+ retirement.unverified = identity === "unknown";
3466
+ return identity === "matching";
3269
3467
  }
3270
3468
  async function dispatchVerifiedQueueOwnerSignal(owner, signal, requireStale, retirement) {
3271
3469
  return await withQueueLeaseMutation(owner.sessionId, async () => {
@@ -3273,26 +3471,15 @@ async function dispatchVerifiedQueueOwnerSignal(owner, signal, requireStale, ret
3273
3471
  retirement.leaseReleased = current?.pid !== owner.pid;
3274
3472
  if (!matchesQueueOwner(current, owner) || retirementBlockedByHeartbeat(current, signal, requireStale)) return false;
3275
3473
  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;
3474
+ if (!await prepareRetiringOwner(current, signal, retirement)) return false;
3283
3475
  return await dispatchQueueOwnerSignal(owner.pid, signal, retirement.deadline);
3284
- });
3476
+ }, { capturedIdentity: retirement.capturedIdentity });
3285
3477
  }
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);
3478
+ async function retireQueueOwner(owner, requireStale, retirement) {
3479
+ const { deadline } = retirement;
3480
+ const exited = await terminateWithDispatch(owner.pid, (signal) => dispatchVerifiedQueueOwnerSignal(owner, signal, requireStale, retirement), isProcessDefinitelyDead, process.platform === "win32" ? deadline : Infinity);
3294
3481
  if (await waitForReleasedOwner(owner, requireStale, retirement)) return false;
3295
- const gone = retirement.gone || await observeRetiringOwner(owner, deadline) === "gone";
3482
+ const gone = retirement.gone || process.platform === "win32" && exited || await observeRetiringOwner(owner, deadline) === "gone";
3296
3483
  if (retirement.unverified && !gone) throw unverifiedQueueOwnerError(owner);
3297
3484
  return gone;
3298
3485
  }
@@ -3307,44 +3494,80 @@ async function waitForReleasedOwner(owner, requireStale, retirement) {
3307
3494
  }
3308
3495
  return false;
3309
3496
  }
3310
- async function retireRecordedDescendants(owner, deadline) {
3497
+ async function retireRecordedDescendants(owner, retirement) {
3311
3498
  await withQueueLeaseMutation(owner.sessionId, async () => {
3312
3499
  const current = await readQueueOwnerRecord(owner.sessionId);
3313
3500
  if (!matchesQueueOwner(current, owner) || current.retirement === void 0) return;
3314
3501
  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;
3502
+ await retireSnapshotDescendants(current, current.retirement, retirement.deadline);
3503
+ }, { capturedIdentity: retirement.capturedIdentity });
3504
+ }
3505
+ function hasNewRetirementWitness(previous, receipt) {
3506
+ const saved = new Map(previous.descendants.map((witness) => [witness.pid, witness.processIdentity]));
3507
+ return receipt.descendants.some((witness) => compareProcessBirthIdentity(saved.get(witness.pid), witness.processIdentity) !== "matching");
3508
+ }
3509
+ async function retireSnapshotDescendants(owner, receipt, deadline) {
3510
+ while (receipt.descendants.length > 0) {
3511
+ const table = await readRetirementSnapshot(deadline);
3512
+ const discovered = captureQueueRetirementReceipt(owner, receipt, false, table);
3513
+ if (hasNewRetirementWitness(receipt, discovered)) {
3514
+ receipt = discovered;
3515
+ owner.retirement = receipt;
3516
+ await replaceQueueOwnerRecord(owner);
3517
+ continue;
3518
+ }
3519
+ signalRecordedDescendants(discovered, table, deadline);
3520
+ await waitForRecordedDescendants(discovered, deadline);
3521
+ discovered.descendants = discovered.descendants.filter((witness) => !isProcessDefinitelyDead(witness.pid));
3522
+ receipt = discovered;
3523
+ owner.retirement = receipt;
3524
+ await replaceQueueOwnerRecord(owner);
3327
3525
  }
3328
- return false;
3329
3526
  }
3330
- async function signalRecordedDescendants(receipt, deadline) {
3527
+ function leafFirstRetirementWitnesses(receipt, table) {
3528
+ const children = new Map(receipt.descendants.map((witness) => [witness.pid, 0]));
3331
3529
  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
- }
3530
+ const parent = retirementParent(witness.pid, table);
3531
+ const count = children.get(parent);
3532
+ if (count !== void 0) children.set(parent, count + 1);
3533
+ }
3534
+ const pending = receipt.descendants.filter((witness) => children.get(witness.pid) === 0);
3535
+ const byPid = new Map(receipt.descendants.map((witness) => [witness.pid, witness]));
3536
+ for (let index = 0; index < pending.length; index += 1) {
3537
+ const parent = retirementParent(pending[index].pid, table);
3538
+ const count = children.get(parent);
3539
+ if (count !== void 0) {
3540
+ const remaining = count - 1;
3541
+ children.set(parent, remaining);
3542
+ if (remaining === 0) pending.push(byPid.get(parent));
3341
3543
  }
3342
3544
  }
3545
+ if (pending.length !== receipt.descendants.length) throw queueRetirementIncomplete();
3546
+ return pending;
3547
+ }
3548
+ function retirementParent(pid, table) {
3549
+ const identity = table.get(pid);
3550
+ return identity && !hasStaleWindowsParent(identity, table.get(identity.parentPid)) ? identity.parentPid : 0;
3551
+ }
3552
+ function signalRecordedDescendants(receipt, table, deadline) {
3553
+ if (receipt.descendants.some((witness) => observeRetirementSnapshot(witness, table) === "unknown")) throw queueRetirementIncomplete();
3554
+ const ordered = leafFirstRetirementWitnesses(receipt, table);
3555
+ let failure;
3556
+ for (const witness of ordered) {
3557
+ if (observeRetirementSnapshot(witness, table) !== "matching") continue;
3558
+ retirementTimeRemaining(deadline);
3559
+ try {
3560
+ process.kill(witness.pid, "SIGKILL");
3561
+ } catch (error) {
3562
+ if (!isProcessDefinitelyDead(witness.pid)) failure ??= error;
3563
+ }
3564
+ }
3565
+ if (failure) throw failure;
3343
3566
  }
3344
3567
  async function waitForRecordedDescendants(receipt, deadline) {
3345
- while (true) {
3346
- if (!await hasSurvivingRetirementDescendant(receipt, deadline)) return;
3347
- await waitMs(Math.min(1e3, retirementTimeRemaining(deadline)));
3568
+ for (let waited = 0; waited < 1e3; waited += PROCESS_POLL_MS) {
3569
+ if (receipt.descendants.every((witness) => isProcessDefinitelyDead(witness.pid))) return;
3570
+ await waitMs(Math.min(PROCESS_POLL_MS, retirementTimeRemaining(deadline)));
3348
3571
  }
3349
3572
  }
3350
3573
  async function assertNoPendingRetirement(owner) {
@@ -3360,12 +3583,13 @@ async function terminateQueueOwnerForSession(sessionId, expectedOwner, requireSt
3360
3583
  return;
3361
3584
  }
3362
3585
  const deadline = retirementDeadline();
3363
- if (!await retireQueueOwner(owner, requireStale, deadline)) {
3586
+ const retirement = await createQueueOwnerRetirement(deadline);
3587
+ if (!await retireQueueOwner(owner, requireStale, retirement)) {
3364
3588
  await assertNoPendingRetirement(owner);
3365
3589
  return;
3366
3590
  }
3367
- await retireRecordedDescendants(owner, deadline);
3368
- await cleanupQueueOwnerFiles(sessionId, owner.socketPath, () => ownsQueueLease(owner), void 0, deadline);
3591
+ await retireRecordedDescendants(owner, retirement);
3592
+ await cleanupQueueOwnerFiles(sessionId, owner.socketPath, () => ownsQueueLease(owner), retirement.capturedIdentity, deadline);
3369
3593
  }
3370
3594
  async function waitMs(ms) {
3371
3595
  await new Promise((resolve) => {
@@ -3870,6 +4094,610 @@ function assertQueueRequestSize(line) {
3870
4094
  });
3871
4095
  }
3872
4096
  //#endregion
4097
+ //#region src/session/queue/socket-output.ts
4098
+ const OUTPUT_CHUNK_BYTES = 65536;
4099
+ const DRAIN_TIMEOUT_MS = 1e3;
4100
+ const DEFAULT_LIMITS = Object.freeze({
4101
+ observerBytes: 67108864,
4102
+ totalBytes: 268435456,
4103
+ observers: 64
4104
+ });
4105
+ var QueueOutputBudget = class {
4106
+ limits;
4107
+ bytes = 0;
4108
+ files = 0;
4109
+ observers = 0;
4110
+ constructor(limits = DEFAULT_LIMITS) {
4111
+ this.limits = limits;
4112
+ }
4113
+ acquire() {
4114
+ if (this.files >= this.limits.observers) throw new Error("Queue output spool descriptor limit reached");
4115
+ this.files += 1;
4116
+ }
4117
+ grow(bytes) {
4118
+ if (bytes > this.limits.totalBytes - this.bytes) throw new Error("Queue output spool storage limit reached");
4119
+ this.bytes += bytes;
4120
+ }
4121
+ release(bytes) {
4122
+ this.bytes -= bytes;
4123
+ this.files -= 1;
4124
+ }
4125
+ acquireObserver() {
4126
+ if (this.observers >= this.limits.observers) throw new Error("Queue blocked observer limit reached");
4127
+ this.observers += 1;
4128
+ }
4129
+ releaseObserver() {
4130
+ this.observers -= 1;
4131
+ }
4132
+ };
4133
+ var OutputRing = class {
4134
+ budget;
4135
+ fd;
4136
+ physicalBytes = 0;
4137
+ readOffset = 0;
4138
+ length = 0;
4139
+ constructor(budget) {
4140
+ this.budget = budget;
4141
+ budget.acquire();
4142
+ try {
4143
+ const root = resolveSecureTempRoot({ fallbackPrefix: "acpx-output" });
4144
+ const file = path.join(root, `${randomUUID()}.tmp`);
4145
+ this.fd = fs.openSync(file, "wx+", 384);
4146
+ fs.unlinkSync(file);
4147
+ } catch (error) {
4148
+ if (this.fd === void 0) budget.release(0);
4149
+ else this.close();
4150
+ throw error;
4151
+ }
4152
+ }
4153
+ append(bytes, offset) {
4154
+ const capacity = this.budget.limits.observerBytes;
4155
+ if (bytes.length - offset > capacity - this.length) throw new Error("Queue observer output backlog limit reached");
4156
+ while (offset < bytes.length) {
4157
+ const position = (this.readOffset + this.length) % capacity;
4158
+ const count = Math.min(OUTPUT_CHUNK_BYTES, bytes.length - offset, capacity - position);
4159
+ const growth = Math.max(0, position + count - this.physicalBytes);
4160
+ this.budget.grow(growth);
4161
+ this.physicalBytes += growth;
4162
+ const written = fs.writeSync(this.fd, bytes, offset, count, position);
4163
+ if (written <= 0) throw new Error("Queue output spool write made no progress");
4164
+ offset += written;
4165
+ this.length += written;
4166
+ }
4167
+ }
4168
+ read() {
4169
+ const capacity = this.budget.limits.observerBytes;
4170
+ const bytes = Buffer.allocUnsafeSlow(Math.min(OUTPUT_CHUNK_BYTES, this.length, capacity - this.readOffset));
4171
+ let offset = 0;
4172
+ while (offset < bytes.length) {
4173
+ const read = fs.readSync(this.fd, bytes, offset, bytes.length - offset, this.readOffset + offset);
4174
+ if (read <= 0) throw new Error("Queue output spool ended before its committed bytes");
4175
+ offset += read;
4176
+ }
4177
+ this.readOffset = (this.readOffset + bytes.length) % capacity;
4178
+ this.length -= bytes.length;
4179
+ return bytes;
4180
+ }
4181
+ close() {
4182
+ const fd = this.fd;
4183
+ this.fd = void 0;
4184
+ if (fd !== void 0) {
4185
+ fs.closeSync(fd);
4186
+ this.budget.release(this.physicalBytes);
4187
+ }
4188
+ }
4189
+ };
4190
+ var QueueSocketOutput = class {
4191
+ socket;
4192
+ budget;
4193
+ ring;
4194
+ blocked = false;
4195
+ ending = false;
4196
+ disposed = false;
4197
+ reserved = false;
4198
+ pendingWrites = 0;
4199
+ constructor(socket, budget) {
4200
+ this.socket = socket;
4201
+ this.budget = budget;
4202
+ socket.on("drain", this.onDrain);
4203
+ socket.once("close", () => {
4204
+ this.disposed = true;
4205
+ socket.off("drain", this.onDrain);
4206
+ this.closeSpool();
4207
+ this.releaseObserver();
4208
+ });
4209
+ }
4210
+ send(message) {
4211
+ if (this.ending || this.socket.destroyed || !this.socket.writable) return;
4212
+ try {
4213
+ this.writeFrame(Buffer.from(`${JSON.stringify(message)}\n`));
4214
+ } catch (error) {
4215
+ this.fail(error);
4216
+ }
4217
+ }
4218
+ writeFrame(bytes) {
4219
+ let offset = 0;
4220
+ while (!this.blocked && offset < bytes.length) {
4221
+ const end = Math.min(offset + OUTPUT_CHUNK_BYTES, bytes.length);
4222
+ const chunk = Buffer.allocUnsafeSlow(end - offset);
4223
+ bytes.copy(chunk, 0, offset, end);
4224
+ this.write(chunk);
4225
+ offset = end;
4226
+ }
4227
+ if (offset < bytes.length) {
4228
+ this.ring ??= new OutputRing(this.budget);
4229
+ this.ring.append(bytes, offset);
4230
+ }
4231
+ }
4232
+ end() {
4233
+ if (this.ending || this.socket.destroyed) return;
4234
+ this.ending = true;
4235
+ this.armTimeout();
4236
+ this.finishIfDrained();
4237
+ }
4238
+ write(bytes) {
4239
+ if (!this.reserved) {
4240
+ this.budget.acquireObserver();
4241
+ this.reserved = true;
4242
+ }
4243
+ this.pendingWrites += 1;
4244
+ try {
4245
+ this.blocked = !this.socket.write(bytes, this.onWrite);
4246
+ } catch (error) {
4247
+ this.pendingWrites -= 1;
4248
+ throw error;
4249
+ }
4250
+ if (this.blocked && !this.socket.timeout) this.armTimeout();
4251
+ }
4252
+ onWrite = (error) => {
4253
+ this.pendingWrites -= 1;
4254
+ if (this.disposed) return;
4255
+ if (error) {
4256
+ this.fail(error);
4257
+ return;
4258
+ }
4259
+ this.releaseIfIdle();
4260
+ };
4261
+ armTimeout() {
4262
+ if (process.platform !== "win32") this.socket.setTimeout(DRAIN_TIMEOUT_MS);
4263
+ }
4264
+ onDrain = () => {
4265
+ if (this.disposed) return;
4266
+ this.blocked = false;
4267
+ if (!this.ending) this.socket.setTimeout(0);
4268
+ try {
4269
+ this.pump();
4270
+ this.releaseIfIdle();
4271
+ this.finishIfDrained();
4272
+ } catch (error) {
4273
+ this.fail(error);
4274
+ }
4275
+ };
4276
+ pump() {
4277
+ while (!this.blocked && this.ring && this.ring.length > 0) this.write(this.ring.read());
4278
+ if (this.ring?.length === 0) this.closeSpool();
4279
+ }
4280
+ finishIfDrained() {
4281
+ if (this.ending && !this.ring?.length && !this.socket.writableEnded) this.socket.end(() => this.socket.destroy());
4282
+ }
4283
+ releaseObserver() {
4284
+ if (this.reserved) {
4285
+ this.reserved = false;
4286
+ this.budget.releaseObserver();
4287
+ }
4288
+ }
4289
+ releaseIfIdle() {
4290
+ if (this.pendingWrites === 0 && !this.blocked && !this.ring?.length) this.releaseObserver();
4291
+ }
4292
+ closeSpool() {
4293
+ const ring = this.ring;
4294
+ this.ring = void 0;
4295
+ try {
4296
+ ring?.close();
4297
+ } catch (error) {
4298
+ this.socket.destroy(error instanceof Error ? error : new Error(String(error)));
4299
+ }
4300
+ }
4301
+ fail(error) {
4302
+ this.disposed = true;
4303
+ this.socket.destroy(error instanceof Error ? error : new Error(String(error)));
4304
+ this.closeSpool();
4305
+ }
4306
+ };
4307
+ //#endregion
4308
+ //#region src/session/queue/ipc-server.ts
4309
+ const QUEUE_SOCKET_DRAIN_TIMEOUT_MS = 1e3;
4310
+ function makeQueueOwnerError(requestId, message, detailCode, options = {}) {
4311
+ return {
4312
+ type: "error",
4313
+ requestId,
4314
+ ownerGeneration: void 0,
4315
+ code: "RUNTIME",
4316
+ detailCode,
4317
+ origin: "queue",
4318
+ retryable: options.retryable,
4319
+ message
4320
+ };
4321
+ }
4322
+ function makeQueueOwnerErrorFromUnknown(requestId, error, detailCode, options = {}) {
4323
+ const normalized = normalizeOutputError(error, {
4324
+ defaultCode: "RUNTIME",
4325
+ origin: "queue",
4326
+ detailCode,
4327
+ retryable: options.retryable
4328
+ });
4329
+ return {
4330
+ type: "error",
4331
+ requestId,
4332
+ code: normalized.code,
4333
+ detailCode: normalized.detailCode,
4334
+ origin: normalized.origin,
4335
+ message: normalized.message,
4336
+ retryable: normalized.retryable,
4337
+ acp: normalized.acp
4338
+ };
4339
+ }
4340
+ var SessionQueueOwner = class SessionQueueOwner {
4341
+ server;
4342
+ controlHandlers;
4343
+ ownerGeneration;
4344
+ maxQueueDepth;
4345
+ maxRequestBytes;
4346
+ onQueueDepthChanged;
4347
+ pending = [];
4348
+ activeTask;
4349
+ waiters = [];
4350
+ sockets = /* @__PURE__ */ new Set();
4351
+ taskSockets = /* @__PURE__ */ new Set();
4352
+ drainingSockets = /* @__PURE__ */ new Set();
4353
+ outputBudget = new QueueOutputBudget();
4354
+ outputs = /* @__PURE__ */ new Map();
4355
+ closed = false;
4356
+ constructor(server, controlHandlers, lease, options) {
4357
+ this.server = server;
4358
+ this.controlHandlers = controlHandlers;
4359
+ this.ownerGeneration = lease.ownerGeneration;
4360
+ this.maxQueueDepth = Math.max(1, Math.round(options.maxQueueDepth));
4361
+ this.maxRequestBytes = queueRequestByteLimit(options.maxRequestBytes?.toString());
4362
+ this.onQueueDepthChanged = options.onQueueDepthChanged;
4363
+ }
4364
+ static async start(lease, controlHandlers, options = { maxQueueDepth: 16 }) {
4365
+ const ownerRef = { current: void 0 };
4366
+ const server = net.createServer((socket) => {
4367
+ ownerRef.current?.handleConnection(socket);
4368
+ });
4369
+ ownerRef.current = new SessionQueueOwner(server, controlHandlers, lease, options);
4370
+ await new Promise((resolve, reject) => {
4371
+ const onListening = () => {
4372
+ server.off("error", onError);
4373
+ resolve();
4374
+ };
4375
+ const onError = (error) => {
4376
+ server.off("listening", onListening);
4377
+ reject(error);
4378
+ };
4379
+ server.once("listening", onListening);
4380
+ server.once("error", onError);
4381
+ server.listen(lease.socketPath);
4382
+ });
4383
+ return ownerRef.current;
4384
+ }
4385
+ beginShutdown() {
4386
+ if (this.closed) return;
4387
+ this.closed = true;
4388
+ for (const waiter of this.waiters.splice(0)) waiter(void 0);
4389
+ for (const task of this.pending.splice(0)) {
4390
+ if (task.waitForCompletion) task.send(makeQueueOwnerError(task.requestId, "Queue owner shutting down before prompt execution", "QUEUE_OWNER_SHUTTING_DOWN", { retryable: true }));
4391
+ task.close();
4392
+ }
4393
+ for (const socket of this.sockets) if (!this.taskSockets.has(socket) && !this.drainingSockets.has(socket)) socket.destroy();
4394
+ this.emitQueueDepth();
4395
+ }
4396
+ async close() {
4397
+ this.beginShutdown();
4398
+ await new Promise((resolve) => {
4399
+ const forceCloseTimer = setTimeout(() => {
4400
+ for (const socket of this.sockets) socket.destroy();
4401
+ }, QUEUE_SOCKET_DRAIN_TIMEOUT_MS);
4402
+ forceCloseTimer.unref();
4403
+ this.server.close(() => {
4404
+ clearTimeout(forceCloseTimer);
4405
+ resolve();
4406
+ });
4407
+ });
4408
+ }
4409
+ async nextTask(timeoutMs) {
4410
+ if (this.pending.length > 0) {
4411
+ const task = this.pending.shift();
4412
+ this.emitQueueDepth();
4413
+ if (task) {
4414
+ this.activeTask = task;
4415
+ recordPerfDuration("queue.owner.wait_ms", Date.now() - task.enqueuedAt);
4416
+ }
4417
+ return task;
4418
+ }
4419
+ if (this.closed) return;
4420
+ return await new Promise((resolve) => {
4421
+ const timer = timeoutMs != null && setTimeout(() => {
4422
+ const index = this.waiters.indexOf(waiter);
4423
+ if (index >= 0) this.waiters.splice(index, 1);
4424
+ resolve(void 0);
4425
+ }, Math.max(0, timeoutMs));
4426
+ const waiter = (task) => {
4427
+ if (timer) clearTimeout(timer);
4428
+ resolve(task);
4429
+ };
4430
+ this.waiters.push(waiter);
4431
+ });
4432
+ }
4433
+ queueDepth() {
4434
+ return this.pending.length;
4435
+ }
4436
+ completeTask(task) {
4437
+ if (this.activeTask === task) this.activeTask = void 0;
4438
+ }
4439
+ async cancelPrompt(targetRequestId) {
4440
+ if (targetRequestId === void 0) return await this.controlHandlers.cancelPrompt();
4441
+ const index = this.pending.findIndex((task) => task.requestId === targetRequestId);
4442
+ if (index >= 0) {
4443
+ const [task] = this.pending.splice(index, 1);
4444
+ task.send(makeQueueOwnerError(task.requestId, "Prompt cancelled before execution", "QUEUE_REQUEST_CANCELLED", { retryable: false }));
4445
+ task.close();
4446
+ this.emitQueueDepth();
4447
+ return true;
4448
+ }
4449
+ return this.activeTask?.requestId === targetRequestId ? await this.controlHandlers.cancelPrompt() : false;
4450
+ }
4451
+ emitQueueDepth() {
4452
+ this.onQueueDepthChanged?.(this.pending.length);
4453
+ }
4454
+ enqueue(task) {
4455
+ if (this.closed) {
4456
+ task.send(makeQueueOwnerError(task.requestId, "Queue owner is shutting down", "QUEUE_OWNER_SHUTTING_DOWN", { retryable: true }));
4457
+ task.close();
4458
+ return false;
4459
+ }
4460
+ if (this.activeTask?.requestId === task.requestId || this.pending.some((queued) => queued.requestId === task.requestId)) {
4461
+ task.send(makeQueueOwnerError(task.requestId, "Request is already queued or active", "QUEUE_REQUEST_DUPLICATE", { retryable: false }));
4462
+ task.close();
4463
+ return false;
4464
+ }
4465
+ const waiter = this.waiters.shift();
4466
+ if (waiter) {
4467
+ this.activeTask = task;
4468
+ waiter(task);
4469
+ return true;
4470
+ }
4471
+ if (this.pending.length >= this.maxQueueDepth) {
4472
+ task.send({
4473
+ ...makeQueueOwnerError(task.requestId, `Queue owner is overloaded (${this.pending.length}/${this.maxQueueDepth} queued)`, "QUEUE_OWNER_OVERLOADED", { retryable: true }),
4474
+ ownerGeneration: this.ownerGeneration
4475
+ });
4476
+ task.close();
4477
+ return false;
4478
+ }
4479
+ this.pending.push(task);
4480
+ this.emitQueueDepth();
4481
+ return true;
4482
+ }
4483
+ handleControlRequest(options) {
4484
+ this.taskSockets.add(options.socket);
4485
+ this.writeQueueMessage(options.socket, {
4486
+ type: "accepted",
4487
+ requestId: options.requestId,
4488
+ ownerGeneration: this.ownerGeneration
4489
+ });
4490
+ options.run().then((message) => {
4491
+ this.writeQueueMessage(options.socket, {
4492
+ ...message,
4493
+ ownerGeneration: this.ownerGeneration
4494
+ });
4495
+ }).catch((error) => {
4496
+ this.writeQueueMessage(options.socket, {
4497
+ ...makeQueueOwnerErrorFromUnknown(options.requestId, error, "QUEUE_CONTROL_REQUEST_FAILED"),
4498
+ ownerGeneration: this.ownerGeneration
4499
+ });
4500
+ }).finally(() => {
4501
+ this.endSocket(options.socket);
4502
+ });
4503
+ }
4504
+ writeQueueMessage(socket, message) {
4505
+ this.outputs.get(socket)?.send(message);
4506
+ }
4507
+ endSocket(socket) {
4508
+ if (socket.destroyed || this.drainingSockets.has(socket)) return;
4509
+ this.taskSockets.delete(socket);
4510
+ this.drainingSockets.add(socket);
4511
+ this.outputs.get(socket)?.end();
4512
+ }
4513
+ failRequest(socket, requestId, message, detailCode) {
4514
+ this.writeQueueMessage(socket, {
4515
+ ...makeQueueOwnerError(requestId, message, detailCode, { retryable: false }),
4516
+ ownerGeneration: this.ownerGeneration
4517
+ });
4518
+ this.endSocket(socket);
4519
+ }
4520
+ parseRequestLine(socket, line) {
4521
+ let parsed;
4522
+ try {
4523
+ parsed = JSON.parse(line);
4524
+ } catch {
4525
+ this.failRequest(socket, "unknown", "Invalid queue request payload", "QUEUE_REQUEST_PAYLOAD_INVALID_JSON");
4526
+ return;
4527
+ }
4528
+ const request = parseQueueRequest(parsed);
4529
+ if (!request) this.failRequest(socket, "unknown", "Invalid queue request", "QUEUE_REQUEST_INVALID");
4530
+ return request ?? void 0;
4531
+ }
4532
+ rejectStaleOwnerGeneration(socket, request) {
4533
+ if (request.ownerGeneration === void 0 || this.ownerGeneration === void 0 || request.ownerGeneration === this.ownerGeneration) return false;
4534
+ this.failRequest(socket, request.requestId, "Queue request targeted a stale queue owner generation", "QUEUE_OWNER_GENERATION_MISMATCH");
4535
+ return true;
4536
+ }
4537
+ rejectClosedOwner(socket, request) {
4538
+ if (!this.closed) return false;
4539
+ this.writeQueueMessage(socket, {
4540
+ ...makeQueueOwnerError(request.requestId, "Queue owner is shutting down", "QUEUE_OWNER_CLOSED", { retryable: true }),
4541
+ ownerGeneration: this.ownerGeneration
4542
+ });
4543
+ this.endSocket(socket);
4544
+ return true;
4545
+ }
4546
+ handleControlQueueRequest(socket, request) {
4547
+ if (request.type === "cancel_prompt") {
4548
+ this.handleControlRequest({
4549
+ socket,
4550
+ requestId: request.requestId,
4551
+ run: async () => ({
4552
+ type: "cancel_result",
4553
+ requestId: request.requestId,
4554
+ cancelled: await this.cancelPrompt(request.targetRequestId)
4555
+ })
4556
+ });
4557
+ return true;
4558
+ }
4559
+ if (request.type === "close_session") {
4560
+ this.handleControlRequest({
4561
+ socket,
4562
+ requestId: request.requestId,
4563
+ run: async () => ({
4564
+ type: "close_session_result",
4565
+ requestId: request.requestId,
4566
+ closed: await this.controlHandlers.closeSession(request.timeoutMs)
4567
+ })
4568
+ });
4569
+ return true;
4570
+ }
4571
+ return this.handleSessionControlQueueRequest(socket, request);
4572
+ }
4573
+ handleSessionControlQueueRequest(socket, request) {
4574
+ if (request.type === "set_mode") {
4575
+ this.handleControlRequest({
4576
+ socket,
4577
+ requestId: request.requestId,
4578
+ run: async () => {
4579
+ await this.controlHandlers.setSessionMode(request.modeId, request.timeoutMs);
4580
+ return {
4581
+ type: "set_mode_result",
4582
+ requestId: request.requestId,
4583
+ modeId: request.modeId
4584
+ };
4585
+ }
4586
+ });
4587
+ return true;
4588
+ }
4589
+ if (request.type === "set_model") {
4590
+ this.handleControlRequest({
4591
+ socket,
4592
+ requestId: request.requestId,
4593
+ run: async () => {
4594
+ const response = await this.controlHandlers.setSessionModel(request.modelId, request.timeoutMs);
4595
+ return {
4596
+ type: "set_model_result",
4597
+ requestId: request.requestId,
4598
+ modelId: request.modelId,
4599
+ response
4600
+ };
4601
+ }
4602
+ });
4603
+ return true;
4604
+ }
4605
+ if (request.type === "set_config_option") {
4606
+ this.handleControlRequest({
4607
+ socket,
4608
+ requestId: request.requestId,
4609
+ run: async () => ({
4610
+ type: "set_config_option_result",
4611
+ requestId: request.requestId,
4612
+ response: await this.controlHandlers.setSessionConfigOption(request.configId, request.value, request.timeoutMs)
4613
+ })
4614
+ });
4615
+ return true;
4616
+ }
4617
+ return false;
4618
+ }
4619
+ enqueuePromptRequest(socket, request) {
4620
+ this.taskSockets.add(socket);
4621
+ const task = {
4622
+ requestId: request.requestId,
4623
+ message: request.message,
4624
+ prompt: request.prompt ?? textPrompt(request.message),
4625
+ permissionMode: request.permissionMode,
4626
+ resumePolicy: request.resumePolicy,
4627
+ nonInteractivePermissions: request.nonInteractivePermissions,
4628
+ permissionPolicy: request.permissionPolicy,
4629
+ timeoutMs: request.timeoutMs,
4630
+ suppressSdkConsoleErrors: request.suppressSdkConsoleErrors,
4631
+ promptRetries: request.promptRetries,
4632
+ sessionOptions: request.sessionOptions,
4633
+ waitForCompletion: request.waitForCompletion,
4634
+ reportPromptStarted: request.reportPromptStarted,
4635
+ enqueuedAt: Date.now(),
4636
+ send: (message) => {
4637
+ this.writeQueueMessage(socket, {
4638
+ ...message,
4639
+ ownerGeneration: this.ownerGeneration
4640
+ });
4641
+ },
4642
+ close: () => {
4643
+ this.endSocket(socket);
4644
+ }
4645
+ };
4646
+ if (!this.enqueue(task)) return;
4647
+ this.writeQueueMessage(socket, {
4648
+ type: "accepted",
4649
+ requestId: request.requestId,
4650
+ ownerGeneration: this.ownerGeneration
4651
+ });
4652
+ if (!request.waitForCompletion) task.close();
4653
+ }
4654
+ handleConnection(socket) {
4655
+ this.sockets.add(socket);
4656
+ this.outputs.set(socket, new QueueSocketOutput(socket, this.outputBudget));
4657
+ socket.setEncoding("utf8");
4658
+ socket.on("timeout", () => socket.destroy());
4659
+ socket.once("close", () => {
4660
+ this.outputs.delete(socket);
4661
+ this.sockets.delete(socket);
4662
+ this.taskSockets.delete(socket);
4663
+ this.drainingSockets.delete(socket);
4664
+ });
4665
+ let buffer = "";
4666
+ let handled = false;
4667
+ const processLine = (line) => {
4668
+ if (handled) return;
4669
+ handled = true;
4670
+ const request = this.parseRequestLine(socket, line);
4671
+ if (!request || this.rejectClosedOwner(socket, request) || this.rejectStaleOwnerGeneration(socket, request)) return;
4672
+ if (this.handleControlQueueRequest(socket, request)) return;
4673
+ if (request.type !== "submit_prompt") {
4674
+ this.failRequest(socket, request.requestId, "Invalid queue request", "QUEUE_REQUEST_INVALID");
4675
+ return;
4676
+ }
4677
+ this.enqueuePromptRequest(socket, request);
4678
+ };
4679
+ socket.on("data", (chunk) => {
4680
+ buffer += chunk;
4681
+ let index = buffer.indexOf("\n");
4682
+ while (index >= 0) {
4683
+ const rawLine = buffer.slice(0, index);
4684
+ if (this.rejectOversizedRequest(socket, rawLine)) return;
4685
+ const line = rawLine.trim();
4686
+ buffer = buffer.slice(index + 1);
4687
+ if (line.length > 0) processLine(line);
4688
+ index = buffer.indexOf("\n");
4689
+ }
4690
+ this.rejectOversizedRequest(socket, buffer);
4691
+ });
4692
+ socket.on("error", () => {});
4693
+ }
4694
+ rejectOversizedRequest(socket, line) {
4695
+ if (!queueRequestExceedsLimit(line, this.maxRequestBytes)) return false;
4696
+ socket.destroy();
4697
+ return true;
4698
+ }
4699
+ };
4700
+ //#endregion
3873
4701
  //#region src/session/queue/ipc.ts
3874
4702
  const MAX_MESSAGE_BUFFER_SIZE = 10485760;
3875
4703
  function assertOwnerGeneration(owner, message) {
@@ -4340,6 +5168,6 @@ async function trySetConfigOptionOnRunningOwner(sessionId, configId, value, time
4340
5168
  });
4341
5169
  }
4342
5170
  //#endregion
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 };
5171
+ export { resetPerfMetrics as $, findSession as A, PERMISSION_POLICY_ACTIONS as At, assertPersistedKeyPolicy as B, PermissionDeniedError as Bt, mergePromptSourceWithText as C, extractAcpError as Ct, DEFAULT_HISTORY_LIMIT as D, NON_INTERACTIVE_PERMISSION_POLICIES as Dt, textPrompt as E, EXIT_CODES as Et, normalizeName as F, AgentStartupError as Ft, SESSION_JOURNAL_SCHEMA as G, SessionModelReplayError as Gt, serializeSessionRecordForDisk as H, QueueConnectionError as Ht, pruneSessions as I, AuthPolicyError as It, watchSession as J, UnsupportedPromptContentError as Jt, SessionJournalReader as K, SessionNotFoundError as Kt, readSessionRecord as L, ClaudeAcpSessionCreateTimeoutError as Lt, isoNow as M, AcpxOperationalError as Mt, listSessions as N, AgentDisconnectedError as Nt, absolutePath as O, OUTPUT_FORMATS as Ot, listSessionsForAgent as P, AgentSpawnError as Pt, measurePerf as Q, resolveSessionRecord as R, CopilotAcpUnsupportedError as Rt, getUnsupportedPromptContentMessage as S, normalizeOutputError as St, promptToDisplayText as T, AUTH_POLICIES as Tt, extractAgentSessionId as U, SessionConfigOptionReplayError as Ut, parseSessionRecord as V, PermissionPromptUnavailableError as Vt, normalizeAgentSessionId as W, SessionModeReplayError as Wt, getPerfMetricsSnapshot as X, formatPerfMetric as Y, incrementPerfCounter as Z, createLockOwner as _, isProcessAlive as _t, trySetModelOnRunningOwner as a, isSessionUpdateNotification as at, observeProcessIncarnation as b, isAcpQueryClosedBeforeResponseError as bt, probeQueueOwnerHealth as c, parsePromptStopReason as ct, releaseQueueOwnerLease as d, sessionBaseDir as dt, setPerfGauge as et, terminateProcess as f, sessionEventActivePath as ft, QueueLeaseGuardSettlementError as g, writePrivateJsonFile as gt, waitMs as h, writePrivateFile as ht, trySetModeOnRunningOwner as i, isAcpMessageObject as it, findSessionByDirectoryWalk as j, SESSION_RECORD_SCHEMA as jt, findGitRepositoryRoot as k, PERMISSION_MODES as kt, readQueueOwnerRecord as l, DEFAULT_EVENT_SEGMENT_MAX_BYTES as lt, tryAcquireQueueOwnerLease as m, sessionEventSegmentPath as mt, tryCloseSessionOnRunningOwner as n, extractSessionUpdateNotification as nt, trySubmitToRunningOwner as o, parseJsonRpcErrorMessage as ot, terminateQueueOwnerForSession as p, sessionEventLockPath as pt, SessionWatchError as q, SessionResumeRequiredError as qt, trySetConfigOptionOnRunningOwner as r, isAcpJsonRpcMessage as rt, SessionQueueOwner as s, parsePermissionNotice as st, tryCancelOnRunningOwner as t, startPerfTimer as tt, refreshQueueOwnerLease as u, defaultSessionEventLog as ut, lockOwnerPid as v, exitCodeForOutputErrorCode as vt, parsePromptSource as w, isAcpResourceNotFoundError as wt, PromptInputValidationError as x, isRetryablePromptError as xt, ProcessDescendants as y, formatErrorMessage as yt, writeSessionRecord as z, GeminiAcpStartupTimeoutError as zt };
4344
5172
 
4345
- //# sourceMappingURL=ipc-DAApJ5mi.js.map
5173
+ //# sourceMappingURL=ipc-D0ZMQ0q9.js.map