@gajae-code/coding-agent 0.13.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/types/config/file-lock.d.ts +2 -0
  3. package/dist/types/config/keybindings.d.ts +2 -2
  4. package/dist/types/config/model-registry.d.ts +4 -0
  5. package/dist/types/config/models-config-schema.d.ts +6 -0
  6. package/dist/types/config/settings-schema.d.ts +17 -5
  7. package/dist/types/config/settings.d.ts +10 -1
  8. package/dist/types/edit/streaming.d.ts +5 -5
  9. package/dist/types/eval/py/kernel.d.ts +2 -2
  10. package/dist/types/eval/py/runtime.d.ts +5 -1
  11. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +3 -0
  12. package/dist/types/gjc-runtime/tmux-sessions.d.ts +3 -1
  13. package/dist/types/modes/components/model-selector.d.ts +3 -2
  14. package/dist/types/runtime/process-lifecycle.d.ts +12 -31
  15. package/dist/types/sdk/broker/identity.d.ts +2 -1
  16. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +17 -1
  17. package/dist/types/sdk/broker/lifecycle.d.ts +11 -6
  18. package/dist/types/sdk/broker/startup-failure.d.ts +32 -0
  19. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
  20. package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
  21. package/dist/types/sdk/bus/index.d.ts +4 -0
  22. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +10 -1
  23. package/dist/types/sdk/bus/reconciliation-store.d.ts +2 -0
  24. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -2
  25. package/dist/types/sdk/bus/telegram-daemon.d.ts +17 -1
  26. package/dist/types/sdk/host/query/cursor.d.ts +4 -0
  27. package/dist/types/sdk/host/query/handlers.d.ts +6 -0
  28. package/dist/types/sdk/host/session-runtime.d.ts +7 -6
  29. package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
  30. package/dist/types/sdk/index.d.ts +1 -0
  31. package/dist/types/sdk/turn-result.d.ts +37 -0
  32. package/dist/types/session/agent-session.d.ts +8 -11
  33. package/dist/types/session/blob-store.d.ts +6 -3
  34. package/dist/types/slash-commands/helpers/fast-status-report.d.ts +12 -3
  35. package/dist/types/task/executor.d.ts +4 -7
  36. package/dist/types/tools/index.d.ts +1 -1
  37. package/dist/types/tools/read.d.ts +1 -1
  38. package/dist/types/tools/search.d.ts +1 -1
  39. package/dist/types/utils/edit-mode.d.ts +69 -0
  40. package/package.json +8 -8
  41. package/scripts/generate-hotkeys-docs.ts +8 -6
  42. package/src/commands/sdk.ts +23 -2
  43. package/src/config/file-lock.ts +25 -5
  44. package/src/config/keybindings.ts +4 -2
  45. package/src/config/model-registry.ts +20 -0
  46. package/src/config/models-config-schema.ts +1 -0
  47. package/src/config/settings-schema.ts +29 -22
  48. package/src/config/settings.ts +42 -10
  49. package/src/coordinator-mcp/server.ts +84 -106
  50. package/src/edit/index.ts +5 -19
  51. package/src/edit/renderer.ts +3 -2
  52. package/src/edit/streaming.ts +28 -15
  53. package/src/eval/py/executor.ts +108 -10
  54. package/src/eval/py/kernel.ts +86 -12
  55. package/src/eval/py/runtime.ts +200 -16
  56. package/src/exec/bash-executor.ts +36 -37
  57. package/src/gjc-runtime/gc-runtime.ts +25 -3
  58. package/src/gjc-runtime/managed-owner-supervisor.ts +27 -1
  59. package/src/gjc-runtime/session-state-sidecar.ts +110 -4
  60. package/src/gjc-runtime/tmux-sessions.ts +34 -5
  61. package/src/internal-urls/docs-index.generated.ts +6 -6
  62. package/src/modes/acp/acp-agent.ts +10 -2
  63. package/src/modes/components/model-selector.ts +28 -8
  64. package/src/modes/controllers/plan-mode-controller.ts +3 -3
  65. package/src/modes/controllers/selector-controller.ts +4 -2
  66. package/src/runtime/process-lifecycle.ts +126 -27
  67. package/src/runtime-mcp/transports/stdio.ts +11 -6
  68. package/src/sdk/acp/adapter.ts +18 -10
  69. package/src/sdk/broker/broker.ts +55 -4
  70. package/src/sdk/broker/ensure.ts +10 -7
  71. package/src/sdk/broker/identity.ts +10 -0
  72. package/src/sdk/broker/lifecycle-ledger.ts +56 -6
  73. package/src/sdk/broker/lifecycle.ts +53 -10
  74. package/src/sdk/broker/session-index.ts +28 -11
  75. package/src/sdk/broker/startup-failure.ts +101 -0
  76. package/src/sdk/broker/transport.ts +1 -0
  77. package/src/sdk/bus/chat-daemon-control.ts +4 -3
  78. package/src/sdk/bus/chat-daemon-runtime.ts +8 -1
  79. package/src/sdk/bus/conversation-store.ts +171 -50
  80. package/src/sdk/bus/index.ts +101 -47
  81. package/src/sdk/bus/kind-aware-reconciliation.ts +52 -2
  82. package/src/sdk/bus/notification-service.ts +3 -3
  83. package/src/sdk/bus/reconciliation-store.ts +16 -0
  84. package/src/sdk/bus/telegram-daemon-contract.ts +11 -2
  85. package/src/sdk/bus/telegram-daemon.ts +131 -29
  86. package/src/sdk/bus/topic-registry.ts +38 -9
  87. package/src/sdk/cli/session-cli.ts +20 -7
  88. package/src/sdk/client/discovery.ts +23 -2
  89. package/src/sdk/host/query/cursor.ts +4 -0
  90. package/src/sdk/host/query/handlers.ts +99 -100
  91. package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
  92. package/src/sdk/host/session-runtime.test.ts +98 -1
  93. package/src/sdk/host/session-runtime.ts +57 -15
  94. package/src/sdk/host/websocket-transport.lifecycle.test.ts +192 -2
  95. package/src/sdk/host/websocket-transport.ts +54 -30
  96. package/src/sdk/index.ts +1 -0
  97. package/src/sdk/mcp/server.ts +19 -6
  98. package/src/sdk/protocol/operation-inventory.generated.json +1 -1
  99. package/src/sdk/protocol/operation-registry.ts +8 -12
  100. package/src/sdk/session.ts +5 -14
  101. package/src/sdk/turn-result.ts +53 -0
  102. package/src/session/agent-session.ts +112 -69
  103. package/src/session/blob-store.ts +91 -21
  104. package/src/session/session-manager.ts +408 -81
  105. package/src/session/session-storage.ts +68 -10
  106. package/src/slash-commands/helpers/fast-status-report.ts +24 -8
  107. package/src/task/executor.ts +25 -8
  108. package/src/task/index.ts +0 -2
  109. package/src/tools/index.ts +1 -1
  110. package/src/tools/read.ts +12 -11
  111. package/src/tools/search.ts +7 -7
  112. package/src/utils/edit-mode.ts +180 -9
  113. package/src/web/search/providers/insane.ts +55 -1
@@ -1359,9 +1359,10 @@ type BoundedLineScanFailure = "read_failed" | "oversized_line" | "unterminated"
1359
1359
  * path. Returns `undefined` only after every byte was consumed as a complete
1360
1360
  * newline-terminated line.
1361
1361
  */
1362
+ const boundedJsonLineDecoder = new TextDecoder("utf-8", { fatal: true });
1362
1363
  function decodeBoundedJsonLine(lineBytes: Uint8Array): string {
1363
1364
  const length = lineBytes.at(-1) === 0x0a ? lineBytes.byteLength - 1 : lineBytes.byteLength;
1364
- return Buffer.from(lineBytes.buffer, lineBytes.byteOffset, length).toString("utf8");
1365
+ return boundedJsonLineDecoder.decode(lineBytes.subarray(0, length));
1365
1366
  }
1366
1367
 
1367
1368
  function bytesStartWith(bytes: Uint8Array, prefix: Uint8Array): boolean {
@@ -3409,6 +3410,61 @@ function sameResumeStat(left: SessionStorageStat, right: SessionStorageStat): bo
3409
3410
  );
3410
3411
  }
3411
3412
 
3413
+ function resumeIdentityMatchesDescriptor(identity: ResumeSessionIdentity, descriptor: DescriptorSnapshot): boolean {
3414
+ return (
3415
+ identity.dev === descriptor.dev &&
3416
+ identity.ino === descriptor.ino &&
3417
+ identity.nlink === descriptor.nlink &&
3418
+ identity.size === descriptor.size &&
3419
+ identity.mtimeNs === descriptor.mtimeNs &&
3420
+ (identity.ctimeNs === undefined || identity.ctimeNs === descriptor.ctimeNs)
3421
+ );
3422
+ }
3423
+
3424
+ function descriptorSnapshotAsStorageStat(descriptor: DescriptorSnapshot): SessionStorageStat {
3425
+ const mtimeMs = Number(descriptor.mtimeNs) / 1_000_000;
3426
+ return { ...descriptor, mtimeMs, mtime: new Date(mtimeMs), isFile: true };
3427
+ }
3428
+
3429
+ function retainedManagedInspectionStorage(
3430
+ storage: SessionStorage,
3431
+ store: ManagedSessionDescendantStore,
3432
+ filePath: string,
3433
+ ): SessionStorage {
3434
+ const resolved = path.resolve(filePath);
3435
+ const relative = path.basename(resolved);
3436
+ const statExpected = (): SessionStorageStat => {
3437
+ store.assertBound();
3438
+ const descriptor = store.descriptorExpected(relative);
3439
+ if (!descriptor) throw Object.assign(new Error("Managed file not found"), { code: "ENOENT" });
3440
+ return descriptorSnapshotAsStorageStat(descriptor);
3441
+ };
3442
+ return new Proxy(storage, {
3443
+ get: (target, property) => {
3444
+ if (property === "statSync")
3445
+ return (candidate: string) =>
3446
+ path.resolve(candidate) === resolved ? statExpected() : target.statSync(candidate);
3447
+ if (property === "existsSync")
3448
+ return (candidate: string) =>
3449
+ path.resolve(candidate) === resolved
3450
+ ? store.descriptorExpected(relative) !== null
3451
+ : target.existsSync(candidate);
3452
+ if (property === "readSnapshotSync")
3453
+ return (candidate: string) => {
3454
+ if (path.resolve(candidate) !== resolved) return target.readSnapshotSync!(candidate);
3455
+ const before = statExpected();
3456
+ const snapshot = store.readExpected(relative);
3457
+ if (!snapshot) throw Object.assign(new Error("Managed file not found"), { code: "ENOENT" });
3458
+ const after = statExpected();
3459
+ if (!sameResumeStat(before, after)) throw new Error("source_changed");
3460
+ return { bytes: snapshot.bytes, stat: before };
3461
+ };
3462
+ const value = Reflect.get(target, property, target);
3463
+ return typeof value === "function" ? value.bind(target) : value;
3464
+ },
3465
+ });
3466
+ }
3467
+
3412
3468
  interface ResumeInspectionSnapshot {
3413
3469
  identity: ResumeSessionIdentity;
3414
3470
  content: Uint8Array;
@@ -3688,34 +3744,40 @@ function inspectTranscriptBounded(
3688
3744
  }
3689
3745
 
3690
3746
  const hash = crypto.createHash("sha256");
3691
- const decoder = new TextDecoder("utf-8", { fatal: true });
3692
- let carry = "";
3693
3747
  let sessionId: string | undefined;
3694
3748
  let cwd: string | undefined;
3695
- try {
3696
- for (let offset = 0; offset < before.size; offset += TRANSCRIPT_CAPTURE_CHUNK_BYTES) {
3697
- const length = Math.min(TRANSCRIPT_CAPTURE_CHUNK_BYTES, before.size - offset);
3698
- const chunk = storage.readRangeSync(canonicalPath, offset, length).bytes;
3699
- hash.update(chunk);
3700
- const text = decoder.decode(chunk, { stream: offset + length < before.size });
3701
- const combined = carry + text;
3702
- const lines = combined.split("\n");
3703
- carry = lines.pop() ?? "";
3704
- for (const line of lines) {
3705
- if (line.length === 0) continue;
3706
- const parsed = JSON.parse(line) as SessionHeader;
3749
+ const scanResult: { stat?: SessionStorageStat } = {};
3750
+ const scanFailure = scanTranscriptLinesBounded(
3751
+ storage,
3752
+ canonicalPath,
3753
+ before.size,
3754
+ (_lineStart, lineBytes) => {
3755
+ hash.update(lineBytes);
3756
+ if (lineBytes.byteLength === 1 && lineBytes[0] === 0x0a) return;
3757
+ try {
3758
+ const parsed = JSON.parse(decodeBoundedJsonLine(lineBytes)) as SessionHeader;
3707
3759
  if (sessionId === undefined) {
3708
- if (parsed.type !== "session" || typeof parsed.id !== "string")
3709
- return { ok: false, error: { kind: "error", reason: "malformed" } };
3760
+ if (parsed.type !== "session" || typeof parsed.id !== "string") return false;
3710
3761
  sessionId = parsed.id;
3711
3762
  cwd = typeof parsed.cwd === "string" ? parsed.cwd : undefined;
3712
3763
  }
3764
+ return;
3765
+ } catch {
3766
+ return false;
3713
3767
  }
3714
- if (((offset + length) & (8 * 1024 * 1024 - 1)) === 0) Bun.gc(true);
3715
- }
3716
- } catch {
3717
- return { ok: false, error: { kind: "error", reason: "malformed" } };
3768
+ },
3769
+ scanResult,
3770
+ true,
3771
+ true,
3772
+ );
3773
+ if (scanFailure) {
3774
+ return {
3775
+ ok: false,
3776
+ error: { kind: "error", reason: scanFailure === "read_failed" ? "read-failed" : "malformed" },
3777
+ };
3718
3778
  }
3779
+ if (scanResult.stat && !sameResumeStat(before, scanResult.stat))
3780
+ return { ok: false, error: { kind: "error", reason: "unstable" } };
3719
3781
  if (sessionId === undefined) return { ok: false, error: { kind: "error", reason: "malformed" } };
3720
3782
 
3721
3783
  let after: SessionStorageStat;
@@ -6766,11 +6828,15 @@ export class SessionManager {
6766
6828
  #managedTranscriptStoreCache: { directory: string; store: ManagedSessionDescendantStore } | null = null;
6767
6829
  #ownedManagedAuthority: ManagedSessionDescendantStore | undefined;
6768
6830
  #managedSidecarCacheStore: EphemeralBlobStore | undefined;
6831
+ readonly #managedSidecarCleanupStores = new Set<EphemeralBlobStore>();
6769
6832
  #managedSidecarAuthorityStore: ManagedSessionDescendantStore | undefined;
6770
6833
  #managedSidecarSecurityContext: ManagedSessionSecurityContext | undefined;
6771
6834
  #managedSidecarCacheSessionFile: string | undefined;
6772
6835
  #managedRangeExpectedDescriptor: DescriptorSnapshot | undefined;
6773
6836
  #boundedReadStorageProxy: SessionStorage | undefined;
6837
+ #boundedManagedSource:
6838
+ | { path: string; store: ManagedSessionDescendantStore; descriptor: DescriptorSnapshot; owned: boolean }
6839
+ | undefined;
6774
6840
  // When set, take precedence over the lazily-derived per-session manager.
6775
6841
  // Subagents adopt the parent's manager so artifact IDs are unique across the
6776
6842
  // whole agent tree and all files land in the parent's artifacts dir.
@@ -7515,6 +7581,7 @@ export class SessionManager {
7515
7581
  this.#titleSource = state.header.titleSource;
7516
7582
  this.#sessionFile = state.sessionFile;
7517
7583
  this.#managedRangeExpectedDescriptor = undefined;
7584
+ this.#clearBoundedManagedSource();
7518
7585
  this.#flushed = false;
7519
7586
  this.#needsFullRewriteOnNextPersist = false;
7520
7587
  this.#ensuredOnDisk = false;
@@ -8289,6 +8356,7 @@ export class SessionManager {
8289
8356
  const runtime = this.#sidecarRuntime;
8290
8357
  if (!runtime?.indexPath || !runtime.tailPath) return undefined;
8291
8358
  const telemetry = this.#firstOpenTelemetry;
8359
+ const boundedReadStorage = this.#boundedReadStorage();
8292
8360
  let tailWriter: SessionStorageWriter | undefined;
8293
8361
  let buildFailed = false;
8294
8362
  const secondaryArtifactsEligible =
@@ -8401,11 +8469,14 @@ export class SessionManager {
8401
8469
  let hashOffset = checkpoint.offset;
8402
8470
  while (hashOffset < baseEndOffset) {
8403
8471
  const length = Math.min(SESSION_RANGE_READ_MAX_BYTES, baseEndOffset - hashOffset);
8404
- const bytes = this.#storage.readRangeSync!(sessionFile, hashOffset, length).bytes;
8405
- if (bytes.byteLength !== length) {
8406
- this.#lazyReopenFallbackReason = "bounded_scan_build_failed";
8472
+ const baseSnapshot = boundedReadStorage.readRangeSync!(sessionFile, hashOffset, length);
8473
+ if (!sameDescriptor(descriptor, baseSnapshot.stat) || baseSnapshot.bytes.byteLength !== length) {
8474
+ this.#lazyReopenFallbackReason = !sameDescriptor(descriptor, baseSnapshot.stat)
8475
+ ? "bounded_first_open_descriptor_changed"
8476
+ : "bounded_scan_build_failed";
8407
8477
  return undefined;
8408
8478
  }
8479
+ const bytes = baseSnapshot.bytes;
8409
8480
  baseHash.update(bytes);
8410
8481
  telemetry.bytesRead += length;
8411
8482
  telemetry.transcriptBytesRead += length;
@@ -8417,7 +8488,7 @@ export class SessionManager {
8417
8488
  this.#lazyReopenFallbackReason = "bounded_scan_budget";
8418
8489
  return undefined;
8419
8490
  }
8420
- const suffixSnapshot = this.#storage.readRangeSync!(sessionFile, baseEndOffset, suffixLength);
8491
+ const suffixSnapshot = boundedReadStorage.readRangeSync!(sessionFile, baseEndOffset, suffixLength);
8421
8492
  if (!sameDescriptor(descriptor, suffixSnapshot.stat) || suffixSnapshot.bytes.byteLength !== suffixLength) {
8422
8493
  this.#lazyReopenFallbackReason = "bounded_first_open_descriptor_changed";
8423
8494
  return undefined;
@@ -9375,6 +9446,7 @@ export class SessionManager {
9375
9446
  this.#sidecarRuntime = undefined;
9376
9447
  this.#releaseManagedSidecarCache();
9377
9448
  this.#boundedReadStorageProxy = undefined;
9449
+ this.#clearBoundedManagedSource();
9378
9450
  if (!this.#lifecycleIdAdopted && stage.sessionId === lifecyclePreallocatedSessionId())
9379
9451
  this.#lifecycleIdAdopted = true;
9380
9452
  this.#persistChain = Promise.resolve();
@@ -9540,6 +9612,21 @@ export class SessionManager {
9540
9612
  throw new Error("Uncommitted session discard is limited to this manager's configured session directory.");
9541
9613
  }
9542
9614
  try {
9615
+ if (this.destination.kind === "managed") {
9616
+ const store = this.#managedTranscriptStore(canonicalCandidate);
9617
+ const transcriptName = path.basename(canonicalCandidate);
9618
+ const artifactName = path.basename(canonicalCandidate.slice(0, -6));
9619
+ const transcript = store.readExpected(transcriptName);
9620
+ let artifacts: native.NativeDirectoryTreeSnapshot | undefined;
9621
+ try {
9622
+ artifacts = store.captureTree(artifactName);
9623
+ } catch (error) {
9624
+ if (!(error instanceof Error) || error.message !== "not_found") throw error;
9625
+ }
9626
+ if (artifacts) store.removeTreeExpected(artifactName, artifacts);
9627
+ if (transcript) store.removeExpected(transcriptName, transcript);
9628
+ return;
9629
+ }
9543
9630
  await this.#storage.deleteSessionWithArtifacts(canonicalCandidate);
9544
9631
  } catch (err) {
9545
9632
  if (isEnoent(err)) return;
@@ -10681,12 +10768,23 @@ export class SessionManager {
10681
10768
  #releaseManagedSidecarCache(): void {
10682
10769
  this.#managedSidecarAuthorityStore?.close();
10683
10770
  this.#managedSidecarSecurityContext?.retainedAuthority?.close();
10771
+ const store = this.#managedSidecarCacheStore;
10684
10772
  this.#managedSidecarAuthorityStore = undefined;
10685
10773
  this.#managedSidecarSecurityContext = undefined;
10686
- this.#managedSidecarCacheStore?.dispose();
10687
10774
  this.#managedSidecarCacheStore = undefined;
10688
10775
  this.#managedSidecarCacheSessionFile = undefined;
10689
10776
  this.#managedRangeExpectedDescriptor = undefined;
10777
+ if (store) this.#managedSidecarCleanupStores.add(store);
10778
+ for (const pending of this.#managedSidecarCleanupStores) {
10779
+ try {
10780
+ pending.dispose();
10781
+ this.#managedSidecarCleanupStores.delete(pending);
10782
+ } catch (error) {
10783
+ const candidate = error instanceof ResidentCacheTrustError ? error.reason : "";
10784
+ const reason = /^[a-z0-9_]{1,64}$/.test(candidate) ? candidate : "cleanup_failed";
10785
+ logger.warn("Failed to dispose the managed sidecar resident cache; retained for retry", { reason });
10786
+ }
10787
+ }
10690
10788
  }
10691
10789
 
10692
10790
  #isManagedSidecarPath(candidate: string): boolean {
@@ -10742,7 +10840,25 @@ export class SessionManager {
10742
10840
  return instanceDir;
10743
10841
  }
10744
10842
 
10843
+ #clearBoundedManagedSource(): void {
10844
+ const source = this.#boundedManagedSource;
10845
+ this.#boundedManagedSource = undefined;
10846
+ if (source?.owned) source.store.close();
10847
+ }
10745
10848
  #readRangeSync(filePath: string, start: number, length: number): SessionStorageRangeSnapshot {
10849
+ const boundedManagedSource = this.#boundedManagedSource;
10850
+ if (
10851
+ this.destination.kind === "managed" &&
10852
+ boundedManagedSource &&
10853
+ path.resolve(filePath) === path.resolve(boundedManagedSource.path)
10854
+ ) {
10855
+ return boundedManagedSource.store.readRangeExpectedSync(
10856
+ path.basename(filePath),
10857
+ start,
10858
+ length,
10859
+ boundedManagedSource.descriptor,
10860
+ );
10861
+ }
10746
10862
  if (
10747
10863
  this.destination.kind === "managed" &&
10748
10864
  this.#sessionFile &&
@@ -10770,6 +10886,17 @@ export class SessionManager {
10770
10886
  }
10771
10887
 
10772
10888
  #statSync(filePath: string): SessionStorageStat {
10889
+ const boundedManagedSource = this.#boundedManagedSource;
10890
+ if (
10891
+ this.destination.kind === "managed" &&
10892
+ boundedManagedSource &&
10893
+ path.resolve(filePath) === path.resolve(boundedManagedSource.path)
10894
+ ) {
10895
+ const descriptor = boundedManagedSource.store.descriptorExpected(path.basename(filePath));
10896
+ if (!descriptor || !sameDescriptor(boundedManagedSource.descriptor, descriptor))
10897
+ throw new Error("source_changed");
10898
+ return descriptor;
10899
+ }
10773
10900
  if (
10774
10901
  this.destination.kind === "managed" &&
10775
10902
  this.#sessionFile &&
@@ -14504,6 +14631,7 @@ export class SessionManager {
14504
14631
  this.#labelsById.clear();
14505
14632
  this.#resetMaterializedCaches();
14506
14633
  this.#releaseManagedSidecarCache();
14634
+ this.#clearBoundedManagedSource();
14507
14635
  }
14508
14636
 
14509
14637
  /** Close the persistent writer after flushing all pending data. */
@@ -17565,10 +17693,11 @@ export class SessionManager {
17565
17693
  }
17566
17694
 
17567
17695
  async #tryForkFromBoundedSource(sourcePath: string, expectedIdentity?: ResumeSessionIdentity): Promise<boolean> {
17568
- const sourceSize = expectedIdentity?.size ?? this.#storage.statSync(sourcePath).size;
17696
+ const boundedReadStorage = this.#boundedReadStorage();
17697
+ const sourceSize = expectedIdentity?.size ?? boundedReadStorage.statSync(sourcePath).size;
17569
17698
  if (
17570
17699
  this.#effectiveSessionMemoryMode(sourceSize) !== "enabled" ||
17571
- typeof this.#storage.readRangeSync !== "function" ||
17700
+ typeof boundedReadStorage.readRangeSync !== "function" ||
17572
17701
  typeof this.#storage.openStagedWriter !== "function"
17573
17702
  )
17574
17703
  return false;
@@ -17584,7 +17713,7 @@ export class SessionManager {
17584
17713
  const preflightHash = crypto.createHash("sha256");
17585
17714
  const preflightResult: { stat?: SessionStorageStat } = {};
17586
17715
  const preflightFailure = scanTranscriptLinesBounded(
17587
- this.#storage,
17716
+ boundedReadStorage,
17588
17717
  sourcePath,
17589
17718
  sourceSize,
17590
17719
  (_offset, lineBytes) => {
@@ -17681,7 +17810,7 @@ export class SessionManager {
17681
17810
  const copyHash = crypto.createHash("sha256");
17682
17811
  const copyResult: { stat?: SessionStorageStat } = {};
17683
17812
  const copyFailure = scanTranscriptLinesBounded(
17684
- this.#storage,
17813
+ boundedReadStorage,
17685
17814
  sourcePath,
17686
17815
  identity.size,
17687
17816
  (_offset, lineBytes) => {
@@ -17732,16 +17861,20 @@ export class SessionManager {
17732
17861
  if (!managedPublishedTranscript) throw new Error("managed_fork_transcript_publish_missing");
17733
17862
  }
17734
17863
  if (copyResult.stat) {
17735
- const current = this.#storage.statSync(sourcePath);
17864
+ const current = boundedReadStorage.statSync(sourcePath);
17736
17865
  if (!sameDescriptor(copyResult.stat, current)) throw new Error("fork_source_identity_changed");
17737
- } else if (revalidateTranscriptIdentityBounded(sourcePath, this.#storage, identity).kind !== "valid") {
17866
+ } else if (revalidateTranscriptIdentityBounded(sourcePath, boundedReadStorage, identity).kind !== "valid") {
17738
17867
  throw new Error("fork_source_identity_changed");
17739
17868
  }
17740
17869
  await this.copyArtifactsForFork(sourcePath, fresh.sessionFile);
17741
- if (this.destination.kind === "managed" && this.#storage.existsSync(fresh.sessionFile.slice(0, -6)))
17742
- managedPublishedArtifacts = this.#managedTranscriptStore(fresh.sessionFile).captureTree(
17743
- path.basename(fresh.sessionFile.slice(0, -6)),
17744
- );
17870
+ if (this.destination.kind === "managed") {
17871
+ const store = this.#managedTranscriptStore(fresh.sessionFile);
17872
+ try {
17873
+ managedPublishedArtifacts = store.captureTree(path.basename(fresh.sessionFile.slice(0, -6)));
17874
+ } catch (error) {
17875
+ if (!(error instanceof Error) || error.message !== "not_found") throw error;
17876
+ }
17877
+ }
17745
17878
  await this.#initSessionFile(fresh.sessionFile);
17746
17879
  writeTerminalBreadcrumb(this.cwd, fresh.sessionFile);
17747
17880
  return true;
@@ -17779,8 +17912,40 @@ export class SessionManager {
17779
17912
  const entryPatches = new Map<string, EntryPatchRecord["patch"]>();
17780
17913
  let overlayBytes = 0;
17781
17914
  let requiresRecordTransforms = false;
17915
+ const managedSourceStorage = this.#boundedManagedSource ? this.#boundedReadStorage() : undefined;
17916
+ const forEachSourceLine = (visit: (line: Uint8Array) => boolean | undefined): void => {
17917
+ if (!managedSourceStorage) {
17918
+ snapshot.forEachLine(visit);
17919
+ return;
17920
+ }
17921
+ const hash = crypto.createHash("sha256");
17922
+ const scanResult: { stat?: SessionStorageStat } = {};
17923
+ let aborted = false;
17924
+ const failure = scanTranscriptLinesBounded(
17925
+ managedSourceStorage,
17926
+ snapshot.sourcePath,
17927
+ snapshot.identity.size,
17928
+ (_offset, line) => {
17929
+ hash.update(line);
17930
+ const body = line[line.byteLength - 1] === 0x0a ? line.subarray(0, line.byteLength - 1) : line;
17931
+ if (body.byteLength === 0) return;
17932
+ if (visit(body) === false) {
17933
+ aborted = true;
17934
+ return false;
17935
+ }
17936
+ },
17937
+ scanResult,
17938
+ true,
17939
+ true,
17940
+ );
17941
+ if (failure && !(failure === "aborted" && aborted)) throw new Error(`transcript_scan_${failure}`);
17942
+ if (aborted) return;
17943
+ const terminalStat = scanResult.stat ?? managedSourceStorage.statSync(snapshot.sourcePath);
17944
+ if (!resumeIdentityMatchesDescriptor(snapshot.identity, terminalStat)) throw new Error("identity-mismatch");
17945
+ if (hash.digest("hex") !== snapshot.identity.sha256) throw new Error("identity-mismatch");
17946
+ };
17782
17947
  try {
17783
- snapshot.forEachLine(line => {
17948
+ forEachSourceLine(line => {
17784
17949
  if (rejected || line.byteLength === 0) return;
17785
17950
  const record = JSON.parse(decodeBoundedJsonLine(line)) as FileEntry | SessionPatchRecord;
17786
17951
  if (ordinal++ === 0) {
@@ -17841,7 +18006,7 @@ export class SessionManager {
17841
18006
  let managedPublishedTranscript: ManagedFileSnapshot | undefined;
17842
18007
  try {
17843
18008
  let writeOrdinal = 0;
17844
- snapshot.forEachLine(line => {
18009
+ forEachSourceLine(line => {
17845
18010
  if (line.byteLength === 0) return;
17846
18011
  if (writeOrdinal++ === 0) {
17847
18012
  staged.writeLine(Buffer.from(JSON.stringify(fresh.header), "utf8"));
@@ -17872,9 +18037,13 @@ export class SessionManager {
17872
18037
  undefined;
17873
18038
  if (!managedPublishedTranscript) throw new Error("managed_fork_transcript_publish_missing");
17874
18039
  }
17875
- const copyDescriptor = snapshot.getLastReadStat();
17876
- if (snapshot.storage instanceof FileSessionStorage && copyDescriptor) {
17877
- const current = snapshot.storage.statSync(snapshot.sourcePath);
18040
+ const copyDescriptor = managedSourceStorage
18041
+ ? managedSourceStorage.statSync(snapshot.sourcePath)
18042
+ : snapshot.getLastReadStat();
18043
+ if (copyDescriptor) {
18044
+ const current = managedSourceStorage
18045
+ ? managedSourceStorage.statSync(snapshot.sourcePath)
18046
+ : snapshot.storage.statSync(snapshot.sourcePath);
17878
18047
  if (!sameDescriptor(copyDescriptor, current)) {
17879
18048
  await this.#cleanupFailedForkDestination(fresh.sessionFile, managedPublishedTranscript);
17880
18049
  return { kind: "error", reason: "identity-mismatch" };
@@ -17913,27 +18082,71 @@ export class SessionManager {
17913
18082
  sessionMemoryMode: SessionMemoryMode = "shadow",
17914
18083
  ): Promise<SessionManager> {
17915
18084
  const destination = destinationFor(cwd, destinationInput, storage);
17916
- let managedSourcePath = sourcePath;
17917
- if (destination.kind === "managed" && storage instanceof FileSessionStorage) {
17918
- const prepared = await SessionManager.prepareManagedCandidateForWrite(
17919
- sourcePath,
17920
- migrationPolicy,
17921
- destination,
17922
- );
17923
- managedSourcePath = storage.existsSync(prepared) ? prepared : sourcePath;
17924
- }
17925
18085
  const dir = destination.directory;
17926
18086
  const manager = new SessionManager(cwd, dir, true, storage, destination);
17927
18087
  manager.#sessionMemoryMode = sessionMemoryMode;
17928
- const sourceSize = storage.statSync(managedSourcePath).size;
17929
- if (manager.#effectiveSessionMemoryMode(sourceSize) === "enabled") {
17930
- const inspected = inspectTranscriptBounded(managedSourcePath, storage, BOUNDED_RESUME_TRANSCRIPT_MAX_BYTES);
18088
+ let managedSourcePath = sourcePath;
18089
+ if (destination.kind === "managed" && storage instanceof FileSessionStorage) {
18090
+ const resolvedSource = path.resolve(sourcePath);
18091
+ const sessionsRoot = path.resolve(destination.securityContext.sessionsRoot);
17931
18092
  if (
17932
- inspected.ok &&
17933
- (await manager.#tryForkFromBoundedSource(managedSourcePath, inspected.inspection.identity))
17934
- )
17935
- return manager;
18093
+ pathIsWithin(sessionsRoot, resolvedSource) &&
18094
+ path.dirname(resolvedSource) !== path.resolve(destination.directory)
18095
+ ) {
18096
+ managedSourcePath = await SessionManager.prepareManagedCandidateForWrite(
18097
+ sourcePath,
18098
+ migrationPolicy,
18099
+ destination,
18100
+ );
18101
+ }
18102
+ }
18103
+ let boundedStorage: SessionStorage = storage;
18104
+ if (
18105
+ destination.kind === "managed" &&
18106
+ storage instanceof FileSessionStorage &&
18107
+ pathIsWithin(path.resolve(destination.securityContext.sessionsRoot), path.resolve(managedSourcePath))
18108
+ ) {
18109
+ const sourceDirectory = path.dirname(path.resolve(managedSourcePath));
18110
+ const destinationDirectory = path.resolve(destination.directory);
18111
+ const sourceStore =
18112
+ sourceDirectory === destinationDirectory
18113
+ ? manager.#managedTranscriptStore(managedSourcePath)
18114
+ : managedStoreFromContext(destination.securityContext, sourceDirectory);
18115
+ try {
18116
+ sourceStore.assertBound();
18117
+ const descriptor = sourceStore.descriptorExpected(path.basename(managedSourcePath));
18118
+ if (!descriptor) throw Object.assign(new Error("Managed file not found"), { code: "ENOENT" });
18119
+ manager.#boundedManagedSource = {
18120
+ path: managedSourcePath,
18121
+ store: sourceStore,
18122
+ descriptor,
18123
+ owned: sourceDirectory !== destinationDirectory,
18124
+ };
18125
+ boundedStorage = manager.#boundedReadStorage();
18126
+ } catch (error) {
18127
+ sourceStore.close();
18128
+ throw error;
18129
+ }
17936
18130
  }
18131
+ let sourceSize: number | undefined;
18132
+ try {
18133
+ sourceSize = boundedStorage.statSync(managedSourcePath).size;
18134
+ if (manager.#effectiveSessionMemoryMode(sourceSize) === "enabled") {
18135
+ const inspected = inspectTranscriptBounded(
18136
+ managedSourcePath,
18137
+ boundedStorage,
18138
+ BOUNDED_RESUME_TRANSCRIPT_MAX_BYTES,
18139
+ );
18140
+ if (
18141
+ inspected.ok &&
18142
+ (await manager.#tryForkFromBoundedSource(managedSourcePath, inspected.inspection.identity))
18143
+ )
18144
+ return manager;
18145
+ }
18146
+ } finally {
18147
+ manager.#clearBoundedManagedSource();
18148
+ }
18149
+ if (sourceSize === undefined) throw new Error("Managed source size unavailable");
17937
18150
  if (sourceSize > eagerHydrationMaxBytes()) throw new SessionTranscriptOversizedError(sourceSize);
17938
18151
  const forkEntries = await loadEntriesFromFile(managedSourcePath, storage);
17939
18152
  migrateToCurrentVersion(forkEntries);
@@ -17997,6 +18210,12 @@ export class SessionManager {
17997
18210
  process.platform !== "win32" &&
17998
18211
  sourceSize !== undefined &&
17999
18212
  sourceSize >= autoModeMinTranscriptBytes());
18213
+ let strictSmallInspection: ResumeInspectionSnapshot | undefined;
18214
+ if (boundedAdmission && sourceSize !== undefined && sourceSize <= eagerHydrationMaxBytes()) {
18215
+ const strict = inspectResumeSessionFile(filePath, storage);
18216
+ if ("kind" in strict) throw new Error(`Could not open session: ${strict.reason}`);
18217
+ strictSmallInspection = strict;
18218
+ }
18000
18219
  if (!boundedAdmission) {
18001
18220
  const inspected = inspectResumeSessionFile(filePath, storage);
18002
18221
  if ("kind" in inspected) {
@@ -18029,7 +18248,9 @@ export class SessionManager {
18029
18248
  manager.buildSessionContext();
18030
18249
  return manager;
18031
18250
  }
18032
- const inspected = inspectTranscriptHeaderBounded(filePath, storage, BOUNDED_RESUME_TRANSCRIPT_MAX_BYTES);
18251
+ const inspected = strictSmallInspection
18252
+ ? { ok: true as const, inspection: { cwd: (strictSmallInspection.entries[0] as SessionHeader).cwd } }
18253
+ : inspectTranscriptHeaderBounded(filePath, storage, BOUNDED_RESUME_TRANSCRIPT_MAX_BYTES);
18033
18254
  if (!inspected.ok) {
18034
18255
  if (inspected.error.reason === "missing") {
18035
18256
  const manager = new SessionManager(getProjectDir(), destination.directory, true, storage, destination);
@@ -18050,23 +18271,72 @@ export class SessionManager {
18050
18271
  );
18051
18272
  manager.#sessionMemoryMode = sessionMemoryMode;
18052
18273
  await manager.#initSessionFile(filePath);
18274
+ if (strictSmallInspection) {
18275
+ const revalidated = revalidateTranscriptIdentityBounded(filePath, storage, strictSmallInspection.identity);
18276
+ if (revalidated.kind !== "valid" || manager.#sessionId !== strictSmallInspection.identity.sessionId) {
18277
+ manager.setSessionMemoryMode("off");
18278
+ await manager.close();
18279
+ throw new Error("Could not open session: unstable");
18280
+ }
18281
+ }
18053
18282
  manager.buildSessionContext();
18054
18283
  return manager;
18055
18284
  }
18056
- let managedResumeBounded = sessionMemoryMode === "enabled";
18057
- if (sessionMemoryMode === "auto" && process.platform !== "win32") {
18285
+ const sameManagedDirectory = path.dirname(path.resolve(filePath)) === path.resolve(destination.directory);
18286
+ let managedInspectionStore: ManagedSessionDescendantStore | undefined;
18287
+ let managedInspectionStorage: SessionStorage = storage;
18288
+ if (sameManagedDirectory) {
18289
+ managedInspectionStore = managedStoreFromContext(destination.securityContext, destination.directory);
18290
+ managedInspectionStorage = retainedManagedInspectionStorage(storage, managedInspectionStore, filePath);
18291
+ }
18292
+ let managedSourceSize: number | undefined;
18293
+ try {
18294
+ managedSourceSize = managedInspectionStorage.statSync(filePath).size;
18295
+ } catch {
18296
+ // Strict inspection below reports the stable failure.
18297
+ }
18298
+ const managedResumeBounded =
18299
+ sessionMemoryMode === "enabled" ||
18300
+ (sessionMemoryMode === "auto" &&
18301
+ process.platform !== "win32" &&
18302
+ managedSourceSize !== undefined &&
18303
+ managedSourceSize >= autoModeMinTranscriptBytes());
18304
+ let strictManagedSmallInspection: ResumeInspectionSnapshot | undefined;
18305
+ if (
18306
+ managedResumeBounded &&
18307
+ managedSourceSize !== undefined &&
18308
+ managedSourceSize <= eagerHydrationMaxBytes() &&
18309
+ sameManagedDirectory
18310
+ ) {
18058
18311
  try {
18059
- managedResumeBounded = storage.statSync(filePath).size >= autoModeMinTranscriptBytes();
18060
- } catch {
18061
- managedResumeBounded = false;
18312
+ const strict = inspectResumeSessionFile(filePath, managedInspectionStorage);
18313
+ if ("kind" in strict) throw new Error(`Could not open session: ${strict.reason}`);
18314
+ strictManagedSmallInspection = strict;
18315
+ } catch (error) {
18316
+ managedInspectionStore?.close();
18317
+ throw error;
18062
18318
  }
18063
18319
  }
18064
18320
 
18065
- if (managedResumeBounded && path.dirname(path.resolve(filePath)) === path.resolve(destination.directory)) {
18321
+ if (managedResumeBounded && sameManagedDirectory) {
18066
18322
  const manager = new SessionManager(getProjectDir(), destination.directory, true, storage, destination);
18067
18323
  manager.#sessionMemoryMode = sessionMemoryMode;
18068
18324
  try {
18069
18325
  await manager.#initSessionFile(filePath, true);
18326
+ if (strictManagedSmallInspection) {
18327
+ const revalidated = revalidateTranscriptIdentityBounded(
18328
+ filePath,
18329
+ managedInspectionStorage,
18330
+ strictManagedSmallInspection.identity,
18331
+ );
18332
+ if (
18333
+ revalidated.kind !== "valid" ||
18334
+ manager.#sessionId !== strictManagedSmallInspection.identity.sessionId
18335
+ ) {
18336
+ manager.setSessionMemoryMode("off");
18337
+ throw new Error("Could not open session: unstable");
18338
+ }
18339
+ }
18070
18340
  const header = manager.#fileEntries.find(entry => entry.type === "session") as SessionHeader | undefined;
18071
18341
  if (header?.cwd) manager.cwd = header.cwd;
18072
18342
  manager.buildSessionContext();
@@ -18074,8 +18344,12 @@ export class SessionManager {
18074
18344
  } catch (error) {
18075
18345
  await manager.close().catch(() => {});
18076
18346
  throw error;
18347
+ } finally {
18348
+ managedInspectionStore?.close();
18077
18349
  }
18078
18350
  }
18351
+ managedInspectionStore?.close();
18352
+ managedInspectionStore = undefined;
18079
18353
  const inspected = inspectResumeSessionFile(filePath, storage);
18080
18354
  if ("kind" in inspected) {
18081
18355
  if (inspected.reason === "missing") {
@@ -18132,6 +18406,7 @@ export class SessionManager {
18132
18406
  const boundedAdmission =
18133
18407
  sessionMemoryMode === "enabled" ||
18134
18408
  (sessionMemoryMode === "auto" &&
18409
+ process.platform !== "win32" &&
18135
18410
  capturedDescriptor !== null &&
18136
18411
  capturedDescriptor.size >= autoModeMinTranscriptBytes());
18137
18412
  if (boundedAdmission && capturedDescriptor && capturedDescriptor.size > 0) {
@@ -18300,25 +18575,25 @@ export class SessionManager {
18300
18575
  /** Delete an authorized managed or project-local picker candidate. */
18301
18576
  static async deleteManagedCandidate(sessionPath: string): Promise<void> {
18302
18577
  const storage = new FileSessionStorage();
18303
- const entries = await loadEntriesFromFile(sessionPath, storage);
18304
- const header = entries.find(entry => entry.type === "session") as SessionHeader | undefined;
18305
- if (!header?.cwd) throw new Error("Session has no valid workspace header.");
18306
- const projectGjcDir = path.join(path.resolve(header.cwd), ".gjc");
18578
+ const inspected = inspectTranscriptHeaderBounded(sessionPath, storage, BOUNDED_RESUME_TRANSCRIPT_MAX_BYTES);
18579
+ if (!inspected.ok || !inspected.inspection.cwd) throw new Error("Session has no valid workspace header.");
18580
+ const headerCwd = inspected.inspection.cwd;
18581
+ const projectGjcDir = path.join(path.resolve(headerCwd), ".gjc");
18307
18582
  if (isProjectSessionTranscriptPath(projectGjcDir, sessionPath)) {
18308
18583
  const relativePath = path.relative(projectGjcDir, path.resolve(sessionPath)).split(path.sep).join("/");
18309
18584
  const authority = nativeSessionManager().openRecoveryFsRoot(projectGjcDir);
18310
18585
  try {
18311
- const captured = authority.readManaged(relativePath);
18312
- if (!captured.ok || !captured.identity || !captured.data || !captured.identity.sha256)
18586
+ const observed = authority.stat(relativePath);
18587
+ if (!observed.ok || !observed.identity?.sha256)
18313
18588
  throw new Error("Project session is no longer an authorized candidate.");
18314
18589
  const removed = authority.removeManaged(
18315
18590
  relativePath,
18316
- captured.identity.dev,
18317
- captured.identity.ino,
18318
- captured.identity.size,
18319
- captured.identity.mtimeNs,
18320
- captured.identity.ctimeNs,
18321
- captured.identity.sha256,
18591
+ observed.identity.dev,
18592
+ observed.identity.ino,
18593
+ observed.identity.size,
18594
+ observed.identity.mtimeNs,
18595
+ observed.identity.ctimeNs,
18596
+ observed.identity.sha256,
18322
18597
  );
18323
18598
  if (!removed.ok) throw new Error(removed.code ?? "Could not delete project session.");
18324
18599
  return;
@@ -18328,7 +18603,7 @@ export class SessionManager {
18328
18603
  }
18329
18604
  const sessionsRoot = path.resolve(sessionPath, "../..");
18330
18605
  const resolved = resolveManagedScope({
18331
- cwd: header.cwd,
18606
+ cwd: headerCwd,
18332
18607
  agentDir: path.resolve(sessionsRoot, ".."),
18333
18608
  sessionsRoot,
18334
18609
  });
@@ -18375,8 +18650,48 @@ export class SessionManager {
18375
18650
  ) {
18376
18651
  const boundedManager = new SessionManager(cwd, destination.directory, true, snapshot.storage, destination);
18377
18652
  boundedManager.#sessionMemoryMode = sessionMemoryMode;
18378
- const bounded = await boundedManager.#tryForkFromCapturedBounded(snapshot);
18379
- if (bounded) return bounded;
18653
+ if (
18654
+ destination.kind === "managed" &&
18655
+ snapshot.storage instanceof FileSessionStorage &&
18656
+ pathIsWithin(path.resolve(destination.securityContext.sessionsRoot), path.resolve(snapshot.sourcePath))
18657
+ ) {
18658
+ const sourceDirectory = path.dirname(path.resolve(snapshot.sourcePath));
18659
+ const destinationDirectory = path.resolve(destination.directory);
18660
+ const sourceStore =
18661
+ sourceDirectory === destinationDirectory
18662
+ ? boundedManager.#managedTranscriptStore(snapshot.sourcePath)
18663
+ : managedStoreFromContext(destination.securityContext, sourceDirectory);
18664
+ try {
18665
+ sourceStore.assertBound();
18666
+ const sourceDescriptor = sourceStore.descriptorExpected(path.basename(snapshot.sourcePath));
18667
+ if (!sourceDescriptor || !resumeIdentityMatchesDescriptor(snapshot.identity, sourceDescriptor))
18668
+ throw new Error("identity-mismatch");
18669
+ boundedManager.#boundedManagedSource = {
18670
+ path: snapshot.sourcePath,
18671
+ store: sourceStore,
18672
+ descriptor: sourceDescriptor,
18673
+ owned: sourceDirectory !== destinationDirectory,
18674
+ };
18675
+ } catch (error) {
18676
+ sourceStore.close();
18677
+ await boundedManager.close().catch(() => {});
18678
+ if (toError(error).message === "identity-mismatch")
18679
+ return { kind: "error", reason: "identity-mismatch" };
18680
+ throw error;
18681
+ }
18682
+ }
18683
+ let bounded: StrictSessionForkResult | undefined;
18684
+ try {
18685
+ bounded = await boundedManager.#tryForkFromCapturedBounded(snapshot);
18686
+ } catch (error) {
18687
+ boundedManager.#clearBoundedManagedSource();
18688
+ await boundedManager.close().catch(() => {});
18689
+ throw error;
18690
+ }
18691
+ if (bounded) {
18692
+ boundedManager.#clearBoundedManagedSource();
18693
+ return bounded;
18694
+ }
18380
18695
  await boundedManager.close();
18381
18696
  }
18382
18697
  if (snapshot.identity.size > eagerHydrationMaxBytes())
@@ -18902,6 +19217,7 @@ export class SessionManager {
18902
19217
  this.#sidecarRuntime = undefined;
18903
19218
  this.#releaseManagedSidecarCache();
18904
19219
  this.#boundedReadStorageProxy = undefined;
19220
+ this.#clearBoundedManagedSource();
18905
19221
  throw error;
18906
19222
  }
18907
19223
  this.#sessionFile = promotionPath;
@@ -18993,6 +19309,17 @@ export class SessionManager {
18993
19309
  } catch {
18994
19310
  // The strict read-only inspection below reports the stable failure.
18995
19311
  }
19312
+ let openPath = selectedPath;
19313
+ if (
19314
+ destination.kind === "managed" &&
19315
+ storage instanceof FileSessionStorage &&
19316
+ selectedSize !== undefined &&
19317
+ selectedSize > eagerHydrationMaxBytes() &&
19318
+ path.dirname(path.resolve(selectedPath)) !== path.resolve(destination.directory) &&
19319
+ pathIsWithin(path.resolve(destination.securityContext.sessionsRoot), path.resolve(selectedPath))
19320
+ ) {
19321
+ openPath = await SessionManager.prepareManagedCandidateForWrite(selectedPath, migrationPolicy, destination);
19322
+ }
18996
19323
  const boundedSelected =
18997
19324
  selectedSize !== undefined &&
18998
19325
  selectedSize > eagerHydrationMaxBytes() &&
@@ -19003,7 +19330,7 @@ export class SessionManager {
19003
19330
  selectedSize >= autoModeMinTranscriptBytes()));
19004
19331
  if (boundedSelected) {
19005
19332
  const manager = await SessionManager.open(
19006
- selectedPath,
19333
+ openPath,
19007
19334
  destination,
19008
19335
  storage,
19009
19336
  migrationPolicy,
@@ -19013,7 +19340,7 @@ export class SessionManager {
19013
19340
  await manager.close();
19014
19341
  return undefined;
19015
19342
  }
19016
- const inspected = await SessionManager.inspectSessionTailReadOnly(selectedPath, storage);
19343
+ const inspected = await SessionManager.inspectSessionTailReadOnly(openPath, storage);
19017
19344
  if (inspected.kind === "error") {
19018
19345
  if (inspected.reason === "oversized") throw new SessionTranscriptOversizedError(inspected.size ?? 0);
19019
19346
  if (inspected.reason === "context_too_large") throw new SessionContextTooLargeError(inspected.size ?? 0);