codex-overleaf-link 2.3.6 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +59 -34
  2. package/README.zh-CN.md +543 -0
  3. package/extension/bootstrap/manifest.template.json +1 -1
  4. package/extension/src/background.js +3 -0
  5. package/extension/src/content/activeTurnControl.js +1 -0
  6. package/extension/src/content/assetTransferBroker.js +2 -0
  7. package/extension/src/content/contentRuntime.js +53 -50
  8. package/extension/src/content/contextTray.js +4 -0
  9. package/extension/src/content/diagnosticsController.js +4 -0
  10. package/extension/src/content/diagnosticsPanel.js +128 -4
  11. package/extension/src/content/generated/content.bundle.js +583 -112
  12. package/extension/src/content/generated/content.bundle.meta.json +4 -3
  13. package/extension/src/content/legacyGlobalRegistry.js +1 -0
  14. package/extension/src/content/modelPicker.js +21 -9
  15. package/extension/src/content/modelPickerSupport.js +38 -1
  16. package/extension/src/content/nativeCompatibilityController.js +4 -1
  17. package/extension/src/content/otWarmMirror.js +34 -30
  18. package/extension/src/content/otWarmMirrorController.js +20 -0
  19. package/extension/src/content/pageBridgeClient.js +2 -0
  20. package/extension/src/content/runResultActions.js +16 -1
  21. package/extension/src/content/runTimelineView.js +1 -1
  22. package/extension/src/content/sessionForkController.js +20 -11
  23. package/extension/src/content/trackedChangeCaptureController.js +153 -0
  24. package/extension/src/content/writebackOrchestrator.js +31 -0
  25. package/extension/src/page/binaryAssetUploader.js +101 -29
  26. package/extension/src/page/overleafRealtimeObserver.js +4 -1
  27. package/extension/src/page/textFileCreator.js +493 -0
  28. package/extension/src/page/trackedChangeCapture.js +342 -0
  29. package/extension/src/page/trackedChangesLifecycle.js +145 -149
  30. package/extension/src/page/treeOperations.js +30 -2
  31. package/extension/src/page/writebackRouter.js +94 -48
  32. package/extension/src/pageBridge.js +62 -25
  33. package/extension/src/shared/compatibility.js +1 -1
  34. package/extension/src/shared/pageRpcContract.js +2 -1
  35. package/extension/src/shared/sessionState.js +6 -2
  36. package/extension/src/shared/storageDb.js +10 -12
  37. package/extension/src/shared/storageRunActions.js +29 -1
  38. package/extension/src/shared/writebackSettlement.js +19 -5
  39. package/extension/styles/panel.css +55 -10
  40. package/native-host/src/taskRunnerRuntime.js +25 -0
  41. package/package.json +5 -1
@@ -1,3 +1,4 @@
1
+ if (typeof document !== 'undefined' && document.documentElement) document.documentElement.setAttribute('data-codex-overleaf-source-digest', "f146d7e07b67706193fa5fcff26d7c9719826ac745aec9b745dd95c52413d27c");
1
2
  (() => {
2
3
  // extension/entries/content-entry.mjs
3
4
  (function runClassicContentModule(module, exports, require2) {
@@ -7886,7 +7887,7 @@ ${block}`;
7886
7887
  if (!result || typeof result !== "object" || result.ok === false) return result;
7887
7888
  if (Array.isArray(result.skipped) && result.skipped.length > 0) return result;
7888
7889
  const expectedFiles = Array.isArray(run?.undoExpectedFiles) ? run.undoExpectedFiles : [];
7889
- if (!expectedFiles.length || isUndoVerifiedContentMatching(run, result)) return result;
7890
+ if (!expectedFiles.length || isUndoVerifiedContentMatching(run, result, options2)) return result;
7890
7891
  const path = expectedFiles.find((entry) => typeof entry?.path === "string")?.path || "";
7891
7892
  appendSyntheticFailure(result, options2.buildFailure?.("undo_not_verified", { path, type: "undo" }, {
7892
7893
  changedDocument: true,
@@ -7895,12 +7896,13 @@ ${block}`;
7895
7896
  }), path, "undo_not_verified", "undo");
7896
7897
  return result;
7897
7898
  }
7898
- function isUndoVerifiedContentMatching(run, result) {
7899
+ function isUndoVerifiedContentMatching(run, result, options2 = {}) {
7899
7900
  const expectedByPath = new Map((run?.undoExpectedFiles || []).filter((file) => typeof file?.path === "string" && typeof file?.content === "string").map((file) => [normalizeSettlementPath(file.path), file.content]).filter(([path]) => path));
7900
7901
  if (!expectedByPath.size) return true;
7902
+ const postByPath = new Map((Array.isArray(options2?.postFiles) ? options2.postFiles : []).filter((file) => typeof file?.path === "string" && typeof file?.content === "string").map((file) => [normalizeSettlementPath(file.path), file.content]));
7901
7903
  const applied = Array.isArray(result?.applied) ? result.applied : [];
7902
7904
  return Array.from(expectedByPath).every(
7903
- ([path, expected]) => applied.some((entry) => isVerifiedUndoEntryForPath(entry, path, expected))
7905
+ ([path, expected]) => applied.some((entry) => isVerifiedUndoEntryForPath(entry, path, expected, postByPath.get(path)))
7904
7906
  );
7905
7907
  }
7906
7908
  function isUndoResultEffectivelyApplied(run, result, undoOperations = []) {
@@ -7914,7 +7916,7 @@ ${block}`;
7914
7916
  (item) => isVerifiedUndoEntryForPath(item, path, expectedByPath.get(path))
7915
7917
  ));
7916
7918
  }
7917
- function isVerifiedUndoEntryForPath(entry, path, expectedContent) {
7919
+ function isVerifiedUndoEntryForPath(entry, path, expectedContent, postContent) {
7918
7920
  const inner = entry?.result || {};
7919
7921
  if (inner.ok === false) return false;
7920
7922
  const normalizedPath = normalizeSettlementPath(path);
@@ -7929,7 +7931,12 @@ ${block}`;
7929
7931
  return inner.method === "overleaf-editor-undo" && inner.verified === true && inner.verifiedContent === expectedContent;
7930
7932
  }
7931
7933
  if (key === `snapshot-undo:${normalizedPath}`) {
7932
- return inner.verifiedContent === expectedContent;
7934
+ if (inner.verifiedContent === expectedContent) return true;
7935
+ const proof = inner.undoRebaseProof, before = proof?.beforeUndoContent;
7936
+ if (inner.verified !== true || proof?.version !== 1 || normalizeSettlementPath(proof.path) !== normalizedPath || typeof before !== "string" || typeof postContent !== "string" || !postContent || typeof inner.verifiedContent !== "string") return false;
7937
+ const start = before.indexOf(postContent);
7938
+ if (start < 0 || before.indexOf(postContent, start + 1) >= 0) return false;
7939
+ return inner.verifiedContent === before.slice(0, start) + expectedContent + before.slice(start + postContent.length);
7933
7940
  }
7934
7941
  return false;
7935
7942
  }
@@ -8775,6 +8782,7 @@ ${block}`;
8775
8782
  undoExpectedFiles: normalizeRunFiles(run.undoExpectedFiles),
8776
8783
  undoStatus: sanitizeAssistantVisibleText(run.undoStatus),
8777
8784
  nativeRequestId: sanitizeAssistantVisibleText(run.nativeRequestId),
8785
+ codexThreadId: sanitizeAssistantVisibleText(run.codexThreadId),
8778
8786
  codexTurnId: sanitizeAssistantVisibleText(run.codexTurnId),
8779
8787
  forkSnapshot: run.forkSnapshot === true,
8780
8788
  forkSourceRunId: sanitizeAssistantVisibleText(run.forkSourceRunId),
@@ -8792,6 +8800,9 @@ ${block}`;
8792
8800
  if (run.changedDocument === true) {
8793
8801
  normalized.changedDocument = true;
8794
8802
  }
8803
+ if (Array.isArray(run.trackedChangeCaptures) && run.trackedChangeCaptures.length && !run.forkSnapshot) {
8804
+ normalized.trackedChangeCaptures = sanitizeAssistantVisibleValue(run.trackedChangeCaptures);
8805
+ }
8795
8806
  applyTrackedChangeStatus(normalized, run.trackedChangeStatus);
8796
8807
  return normalized;
8797
8808
  }
@@ -9226,6 +9237,7 @@ ${block}`;
9226
9237
  undoExpectedFiles: undoPayload.undoExpectedFiles,
9227
9238
  undoStatus: summarizeTextForStorage(run.undoStatus, "undo status"),
9228
9239
  nativeRequestId: normalizeTextField(run.nativeRequestId, 160),
9240
+ codexThreadId: normalizeTextField(run.codexThreadId, 160),
9229
9241
  codexTurnId: normalizeTextField(run.codexTurnId, 160),
9230
9242
  forkSnapshot: run.forkSnapshot === true,
9231
9243
  forkSourceRunId: normalizeTextField(run.forkSourceRunId, 160),
@@ -9558,6 +9570,7 @@ ${block}`;
9558
9570
  setActiveSession,
9559
9571
  updateActiveSession,
9560
9572
  normalizeRuns,
9573
+ normalizeProjectReferenceFiles,
9561
9574
  prepareStateForStorage,
9562
9575
  estimateJsonBytes,
9563
9576
  computeSafeTaskSummary
@@ -9798,7 +9811,13 @@ ${block}`;
9798
9811
  } catch (_error) {
9799
9812
  return empty;
9800
9813
  }
9801
- return getUtf8ByteLength(serialized) <= MAX_PERSISTED_ACTION_BYTES_PER_RUN ? payload : empty;
9814
+ if (getUtf8ByteLength(serialized) > MAX_PERSISTED_ACTION_BYTES_PER_RUN) return empty;
9815
+ const captures = cloneSerializableArray(run.trackedChangeCaptures);
9816
+ if (captures.length) {
9817
+ const extended = { ...payload, trackedChangeCaptures: captures };
9818
+ if (getUtf8ByteLength(JSON.stringify(extended)) <= MAX_PERSISTED_ACTION_BYTES_PER_RUN) return extended;
9819
+ }
9820
+ return payload;
9802
9821
  }
9803
9822
  function emptyActionPayload() {
9804
9823
  return {
@@ -9826,6 +9845,37 @@ ${block}`;
9826
9845
  }
9827
9846
  return value.length * 2;
9828
9847
  }
9848
+ function compactRunExecutionSnapshot(run, normalizeString) {
9849
+ var snapshot = run && run.executionSnapshot;
9850
+ if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot)) return {};
9851
+ var normalize = typeof normalizeString === "function" ? normalizeString : String;
9852
+ var compact = {};
9853
+ var fields = [
9854
+ "schemaVersion",
9855
+ "mode",
9856
+ "providerId",
9857
+ "providerRevision",
9858
+ "model",
9859
+ "reasoningEffort",
9860
+ "speedTier",
9861
+ "autoRecompile",
9862
+ "requireReviewing",
9863
+ "capturedAt",
9864
+ "source"
9865
+ ];
9866
+ for (var key of fields) {
9867
+ if (!Object.prototype.hasOwnProperty.call(snapshot, key)) continue;
9868
+ var value = snapshot[key];
9869
+ if (typeof value === "string") compact[key] = normalize(value);
9870
+ else if (value === null || typeof value === "number" || typeof value === "boolean") compact[key] = value;
9871
+ }
9872
+ if (Array.isArray(snapshot.focusFiles)) {
9873
+ compact.focusFiles = snapshot.focusFiles.slice(0, 100).filter(function(value2) {
9874
+ return typeof value2 === "string";
9875
+ }).map(normalize);
9876
+ }
9877
+ return { executionSnapshot: compact };
9878
+ }
9829
9879
  function compactProviderSnapshot(value) {
9830
9880
  return {
9831
9881
  providerId: typeof value.providerId === "string" && value.providerId ? value.providerId : "builtin",
@@ -9868,6 +9918,7 @@ ${block}`;
9868
9918
  compactRunsForStorage,
9869
9919
  compactRunActionPayload,
9870
9920
  compactProviderSnapshot,
9921
+ compactRunExecutionSnapshot,
9871
9922
  hashString
9872
9923
  };
9873
9924
  });
@@ -9945,19 +9996,21 @@ ${block}`;
9945
9996
  require2("./storageRunActions"),
9946
9997
  require2("./runInputQueue"),
9947
9998
  require2("./settlementFacts"),
9948
- require2("./storageSessionClaims")
9999
+ require2("./storageSessionClaims"),
10000
+ require2("./sessionState")
9949
10001
  );
9950
10002
  } else {
9951
10003
  root.CodexOverleafModuleRegistry.define("StorageDb", [
9952
10004
  "StorageRunActions",
9953
10005
  "RunInputQueue",
9954
10006
  "SettlementFacts",
9955
- "StorageSessionClaims"
10007
+ "StorageSessionClaims",
10008
+ "SessionState"
9956
10009
  ], factory);
9957
10010
  }
9958
- })(typeof globalThis !== "undefined" ? globalThis : window, function storageDbFactory(StorageRunActions, RunInputQueue, SettlementFacts, StorageSessionClaims) {
10011
+ })(typeof globalThis !== "undefined" ? globalThis : window, function storageDbFactory(StorageRunActions, RunInputQueue, SettlementFacts, StorageSessionClaims, SessionState) {
9959
10012
  "use strict";
9960
- if (!StorageRunActions?.compactRunsForStorage || !StorageRunActions?.compactRunActionPayload || !StorageRunActions?.compactProviderSnapshot || !StorageRunActions?.compactStructuredEventValue || !StorageRunActions?.hashString || !RunInputQueue?.compactForStorage || !SettlementFacts?.compactSettlementFacts || !StorageSessionClaims?.createSessionClaimer) {
10013
+ if (!StorageRunActions?.compactRunsForStorage || !StorageRunActions?.compactRunActionPayload || !StorageRunActions?.compactProviderSnapshot || !StorageRunActions?.compactRunExecutionSnapshot || !StorageRunActions?.compactStructuredEventValue || !StorageRunActions?.hashString || !RunInputQueue?.compactForStorage || !SettlementFacts?.compactSettlementFacts || !StorageSessionClaims?.createSessionClaimer) {
9961
10014
  throw new Error("Codex Overleaf storage run-action helpers are unavailable.");
9962
10015
  }
9963
10016
  var TARGET_SCHEMA_VERSION = 3;
@@ -10225,6 +10278,7 @@ ${block}`;
10225
10278
  codexThreadId: typeof input.codexThreadId === "string" ? input.codexThreadId : "",
10226
10279
  status: typeof input.status === "string" && input.status ? input.status : "active",
10227
10280
  focusFiles: normalizePathList(input.focusFiles),
10281
+ projectReferenceFiles: SessionState.normalizeProjectReferenceFiles(input.projectReferenceFiles),
10228
10282
  pendingInputs: RunInputQueue.compactForStorage(input.pendingInputs, {
10229
10283
  normalizeField: normalizeTextField,
10230
10284
  normalizeDisplay: normalizeDisplayTextForStorage,
@@ -10264,8 +10318,8 @@ ${block}`;
10264
10318
  }
10265
10319
  }
10266
10320
  function deriveSafeTaskSummaryFromInput(input) {
10267
- var SessionState = loadSessionState();
10268
- if (!SessionState || !(SessionState.computeSafeTaskSummary instanceof Function)) {
10321
+ var SessionState2 = loadSessionState();
10322
+ if (!SessionState2 || !(SessionState2.computeSafeTaskSummary instanceof Function)) {
10269
10323
  return "";
10270
10324
  }
10271
10325
  var task = typeof input.task === "string" && input.task ? input.task : "";
@@ -10275,7 +10329,7 @@ ${block}`;
10275
10329
  task = firstRun.task;
10276
10330
  }
10277
10331
  }
10278
- return SessionState.computeSafeTaskSummary(task);
10332
+ return SessionState2.computeSafeTaskSummary(task);
10279
10333
  }
10280
10334
  var _sessionStateCache = null;
10281
10335
  function loadSessionState() {
@@ -10560,12 +10614,7 @@ ${block}`;
10560
10614
  });
10561
10615
  }
10562
10616
  function compactRunsForStorage(runs, options2) {
10563
- return StorageRunActions.compactRunsForStorage(
10564
- runs,
10565
- options2,
10566
- SESSION_STORAGE_LIMITS.maxRunsPerSession,
10567
- compactRunForStorage
10568
- );
10617
+ return StorageRunActions.compactRunsForStorage(runs, options2, SESSION_STORAGE_LIMITS.maxRunsPerSession, compactRunForStorage);
10569
10618
  }
10570
10619
  function compactRunForStorage(run, keepActionPayload) {
10571
10620
  var actionPayload = StorageRunActions.compactRunActionPayload(run, keepActionPayload);
@@ -10574,6 +10623,9 @@ ${block}`;
10574
10623
  task: normalizeDisplayTextForStorage(run.task || "untitled task", SESSION_STORAGE_LIMITS.taskChars),
10575
10624
  mode: typeof run.mode === "string" ? redactSecretLikeText(run.mode) : "",
10576
10625
  ...StorageRunActions.compactProviderSnapshot(run),
10626
+ ...StorageRunActions.compactRunExecutionSnapshot(run, function(text) {
10627
+ return normalizeTextField(text, 4096);
10628
+ }),
10577
10629
  model: normalizeTextField(run.model, 80),
10578
10630
  reasoningEffort: typeof run.reasoningEffort === "string" ? redactSecretLikeText(run.reasoningEffort) : "",
10579
10631
  speedTier: typeof run.speedTier === "string" ? redactSecretLikeText(run.speedTier) : "",
@@ -10589,8 +10641,10 @@ ${block}`;
10589
10641
  undoBaseFiles: actionPayload.undoBaseFiles,
10590
10642
  undoTrackedChanges: actionPayload.undoTrackedChanges,
10591
10643
  undoExpectedFiles: actionPayload.undoExpectedFiles,
10644
+ ...actionPayload.trackedChangeCaptures ? { trackedChangeCaptures: actionPayload.trackedChangeCaptures } : {},
10592
10645
  undoStatus: normalizeDisplayTextForStorage(run.undoStatus, SESSION_STORAGE_LIMITS.statusTextChars),
10593
10646
  nativeRequestId: normalizeTextField(run.nativeRequestId, 160),
10647
+ codexThreadId: normalizeTextField(run.codexThreadId, 160),
10594
10648
  codexTurnId: normalizeTextField(run.codexTurnId, 160),
10595
10649
  nativeEventSeq: Number.isFinite(Number(run.nativeEventSeq)) ? Number(run.nativeEventSeq) : 0,
10596
10650
  queueItemId: normalizeTextField(run.queueItemId, 160)
@@ -11775,7 +11829,7 @@ ${block}`;
11775
11829
  const MIN_NATIVE_VERSION = "1.0.0";
11776
11830
  const MIN_COMPATIBLE_NATIVE_VERSION = "1.0.0";
11777
11831
  const MIN_COMPATIBLE_EXTENSION_VERSION = "1.0.0";
11778
- const BUILD_TARGET_VERSION = "2.3.6";
11832
+ const BUILD_TARGET_VERSION = "2.4.0";
11779
11833
  const DEFAULT_CHROME_EXTENSION_ID = "illdpneeeopfffmiepaejglgmhpmdhdc";
11780
11834
  const REQUIRED_CAPABILITIES = Object.freeze([
11781
11835
  "bridgePing",
@@ -12078,7 +12132,7 @@ ${block}`;
12078
12132
  if (root) root.CodexOverleafPageRpcContract = api;
12079
12133
  })(typeof window !== "undefined" ? window : typeof globalThis !== "undefined" ? globalThis : this, function() {
12080
12134
  "use strict";
12081
- var REVISION = "2026-08-04-page-rpc-contract-v15";
12135
+ var REVISION = "2026-09-14-native-text-upload-v28";
12082
12136
  var METHODS = freezeCatalog({
12083
12137
  initializeCapability: entry("bridge.initialize", "control", "none", "default", false, "none"),
12084
12138
  probe: entry("bridge.probe", "read", "none", "default", false, "safe"),
@@ -12095,6 +12149,7 @@ ${block}`;
12095
12149
  binaryUploadCommit: entry("asset.write", "document", "required", "writeback", true, "no_retry"),
12096
12150
  binaryUploadAbort: entry("asset.stage", "cache", "none", "default", false, "idempotent"),
12097
12151
  jumpToPosition: entry("editor.navigate", "navigation", "required", "default", false, "idempotent"),
12152
+ reconcileTrackedChangeCapture: entry("tracked_changes.read", "read", "required", "default", false, "safe"),
12098
12153
  rejectTrackedChanges: entry("tracked_changes.reject", "document", "required", "lifecycle", true, "no_retry"),
12099
12154
  acceptTrackedChanges: entry("tracked_changes.accept", "document", "required", "lifecycle", true, "no_retry"),
12100
12155
  triggerCompile: entry("compile.trigger", "page_state", "required", "compile", false, "no_retry"),
@@ -13463,9 +13518,11 @@ ${block}`;
13463
13518
  ["src/page/overleafProjectSnapshot.js", "codex-overleaf-project-snapshot-script"],
13464
13519
  ["src/page/pageBridgeCapability.js", "codex-overleaf-page-bridge-capability-script"],
13465
13520
  ["src/page/treeOperations.js", "codex-overleaf-tree-operations-script", true],
13521
+ ["src/page/textFileCreator.js", "codex-overleaf-text-file-creator-script", true],
13466
13522
  ["src/page/binaryAssetUploader.js", "codex-overleaf-binary-asset-uploader-script", true],
13467
13523
  ["src/page/snapshotRouter.js", "codex-overleaf-snapshot-router-script"],
13468
13524
  ["src/page/writeGuard.js", "codex-overleaf-write-guard-script", true],
13525
+ ["src/page/trackedChangeCapture.js", "codex-overleaf-tracked-change-capture-script", true],
13469
13526
  ["src/page/trackedChangesLifecycle.js", "codex-overleaf-tracked-changes-lifecycle-script", true],
13470
13527
  ["src/page/writebackRouter.js", "codex-overleaf-writeback-router-script", true],
13471
13528
  ["src/pageBridge.js", "codex-overleaf-page-bridge-script", true]
@@ -13665,7 +13722,9 @@ ${block}`;
13665
13722
  async function dispatch(payload, channelMethod) {
13666
13723
  const compatibilityGate = await ensureForMethod(payload?.method);
13667
13724
  if (!compatibilityGate.ok) return compatibilityGate.response;
13668
- if (gatedMethods.has(payload?.method)) options2.throwIfCancellationRequested?.();
13725
+ if (gatedMethods.has(payload?.method) && payload.method !== "mirror.invalidate") {
13726
+ options2.throwIfCancellationRequested?.();
13727
+ }
13669
13728
  return nativeChannel[channelMethod](
13670
13729
  attachEvidence(payload, compatibilityGate.compatibility)
13671
13730
  );
@@ -14318,8 +14377,10 @@ ${block}`;
14318
14377
  merged.applied.push(...Array.isArray(result?.applied) ? result.applied : []);
14319
14378
  merged.skipped.push(...Array.isArray(result?.skipped) ? result.skipped : []);
14320
14379
  merged.trackedChanges.push(...Array.isArray(result?.trackedChanges) ? result.trackedChanges : []);
14380
+ if (result?.trackedChangeCaptures?.length) (merged.trackedChangeCaptures || (merged.trackedChangeCaptures = [])).push(...result.trackedChangeCaptures);
14321
14381
  if (result?.ok !== true) merged.ok = false;
14322
14382
  }
14383
+ if (merged.trackedChangeCaptures?.some((capture) => capture.state !== "observed")) merged.trackedChanges = [];
14323
14384
  if (merged.skipped.length) merged.ok = false;
14324
14385
  return merged;
14325
14386
  }
@@ -15315,6 +15376,8 @@ ${block}`;
15315
15376
  saveState = null,
15316
15377
  saveStateSoon = () => {
15317
15378
  },
15379
+ onProjectFilesReady = () => {
15380
+ },
15318
15381
  updateActiveSession = defaultUpdateActiveSession,
15319
15382
  callPageBridge = () => Promise.resolve({ ok: false }),
15320
15383
  getCurrentProjectId = () => "",
@@ -15478,6 +15541,7 @@ ${block}`;
15478
15541
  renderContextFiles(contextProject);
15479
15542
  if (isExactContextFileListProject(contextProject)) {
15480
15543
  setContextSyncState("exact-ready");
15544
+ onProjectFilesReady(contextProject);
15481
15545
  } else if (contextSyncState !== "partial") {
15482
15546
  setContextSyncState("exact-loading");
15483
15547
  void enhanceContextFilesFromExactSnapshot({ loadId: contextLoadId });
@@ -15499,6 +15563,7 @@ ${block}`;
15499
15563
  contextProject = project;
15500
15564
  setContextSyncState(isExactContextFileListProject(project) ? "exact-ready" : "exact-loading");
15501
15565
  renderContextFiles(project);
15566
+ if (isExactContextFileListProject(project)) onProjectFilesReady(project);
15502
15567
  if (!isExactContextFileListProject(project)) {
15503
15568
  void enhanceContextFilesFromExactSnapshot({
15504
15569
  force: Boolean(options2.force),
@@ -15556,6 +15621,7 @@ ${block}`;
15556
15621
  contextProject = project;
15557
15622
  setContextSyncState("exact-ready");
15558
15623
  renderContextFiles(project);
15624
+ onProjectFilesReady(project);
15559
15625
  } catch (_error) {
15560
15626
  if (loadId === contextLoadId) {
15561
15627
  setContextSyncState("partial");
@@ -16756,6 +16822,20 @@ ${block}`;
16756
16822
  })) return { ok: false, reason: "ot_event_expired" };
16757
16823
  return { ok: true };
16758
16824
  }
16825
+ function readOtBridgeStatus(response) {
16826
+ if (!response || response.ok === false) return "unavailable";
16827
+ return response.state || response.status || response.result?.state || response.result?.status || "unavailable";
16828
+ }
16829
+ function readOtBridgeEvents(response) {
16830
+ if (Array.isArray(response)) return response;
16831
+ if (Array.isArray(response?.events)) return response.events;
16832
+ if (Array.isArray(response?.result?.events)) return response.result.events;
16833
+ return [];
16834
+ }
16835
+ function readOtBridgeErrorCode(response, fallback) {
16836
+ const value = response?.lastErrorCode || response?.reason || response?.error?.code || response?.error || fallback;
16837
+ return typeof value === "string" ? value : fallback;
16838
+ }
16759
16839
  function normalizeFailureCode(value, fallback = "ot_error") {
16760
16840
  const code = typeof value === "string" ? value : value?.code;
16761
16841
  if (/^[a-z][a-z0-9_]{0,79}$/.test(code || "")) return code;
@@ -16833,6 +16913,9 @@ ${block}`;
16833
16913
  takePatchBatch,
16834
16914
  validatePatchReceipt,
16835
16915
  normalizeFailureCode,
16916
+ readOtBridgeStatus,
16917
+ readOtBridgeEvents,
16918
+ readOtBridgeErrorCode,
16836
16919
  getFailureMessageKey,
16837
16920
  buildPatchFilesRequest,
16838
16921
  canUseOtWarmStart,
@@ -20428,7 +20511,8 @@ ${block}`;
20428
20511
  container,
20429
20512
  callbacks: options2.callbacks || {},
20430
20513
  i18n: options2.i18n || {},
20431
- dismissInstalled: false
20514
+ dismissInstalled: false,
20515
+ cleanup: []
20432
20516
  };
20433
20517
  container.innerHTML = `
20434
20518
  <div class="codex-diagnostics-wrap">
@@ -20512,6 +20596,11 @@ ${block}`;
20512
20596
  instance.callbacks.onBeforeOpen?.();
20513
20597
  }
20514
20598
  popover.hidden = !open;
20599
+ if (open) {
20600
+ openFloating(instance, popover, 300);
20601
+ } else {
20602
+ hideFloating(popover);
20603
+ }
20515
20604
  button.dataset.active = open ? "true" : "false";
20516
20605
  button.setAttribute("aria-expanded", open ? "true" : "false");
20517
20606
  }
@@ -20522,7 +20611,7 @@ ${block}`;
20522
20611
  if (!popover || !button) {
20523
20612
  return;
20524
20613
  }
20525
- popover.hidden = true;
20614
+ hideFloating(popover);
20526
20615
  button.dataset.active = "false";
20527
20616
  button.setAttribute("aria-expanded", "false");
20528
20617
  }
@@ -20530,7 +20619,7 @@ ${block}`;
20530
20619
  const instance = target?._instance || target;
20531
20620
  const result = instance?.container?.querySelector("[data-diagnostics-result]");
20532
20621
  if (result) {
20533
- result.hidden = true;
20622
+ hideFloating(result);
20534
20623
  }
20535
20624
  }
20536
20625
  function showLoading(target, title, subtitle) {
@@ -20586,6 +20675,65 @@ ${block}`;
20586
20675
  details.open = false;
20587
20676
  details.hidden = !technicalText;
20588
20677
  technical.textContent = technicalText;
20678
+ closeMenu(instance);
20679
+ openFloating(instance, root, 560);
20680
+ }
20681
+ function openFloating(instance, element, preferredWidth) {
20682
+ if (element.isConnected === false) return;
20683
+ element.hidden = false;
20684
+ if (typeof element.showPopover === "function") {
20685
+ element.setAttribute("popover", "manual");
20686
+ try {
20687
+ if (!element.matches(":popover-open")) element.showPopover();
20688
+ } catch (_error) {
20689
+ element.removeAttribute("popover");
20690
+ }
20691
+ }
20692
+ positionFloating(instance, element, preferredWidth);
20693
+ }
20694
+ function hideFloating(element) {
20695
+ if (!element) return;
20696
+ if (typeof element.hidePopover === "function" && element.getAttribute("popover") === "manual") {
20697
+ try {
20698
+ if (element.matches(":popover-open")) element.hidePopover();
20699
+ } catch (_error) {
20700
+ element.removeAttribute("popover");
20701
+ }
20702
+ }
20703
+ element.hidden = true;
20704
+ }
20705
+ function positionFloating(instance, element, preferredWidth) {
20706
+ if (!element || element.hidden) return;
20707
+ const button = instance.container.querySelector("[data-diagnostics-menu]");
20708
+ if (!button?.getBoundingClientRect || !element.getBoundingClientRect) return;
20709
+ if (button.getClientRects && !button.getClientRects().length) {
20710
+ closeMenu(instance);
20711
+ closeResult(instance);
20712
+ return;
20713
+ }
20714
+ const doc = instance.container.ownerDocument || document;
20715
+ const view = doc.defaultView || window;
20716
+ const viewport = view.visualViewport;
20717
+ const width = viewport?.width || view.innerWidth || doc.documentElement?.clientWidth;
20718
+ const height = viewport?.height || view.innerHeight || doc.documentElement?.clientHeight;
20719
+ if (!(width > 0 && height > 0)) return;
20720
+ const margin = 12;
20721
+ const gap = 8;
20722
+ const leftEdge = (viewport?.offsetLeft || 0) + margin;
20723
+ const topEdge = (viewport?.offsetTop || 0) + margin;
20724
+ const rightEdge = leftEdge + Math.max(0, width - margin * 2);
20725
+ const bottomEdge = topEdge + Math.max(0, height - margin * 2);
20726
+ element.style.width = `${Math.min(preferredWidth, Math.max(0, width - margin * 2))}px`;
20727
+ element.style.maxHeight = `${Math.min(640, Math.max(0, height - margin * 2))}px`;
20728
+ const anchor = button.getBoundingClientRect();
20729
+ const bounds = element.getBoundingClientRect();
20730
+ const below = Math.max(0, bottomEdge - anchor.bottom - gap);
20731
+ const above = Math.max(0, anchor.top - topEdge - gap);
20732
+ const desiredTop = bounds.height > below && above > below ? anchor.top - gap - bounds.height : anchor.bottom + gap;
20733
+ element.style.left = `${Math.max(leftEdge, Math.min(anchor.right - bounds.width, rightEdge - bounds.width))}px`;
20734
+ element.style.top = `${Math.max(topEdge, Math.min(desiredTop, bottomEdge - bounds.height))}px`;
20735
+ element.style.right = "auto";
20736
+ element.style.bottom = "auto";
20589
20737
  }
20590
20738
  function renderInstallCommand(instance, container, command) {
20591
20739
  const wrap = document.createElement("div");
@@ -20660,13 +20808,60 @@ ${block}`;
20660
20808
  return;
20661
20809
  }
20662
20810
  instance.dismissInstalled = true;
20663
- document.addEventListener("click", (event) => {
20811
+ const doc = instance.container.ownerDocument || document;
20812
+ const view = doc.defaultView || window;
20813
+ const listen = (target, type, handler, options2) => {
20814
+ if (!target?.addEventListener) return;
20815
+ target.addEventListener(type, handler, options2);
20816
+ instance.cleanup.push(() => target.removeEventListener(type, handler, options2));
20817
+ };
20818
+ listen(doc, "click", (event) => {
20664
20819
  const wrap = instance.container?.querySelector(".codex-diagnostics-wrap");
20665
20820
  if (!wrap || wrap.contains(event.target)) {
20666
20821
  return;
20667
20822
  }
20668
20823
  closeMenu(instance);
20669
20824
  }, true);
20825
+ listen(doc, "keydown", (event) => {
20826
+ if (event.key !== "Escape") return;
20827
+ const menu = instance.container.querySelector("[data-diagnostics-popover]");
20828
+ const result = instance.container.querySelector("[data-diagnostics-result]");
20829
+ if (menu && !menu.hidden) closeMenu(instance);
20830
+ else if (result && !result.hidden) closeResult(instance);
20831
+ else return;
20832
+ event.preventDefault();
20833
+ event.stopPropagation();
20834
+ instance.container.querySelector("[data-diagnostics-menu]")?.focus();
20835
+ }, true);
20836
+ let frame = null;
20837
+ const reposition = () => {
20838
+ frame = null;
20839
+ positionFloating(instance, instance.container.querySelector("[data-diagnostics-popover]"), 300);
20840
+ positionFloating(instance, instance.container.querySelector("[data-diagnostics-result]"), 560);
20841
+ };
20842
+ const schedule = () => {
20843
+ if (frame !== null) return;
20844
+ if (view.requestAnimationFrame) frame = view.requestAnimationFrame(reposition);
20845
+ else reposition();
20846
+ };
20847
+ listen(view, "resize", schedule);
20848
+ listen(doc, "scroll", schedule, true);
20849
+ listen(view.visualViewport, "resize", schedule);
20850
+ listen(view.visualViewport, "scroll", schedule);
20851
+ if (typeof view.ResizeObserver === "function") {
20852
+ const observer = new view.ResizeObserver(schedule);
20853
+ for (const node of [
20854
+ instance.container.closest("#codex-overleaf-panel") || instance.container,
20855
+ instance.container.querySelector("[data-diagnostics-popover]"),
20856
+ instance.container.querySelector("[data-diagnostics-result]")
20857
+ ]) {
20858
+ if (node) observer.observe(node);
20859
+ }
20860
+ instance.cleanup.push(() => observer.disconnect());
20861
+ }
20862
+ instance.cleanup.push(() => {
20863
+ if (frame !== null) view.cancelAnimationFrame?.(frame);
20864
+ });
20670
20865
  }
20671
20866
  const HEALTH_STATES = ["ok", "warn", "fail", "unknown"];
20672
20867
  function updateStatus(target, status = {}) {
@@ -20823,6 +21018,10 @@ ${block}`;
20823
21018
  return key;
20824
21019
  }
20825
21020
  function destroy(instance) {
21021
+ closeMenu(instance);
21022
+ closeResult(instance);
21023
+ for (const cleanup of instance.cleanup || []) cleanup();
21024
+ instance.cleanup = [];
20826
21025
  instance.container.textContent = "";
20827
21026
  }
20828
21027
  window.CodexOverleafDiagnosticsPanel = {
@@ -21290,6 +21489,7 @@ ${block}`;
21290
21489
  const raw = entry?.event || {};
21291
21490
  if (raw.type === "codex.turn.bound") {
21292
21491
  session.codexThreadId = String(raw.detail?.threadId || session.codexThreadId || "");
21492
+ record.codexThreadId = String(raw.detail?.threadId || record.codexThreadId || "");
21293
21493
  record.codexTurnId = String(raw.detail?.turnId || record.codexTurnId || "");
21294
21494
  record.nativeEventSeq = sequence;
21295
21495
  continue;
@@ -29322,6 +29522,8 @@ failedAt: ${new Date(failure.at).toISOString()}`;
29322
29522
  lines.push(`CodeMirror: docLength=${view.docLength || 0}; dispatch=${Boolean(view.hasDispatch)}; source=${view.source || "unknown"}`);
29323
29523
  }
29324
29524
  }
29525
+ const capture = probe?.trackedChangeCapture;
29526
+ if (capture) lines.push(tx("Tracked ledger: ", "\u7559\u75D5\u8BB0\u5F55\uFF1A") + "source=" + capture.source + "; ready=" + capture.ready + "; count=" + capture.count + "; doc=" + (capture.docId || "none") + "; adapter=" + capture.adapter + "; reason=" + (capture.reason || "none"));
29325
29527
  const projectDiagnostics = probe?.projectDiagnostics;
29326
29528
  if (projectDiagnostics) {
29327
29529
  lines.push(`Project records: ${projectDiagnostics.docRecordCount || 0}; roots=${(projectDiagnostics.internalRootKeys || []).slice(0, 6).join(", ") || "none"}`);
@@ -29548,7 +29750,17 @@ failedAt: ${new Date(failure.at).toISOString()}`;
29548
29750
  textarea.remove();
29549
29751
  if (!copied) throw new Error("clipboard_copy_failed");
29550
29752
  }
29551
- return Object.freeze({ configureResultActions });
29753
+ return Object.freeze({ configureResultActions, projectUndoAvailability });
29754
+ }
29755
+ function projectUndoAvailability(run, projectRunSettlement) {
29756
+ const payload = run.recoveryPayload && typeof run.recoveryPayload === "object" ? run.recoveryPayload : run;
29757
+ if (!Array.isArray(payload.appliedOperations)) return projectRunSettlement(run);
29758
+ const appliedOperations = payload.appliedOperations.filter((operation) => operation?.type !== "binary-create" && operation?.type !== "overwrite-binary");
29759
+ if (appliedOperations.length === payload.appliedOperations.length) return projectRunSettlement(run);
29760
+ return projectRunSettlement({
29761
+ ...run,
29762
+ recoveryPayload: { ...payload, appliedOperations }
29763
+ });
29552
29764
  }
29553
29765
  return Object.freeze({ create });
29554
29766
  });
@@ -30580,7 +30792,7 @@ ${conclusion}` : `${label} ${conclusion}`;
30580
30792
  configureLifecycleUndoButton(button, run);
30581
30793
  return;
30582
30794
  }
30583
- const projection = projectRunSettlement(run);
30795
+ const projection = RunResultActions.projectUndoAvailability(run, projectRunSettlement);
30584
30796
  if (!projection.canUndo && run.undoStatus !== "applied") {
30585
30797
  button.hidden = true;
30586
30798
  return;
@@ -31578,19 +31790,26 @@ ${conclusion}` : `${label} ${conclusion}`;
31578
31790
  const source = findSource(previousState, runId);
31579
31791
  if (!source) return failFork("session_fork_source_missing", tr("forkRunUnavailable"));
31580
31792
  const { session, run, runIndex } = source;
31581
- if (run.status === "running" || !session.codexThreadId || !run.codexTurnId) {
31793
+ const draftThreadId = run.codexTurnId && run.interruptedDraft?.turnId === run.codexTurnId ? run.interruptedDraft.threadId : "";
31794
+ const sourceThreadId = String(run.codexThreadId || draftThreadId || session.codexThreadId || "").trim();
31795
+ if (run.status === "running" || !sourceThreadId || !run.codexTurnId) {
31582
31796
  return failFork("session_fork_unavailable", tr("forkRunUnavailable"));
31583
31797
  }
31584
- const response = await sendBackgroundNative({
31585
- method: "codex.thread.fork",
31586
- params: {
31587
- threadId: session.codexThreadId,
31588
- lastTurnId: run.codexTurnId,
31589
- speedTier: session.speedTier,
31590
- loadCodexLocalSkills: previousState.loadCodexLocalSkills !== false,
31591
- loadCodexOverleafSkills: previousState.loadCodexOverleafSkills !== false
31592
- }
31593
- });
31798
+ let response;
31799
+ try {
31800
+ response = await sendBackgroundNative({
31801
+ method: "codex.thread.fork",
31802
+ params: {
31803
+ threadId: sourceThreadId,
31804
+ lastTurnId: run.codexTurnId,
31805
+ speedTier: session.speedTier,
31806
+ loadCodexLocalSkills: previousState.loadCodexLocalSkills !== false,
31807
+ loadCodexOverleafSkills: previousState.loadCodexOverleafSkills !== false
31808
+ }
31809
+ });
31810
+ } catch (error) {
31811
+ return failFork(error?.code || "session_fork_failed", error?.message || tr("forkRunFailed"));
31812
+ }
31594
31813
  if (!response?.ok || !response.result?.threadId) {
31595
31814
  return failFork(response?.error?.code || "session_fork_failed", response?.error?.message || tr("forkRunFailed"));
31596
31815
  }
@@ -32027,11 +32246,45 @@ ${conclusion}` : `${label} ${conclusion}`;
32027
32246
  function formatCompactModelLabel(label) {
32028
32247
  return String(label || "").replace(/^gpt[-\s]*/i, "");
32029
32248
  }
32030
- function retainSelectedModel(models, selectedModel) {
32249
+ function retainSelectedModel(models, selectedModel, options2 = {}) {
32031
32250
  const selectedId = normalizeModelOptionId(selectedModel);
32032
32251
  if (!selectedId) return "";
32252
+ if (options2.preserveUnavailable === true) return selectedId;
32033
32253
  return (Array.isArray(models) ? models : []).some((model) => normalizeModelOptionId(typeof model === "string" ? model : model?.id) === selectedId) ? selectedId : "";
32034
32254
  }
32255
+ function preserveSelectedCapabilities(models, selectedModel, preferences = {}, provisional = false) {
32256
+ const selectedId = normalizeModelOptionId(selectedModel);
32257
+ const saved = preferences || {};
32258
+ return (Array.isArray(models) ? models : []).map((model) => {
32259
+ if (model.id !== selectedId || !provisional && !model.unverified) return model;
32260
+ const reasoningEfforts = normalizeReasoningEffortsForSelect([
32261
+ ...model.reasoningEfforts || [],
32262
+ saved.reasoningEffort
32263
+ ]);
32264
+ const speedTiers = normalizeSpeedTiersForSelect(model.speedTiers);
32265
+ const speed = ["standard", "fast"].includes(saved.speedTier) ? saved.speedTier : "";
32266
+ if (speed && !speedTiers.includes(speed)) speedTiers.push(speed);
32267
+ return {
32268
+ ...model,
32269
+ unverified: true,
32270
+ reasoningEfforts,
32271
+ speedTiers,
32272
+ defaultReasoningEffort: reasoningEfforts.includes(saved.reasoningEffort) ? saved.reasoningEffort : model.defaultReasoningEffort,
32273
+ defaultSpeedTier: speed || model.defaultSpeedTier || "standard"
32274
+ };
32275
+ });
32276
+ }
32277
+ function getRenderedModelEntries(panel) {
32278
+ return Array.from(panel?.querySelector("[data-model]")?.options || []).map((option) => ({
32279
+ id: option.value,
32280
+ label: option.textContent,
32281
+ reasoningEfforts: (option.dataset.reasoningEfforts || "").split(",").filter(Boolean),
32282
+ defaultReasoningEffort: option.dataset.defaultReasoningEffort || "",
32283
+ reasoningPresentation: option.dataset.reasoningPresentation || "",
32284
+ speedTiers: (option.dataset.speedTiers || "standard").split(",").filter(Boolean),
32285
+ defaultSpeedTier: option.dataset.defaultSpeedTier || "standard"
32286
+ }));
32287
+ }
32035
32288
  function shouldUseBuiltInFallback(response) {
32036
32289
  const providerId = normalizeModelOptionId(response?.result?.providerId);
32037
32290
  return response?.ok === true && (!providerId || providerId === "builtin");
@@ -32100,6 +32353,8 @@ ${conclusion}` : `${label} ${conclusion}`;
32100
32353
  normalizeReasoningEffortsForSelect,
32101
32354
  normalizeSpeedTiersForSelect,
32102
32355
  retainSelectedModel,
32356
+ preserveSelectedCapabilities,
32357
+ getRenderedModelEntries,
32103
32358
  shouldUseBuiltInFallback
32104
32359
  };
32105
32360
  return api;
@@ -32222,7 +32477,8 @@ ${conclusion}` : `${label} ${conclusion}`;
32222
32477
  return { stale: false, error: response?.error || { code: "provider_models_unavailable" } };
32223
32478
  }
32224
32479
  const sourceModels = hasDiscoveredModels ? response.result.models : fallbackModels;
32225
- const retainedSelectedModel = retainSelectedModel(sourceModels, currentSelectedModel);
32480
+ const preserveBuiltin = (selection?.providerId || "builtin") === "builtin" && (getState()?.providerId || "builtin") === "builtin";
32481
+ const retainedSelectedModel = retainSelectedModel(sourceModels, currentSelectedModel, { preserveUnavailable: preserveBuiltin });
32226
32482
  const normalized = modelCatalog.normalizeDiscoveredModels({ models: sourceModels, selectedModel: retainedSelectedModel });
32227
32483
  if (normalized.usedFallback && response.result?.providerId && response.result.providerId !== "builtin") {
32228
32484
  applyUnavailableModelOptions({ code: "provider_model_catalog_invalid", message: "The active provider returned no usable models." });
@@ -32231,9 +32487,10 @@ ${conclusion}` : `${label} ${conclusion}`;
32231
32487
  error: { code: "provider_model_catalog_invalid", message: "The active provider returned no usable models." }
32232
32488
  };
32233
32489
  }
32234
- renderModelOptions(normalized.models, retainedSelectedModel, { allowUnknownSelected: false });
32490
+ const renderedModels = preserveBuiltin ? Support.preserveSelectedCapabilities(normalized.models, retainedSelectedModel, getState(), !hasDiscoveredModels || response.result?.source === "fallback") : normalized.models;
32491
+ renderModelOptions(renderedModels, retainedSelectedModel, { allowUnknownSelected: false });
32235
32492
  modelDiscovery = {
32236
- status: hasDiscoveredModels && !normalized.usedFallback ? "discovered" : "fallback",
32493
+ status: hasDiscoveredModels && !normalized.usedFallback && response.result?.source !== "fallback" ? "discovered" : "fallback",
32237
32494
  source: hasDiscoveredModels ? response.result?.source || "unknown" : "fallback",
32238
32495
  fetchedAt: hasDiscoveredModels ? response.result?.fetchedAt || "" : "",
32239
32496
  errorCode: hasDiscoveredModels ? "" : response?.error?.code || "",
@@ -32259,15 +32516,16 @@ ${conclusion}` : `${label} ${conclusion}`;
32259
32516
  }
32260
32517
  }
32261
32518
  function applyUnavailableModelOptions(error) {
32519
+ const selectedModel = resolveSelectedModel();
32262
32520
  const modelSelect = getPanel()?.querySelector("[data-model]");
32263
32521
  if (modelSelect) {
32264
32522
  modelSelect.textContent = "";
32265
32523
  const option = document.createElement("option");
32266
- option.value = resolveSelectedModel();
32524
+ option.value = selectedModel;
32267
32525
  option.textContent = tx("Models unavailable", "\u6A21\u578B\u4E0D\u53EF\u7528");
32268
32526
  option.disabled = true;
32269
32527
  modelSelect.append(option);
32270
- modelSelect.value = "";
32528
+ modelSelect.value = selectedModel;
32271
32529
  modelSelect.disabled = true;
32272
32530
  }
32273
32531
  modelDiscovery = {
@@ -32277,8 +32535,18 @@ ${conclusion}` : `${label} ${conclusion}`;
32277
32535
  errorCode: error?.code || "",
32278
32536
  errorMessage: error?.message || (error ? String(error) : "")
32279
32537
  };
32280
- renderReasoningOptions([]);
32281
- renderSpeedOptions([]);
32538
+ const pendingModels = Support.preserveSelectedCapabilities(
32539
+ [{ id: selectedModel, unverified: true }],
32540
+ selectedModel,
32541
+ getState(),
32542
+ true
32543
+ );
32544
+ renderReasoningOptions(pendingModels);
32545
+ renderSpeedOptions(pendingModels);
32546
+ for (const selector of ["[data-reasoning]", "[data-speed]"]) {
32547
+ const input = getPanel()?.querySelector(selector);
32548
+ if (input) input.disabled = true;
32549
+ }
32282
32550
  renderModelConfigChoices();
32283
32551
  updateModelDisplay();
32284
32552
  }
@@ -32286,9 +32554,9 @@ ${conclusion}` : `${label} ${conclusion}`;
32286
32554
  const modelCatalog = getModelCatalog();
32287
32555
  const fallbackModels = modelCatalog.FALLBACK_MODELS;
32288
32556
  const sourceModels = fallbackModels;
32289
- const retainedSelectedModel = retainSelectedModel(sourceModels, selectedModel);
32557
+ const retainedSelectedModel = retainSelectedModel(sourceModels, selectedModel, { preserveUnavailable: (getState()?.providerId || "builtin") === "builtin" });
32290
32558
  const normalized = modelCatalog.normalizeDiscoveredModels({ models: sourceModels, selectedModel: retainedSelectedModel });
32291
- renderModelOptions(normalized.models, retainedSelectedModel);
32559
+ renderModelOptions(Support.preserveSelectedCapabilities(normalized.models, retainedSelectedModel, getState(), true), retainedSelectedModel);
32292
32560
  modelDiscovery = {
32293
32561
  status: "fallback",
32294
32562
  source: "fallback",
@@ -33653,6 +33921,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33653
33921
  let currentOtStatus = "off";
33654
33922
  let otTogglePending = false;
33655
33923
  let otSyncRequestId = 0;
33924
+ let otStartRetryCount = 0;
33656
33925
  let otWarmMirrorProjectId = "";
33657
33926
  let lastExperimentalOtProjectId = "";
33658
33927
  let otWarmMirrorState = {
@@ -33802,6 +34071,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33802
34071
  const projectId = getCurrentProjectId();
33803
34072
  otWarmMirrorState.failureActive = false;
33804
34073
  lastExperimentalOtProjectId = projectId;
34074
+ otStartRetryCount = 0;
33805
34075
  setExperimentalOtEnabledForProject(projectId, checkbox.checked);
33806
34076
  updateExperimentalOtToggleControl(checkbox.checked);
33807
34077
  const savedBadge = getPanel()?.querySelector('details[data-set-group="experimental"] [data-set-saved]');
@@ -33877,8 +34147,10 @@ ${conclusion}` : `${label} ${conclusion}`;
33877
34147
  await pauseOtWarmMirror(pauseAfterStart.reason || "paused");
33878
34148
  return response;
33879
34149
  }
33880
- otWarmMirrorState.failureActive = false;
34150
+ if (status === "starting") recordOtFailure("missing_active_path", "start");
34151
+ else otWarmMirrorState.failureActive = false;
33881
34152
  updateOtStatusDisplay(status);
34153
+ otStartRetryCount = 0;
33882
34154
  scheduleOtEventPolling(projectId, { immediate: true });
33883
34155
  return response;
33884
34156
  }
@@ -33905,23 +34177,31 @@ ${conclusion}` : `${label} ${conclusion}`;
33905
34177
  clearOtEventPolling({ clearPatchQueue: true });
33906
34178
  recordOtFailure(readOtBridgeErrorCode(response, "ot_start_failed"), "start");
33907
34179
  otWarmMirrorState.suspended = true;
33908
- setExperimentalOtEnabledForProject(projectId, false);
33909
- const experimentalOtCheckbox = getPanel()?.querySelector("[data-experimental-ot]");
33910
- if (experimentalOtCheckbox) {
33911
- experimentalOtCheckbox.checked = false;
33912
- }
34180
+ updateExperimentalOtToggleControl(isExperimentalOtEnabledForProject(projectId));
33913
34181
  updateOtStatusDisplay("unavailable");
33914
- saveStateSoon();
33915
34182
  try {
33916
34183
  await callPageBridge("stopOtObserver", { projectId });
33917
34184
  } catch (_error) {
33918
34185
  }
34186
+ scheduleOtStartRetry(projectId, requestId);
34187
+ }
34188
+ function scheduleOtStartRetry(projectId, requestId) {
34189
+ const retryDelays = [1e3, 3e3, 6e3];
34190
+ const canRetry = () => isCurrentOtSync(requestId, projectId) && getCurrentProjectId() === projectId && isExperimentalOtEnabledForProject(projectId) && !getCurrentRunView();
34191
+ if (!canRetry() || otStartRetryCount >= retryDelays.length) return;
34192
+ const delayMs = retryDelays[otStartRetryCount++];
34193
+ otWarmMirrorState.pollTimer = window.setTimeout(() => {
34194
+ otWarmMirrorState.pollTimer = null;
34195
+ if (!canRetry()) return;
34196
+ syncOtWarmMirrorController().catch(() => {
34197
+ if (getCurrentProjectId() === projectId && isExperimentalOtEnabledForProject(projectId)) {
34198
+ updateOtStatusDisplay("unavailable");
34199
+ }
34200
+ });
34201
+ }, delayMs);
33919
34202
  }
33920
34203
  function readOtBridgeStatus(response) {
33921
- if (!response || response.ok === false) {
33922
- return "unavailable";
33923
- }
33924
- return response.state || response.status || response.result?.state || response.result?.status || "unavailable";
34204
+ return otWarmMirrorController.readOtBridgeStatus(response);
33925
34205
  }
33926
34206
  function ensureOtWarmMirrorStateProject(projectId) {
33927
34207
  if (!projectId || otWarmMirrorState.projectId === projectId) {
@@ -33929,6 +34209,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33929
34209
  }
33930
34210
  clearOtEventPolling({ clearPatchQueue: true });
33931
34211
  otWarmMirrorState.projectId = projectId;
34212
+ otStartRetryCount = 0;
33932
34213
  otWarmMirrorState.lastPatchAt = 0;
33933
34214
  otWarmMirrorState.lastErrorCode = "";
33934
34215
  otWarmMirrorState.failureActive = false;
@@ -33992,6 +34273,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33992
34273
  return statusResponse;
33993
34274
  }
33994
34275
  const status = readOtBridgeStatus(statusResponse);
34276
+ if (status === "observing" && otWarmMirrorState.lastFailure?.phase === "start") Object.assign(otWarmMirrorState, { failureActive: false, lastErrorCode: "", lastFailure: null });
33995
34277
  if (["unavailable", "inconsistent"].includes(status)) recordOtFailure(readOtBridgeErrorCode(statusResponse, "ot_observer_unavailable"), "observe");
33996
34278
  if (status) {
33997
34279
  updateOtStatusDisplay(status);
@@ -34022,20 +34304,10 @@ ${conclusion}` : `${label} ${conclusion}`;
34022
34304
  }
34023
34305
  }
34024
34306
  function readOtBridgeEvents(response) {
34025
- if (Array.isArray(response)) {
34026
- return response;
34027
- }
34028
- if (Array.isArray(response?.events)) {
34029
- return response.events;
34030
- }
34031
- if (Array.isArray(response?.result?.events)) {
34032
- return response.result.events;
34033
- }
34034
- return [];
34307
+ return otWarmMirrorController.readOtBridgeEvents(response);
34035
34308
  }
34036
34309
  function readOtBridgeErrorCode(response, fallback) {
34037
- const value = response?.lastErrorCode || response?.reason || response?.error?.code || response?.error || fallback;
34038
- return typeof value === "string" ? value : fallback;
34310
+ return otWarmMirrorController.readOtBridgeErrorCode(response, fallback);
34039
34311
  }
34040
34312
  function recordOtFailure(code, phase) {
34041
34313
  code = otWarmMirrorController.normalizeFailureCode(code);
@@ -34511,6 +34783,7 @@ ${conclusion}` : `${label} ${conclusion}`;
34511
34783
  let tx;
34512
34784
  let getLocale;
34513
34785
  let appendRunEvent;
34786
+ let appendRunRecordEvent;
34514
34787
  let pendingMirrorRefresh = null;
34515
34788
  let appendChangeSummary;
34516
34789
  let appendCompletionReport;
@@ -34557,6 +34830,34 @@ ${conclusion}` : `${label} ${conclusion}`;
34557
34830
  function buildSettlement(input = {}) {
34558
34831
  return writebackSettlement?.settle instanceof Function ? writebackSettlement.settle(input) : null;
34559
34832
  }
34833
+ function invalidateMirrorAfterUndo(runId, projectId, result = {}) {
34834
+ if (!projectId || result?.changedDocument !== true && !result?.applied?.length) return Promise.resolve("not-attempted");
34835
+ const pending = Promise.resolve(pendingMirrorRefresh).catch(() => {
34836
+ }).then(async () => {
34837
+ const response = await sendBackgroundNative({ method: "mirror.invalidate", params: { projectId } });
34838
+ if (!response?.ok || response.result?.invalidated !== true) {
34839
+ throw new Error(response?.error?.message || "Native host did not confirm mirror invalidation.");
34840
+ }
34841
+ if (getCurrentProjectId() === projectId) {
34842
+ resetContextProject();
34843
+ await callPageBridge("invalidateProjectSnapshot", { invalidateFileList: true });
34844
+ }
34845
+ return "invalidated";
34846
+ }).catch((error) => {
34847
+ appendRunRecordEvent?.(runId, {
34848
+ status: "warning",
34849
+ title: tx(
34850
+ `Overleaf undo finished, but local mirror invalidation failed: ${error.message}. Refresh this project or reconnect the native host before running again.`,
34851
+ `Overleaf \u64A4\u9500\u5DF2\u5B8C\u6210\uFF0C\u4F46\u672C\u5730\u955C\u50CF\u5931\u6548\u5904\u7406\u5931\u8D25\uFF1A${error.message}\u3002\u8BF7\u5237\u65B0\u9879\u76EE\u6216\u91CD\u65B0\u8FDE\u63A5 Native Host \u540E\u518D\u8FD0\u884C\u3002`
34852
+ )
34853
+ });
34854
+ return "failed";
34855
+ }).finally(() => {
34856
+ if (pendingMirrorRefresh === pending) pendingMirrorRefresh = null;
34857
+ });
34858
+ pendingMirrorRefresh = pending;
34859
+ return pending;
34860
+ }
34560
34861
  async function applySyncChangesToOverleaf(syncChanges = [], project = {}, options2 = {}) {
34561
34862
  const runMode = options2.mode || getState().mode;
34562
34863
  const runRequireReviewing = typeof options2.requireReviewing === "boolean" ? options2.requireReviewing : getState().requireReviewing === true;
@@ -35209,6 +35510,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35209
35510
  tx,
35210
35511
  getLocale,
35211
35512
  appendRunEvent,
35513
+ appendRunRecordEvent,
35212
35514
  appendChangeSummary,
35213
35515
  appendCompletionReport,
35214
35516
  appendOperationsPreview,
@@ -35254,6 +35556,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35254
35556
  return {
35255
35557
  applySyncChangesToOverleaf,
35256
35558
  resolveCompileLogContext,
35559
+ invalidateMirrorAfterUndo,
35257
35560
  getPendingMirrorRefresh: () => pendingMirrorRefresh
35258
35561
  };
35259
35562
  }
@@ -35261,6 +35564,153 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35261
35564
  })();
35262
35565
  }).call(globalThis, void 0, void 0, void 0);
35263
35566
  (function runClassicContentModule94(module, exports, require2) {
35567
+ (function(root, factory) {
35568
+ const api = factory();
35569
+ if (typeof module === "object" && module.exports) module.exports = api;
35570
+ if (root) root.CodexOverleafTrackedChangeCaptureController = api;
35571
+ })(typeof window !== "undefined" ? window : globalThis, function() {
35572
+ "use strict";
35573
+ function create(deps = {}) {
35574
+ const now = deps.now || (() => Date.now());
35575
+ const defer = deps.setTimeout || setTimeout;
35576
+ const cancelTimer = deps.clearTimeout || clearTimeout;
35577
+ let timer = null, busy = false, disposed = false;
35578
+ const runs = () => deps.getRuns() || [];
35579
+ const pending = (run) => Array.isArray(run.trackedChangeCaptures) && run.trackedChangeCaptures.some((capture) => capture.state === "pending");
35580
+ const terminal = (run) => ["accepted", "rejected"].includes(run.trackedChangeStatus) || run.undoStatus === "applied" || run.forkSnapshot === true;
35581
+ function buildPostFiles(run) {
35582
+ const expectedFiles = Array.isArray(run?.undoExpectedFiles) ? run.undoExpectedFiles : [];
35583
+ const appliedOperations = Array.isArray(run?.appliedOperations) ? run.appliedOperations : [];
35584
+ if (!expectedFiles.length || !appliedOperations.length) return [];
35585
+ const postFilesByPath = deps.buildExpectedFilesAfterOperations({ files: expectedFiles }, appliedOperations);
35586
+ const expectedPaths = new Set(expectedFiles.map((file) => file?.path).filter(Boolean));
35587
+ return Array.from(postFilesByPath.entries()).filter(([path, content]) => expectedPaths.has(path) && typeof content === "string").map(([path, content]) => ({ path, content }));
35588
+ }
35589
+ function hasLegacyUntrackedCheckpoint(run) {
35590
+ if (!run || run.executionSnapshot || run.trackedChangeStatus || run.undoStatus === "applied" || run.forkSnapshot || run.undoTrackedChanges?.length || run.trackedChangeCaptures?.length) return false;
35591
+ const operations = Array.isArray(run.undoOperations) ? run.undoOperations : [];
35592
+ const before = Array.isArray(run.undoExpectedFiles) ? run.undoExpectedFiles : [];
35593
+ if (!operations.length || !before.length || before.some((file) => !file?.path || typeof file.content !== "string")) return false;
35594
+ const paths = new Set(before.map((file) => file.path));
35595
+ if (paths.size !== before.length || operations.some((op) => op?.type !== "edit" || !paths.has(op.path))) return false;
35596
+ const complete = (files) => Array.isArray(files) && files.length === paths.size && new Set(files.map((file) => file?.path)).size === paths.size && files.every((file) => paths.has(file?.path) && typeof file.content === "string");
35597
+ const storedPost = run.undoBaseFiles;
35598
+ try {
35599
+ const post = buildPostFiles(run);
35600
+ if (!complete(storedPost) || !complete(post)) return false;
35601
+ const postByPath = new Map(post.map((file) => [file.path, file.content]));
35602
+ if (!storedPost.every((file) => postByPath.get(file.path) === file.content)) return false;
35603
+ const restored = deps.buildExpectedFilesAfterOperations({ files: storedPost }, operations);
35604
+ return before.every((file) => restored.get(file.path) === file.content);
35605
+ } catch (_error) {
35606
+ return false;
35607
+ }
35608
+ }
35609
+ function schedule() {
35610
+ if (disposed || timer !== null || busy || !runs().some(pending)) return;
35611
+ timer = defer(() => {
35612
+ timer = null;
35613
+ flush().catch((error) => deps.onError?.(error));
35614
+ }, 500);
35615
+ }
35616
+ function record(run, captures) {
35617
+ if (!Array.isArray(captures) || !captures.length) return;
35618
+ const next = [...run.trackedChangeCaptures || [], ...captures.filter(Boolean)];
35619
+ if (next.every((capture) => capture.state === "observed")) return;
35620
+ run.trackedChangeCaptures = next;
35621
+ deps.notify?.(run, next.some((capture) => capture.state === "pending") ? "pending" : "needs_review", {
35622
+ captures: next.map((capture) => ({ path: capture.path, reason: capture.reason, ...capture.diagnostics }))
35623
+ });
35624
+ deps.persist().then(schedule).catch((error) => deps.onError?.(error));
35625
+ }
35626
+ async function flush() {
35627
+ if (disposed || busy) return;
35628
+ busy = true;
35629
+ try {
35630
+ for (const run of runs().filter(pending)) {
35631
+ if (terminal(run)) {
35632
+ run.trackedChangeCaptures = [];
35633
+ await deps.persist();
35634
+ continue;
35635
+ }
35636
+ if (run.runProjectId !== deps.getProjectId()) continue;
35637
+ const newer = runs().some((other) => other.id !== run.id && other.runProjectId === run.runProjectId && Date.parse(other.startedAt) > Date.parse(run.startedAt));
35638
+ const original = run.trackedChangeCaptures;
35639
+ const captures = original.map((capture) => ({ ...capture }));
35640
+ const postFiles = buildPostFiles(run);
35641
+ for (let i = 0; i < captures.length; i++) {
35642
+ const capture = captures[i];
35643
+ if (capture.state !== "pending") continue;
35644
+ const expected = postFiles.find((file) => file.path === capture.path);
35645
+ if (newer || !expected || now() >= capture.expiresAt || run.status === "cancelled") {
35646
+ capture.state = "needs_review";
35647
+ capture.reason = newer ? "capture_superseded" : !expected ? "capture_baseline_missing" : run.status === "cancelled" ? "capture_cancelled" : "capture_expired";
35648
+ continue;
35649
+ }
35650
+ let response;
35651
+ try {
35652
+ response = await deps.callPageBridge("reconcileTrackedChangeCapture", {
35653
+ runProjectId: run.runProjectId,
35654
+ capture,
35655
+ expectedContent: expected.content
35656
+ });
35657
+ } catch (_error) {
35658
+ response = null;
35659
+ }
35660
+ if (disposed || deps.getProjectId() !== run.runProjectId || !runs().includes(run) || terminal(run) || run.trackedChangeCaptures !== original) return;
35661
+ const superseded = runs().some((other) => other.id !== run.id && other.runProjectId === run.runProjectId && Date.parse(other.startedAt) > Date.parse(run.startedAt));
35662
+ if (superseded || run.status === "cancelled") {
35663
+ captures[i] = { ...capture, state: "needs_review", reason: "capture_superseded" };
35664
+ } else if (response?.capture?.path === capture.path && response.capture.runProjectId === run.runProjectId && ["observed", "pending", "needs_review"].includes(response.capture.state)) {
35665
+ captures[i] = response.capture;
35666
+ } else {
35667
+ captures[i] = { ...capture, state: "needs_review", reason: "capture_bridge_unavailable" };
35668
+ }
35669
+ }
35670
+ if (!runs().includes(run) || terminal(run) || deps.getProjectId() !== run.runProjectId) continue;
35671
+ if (captures.length && captures.every((capture) => capture.state === "observed")) {
35672
+ const refs = deps.normalizeTrackedChanges([
35673
+ ...run.undoTrackedChanges || [],
35674
+ ...captures.flatMap((capture) => capture.refs || [])
35675
+ ]);
35676
+ if (refs.length) {
35677
+ run.undoTrackedChanges = refs;
35678
+ run.trackedChangeStatus = "pending";
35679
+ run.trackedChangeCaptures = [];
35680
+ await deps.persist();
35681
+ deps.refresh(run);
35682
+ deps.notify?.(run, "observed", { count: refs.length });
35683
+ continue;
35684
+ }
35685
+ }
35686
+ const changed = JSON.stringify(original.map(summary)) !== JSON.stringify(captures.map(summary));
35687
+ if (changed) {
35688
+ run.trackedChangeCaptures = captures;
35689
+ await deps.persist();
35690
+ if (!pending(run)) deps.notify?.(run, "needs_review", {
35691
+ reasons: captures.filter((capture) => capture.state !== "observed").map((capture) => capture.reason)
35692
+ });
35693
+ }
35694
+ }
35695
+ } finally {
35696
+ busy = false;
35697
+ if (runs().some((run) => pending(run) && run.runProjectId === deps.getProjectId())) schedule();
35698
+ }
35699
+ }
35700
+ function summary(capture) {
35701
+ return { state: capture.state, reason: capture.reason, refs: (capture.refs || []).map((ref) => ref.key) };
35702
+ }
35703
+ function dispose() {
35704
+ disposed = true;
35705
+ if (timer !== null) cancelTimer(timer);
35706
+ timer = null;
35707
+ }
35708
+ return { record, schedule, flush, dispose, buildPostFiles, hasLegacyUntrackedCheckpoint };
35709
+ }
35710
+ return { create };
35711
+ });
35712
+ }).call(globalThis, void 0, void 0, void 0);
35713
+ (function runClassicContentModule95(module, exports, require2) {
35264
35714
  (function initCodexOverleafRunSettlementPersistence(root, factory) {
35265
35715
  const api = factory();
35266
35716
  if (typeof module === "object" && module.exports) module.exports = api;
@@ -35303,7 +35753,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35303
35753
  return Object.freeze({ create });
35304
35754
  });
35305
35755
  }).call(globalThis, void 0, void 0, void 0);
35306
- (function runClassicContentModule95(module, exports, require2) {
35756
+ (function runClassicContentModule96(module, exports, require2) {
35307
35757
  (function initPostNavigationSettlementPersistence(root, factory) {
35308
35758
  const api = factory();
35309
35759
  if (typeof module === "object" && module.exports) module.exports = api;
@@ -35395,7 +35845,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35395
35845
  return Object.freeze({ persist, persistRequired });
35396
35846
  });
35397
35847
  }).call(globalThis, void 0, void 0, void 0);
35398
- (function runClassicContentModule96(module, exports, require2) {
35848
+ (function runClassicContentModule97(module, exports, require2) {
35399
35849
  (function initCodexOverleafPanelMaintenance() {
35400
35850
  "use strict";
35401
35851
  function create(deps = {}) {
@@ -35726,7 +36176,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35726
36176
  window.CodexOverleafPanelMaintenance = { create };
35727
36177
  })();
35728
36178
  }).call(globalThis, void 0, void 0, void 0);
35729
- (function runClassicContentModule97(module, exports, require2) {
36179
+ (function runClassicContentModule98(module, exports, require2) {
35730
36180
  (function initCodexOverleafUpdateIdle(root) {
35731
36181
  "use strict";
35732
36182
  function create(options2 = {}) {
@@ -35794,7 +36244,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35794
36244
  root.CodexOverleafUpdateIdle = Object.freeze({ create });
35795
36245
  })(typeof window !== "undefined" ? window : globalThis);
35796
36246
  }).call(globalThis, void 0, void 0, void 0);
35797
- (function runClassicContentModule98(module, exports, require2) {
36247
+ (function runClassicContentModule99(module, exports, require2) {
35798
36248
  (function initCodexOverleafUpdateNotice(root, factory) {
35799
36249
  if (typeof module === "object" && module.exports) {
35800
36250
  module.exports = factory(require2("../shared/managedUpdateProjection"));
@@ -36259,7 +36709,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
36259
36709
  return Object.freeze({ mount });
36260
36710
  });
36261
36711
  }).call(globalThis, void 0, void 0, void 0);
36262
- (function runClassicContentModule99(module, exports, require2) {
36712
+ (function runClassicContentModule100(module, exports, require2) {
36263
36713
  (function initCodexOverleafContentRuntime(root) {
36264
36714
  "use strict";
36265
36715
  function init(dependencies2) {
@@ -36312,6 +36762,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
36312
36762
  "mirror.sync",
36313
36763
  "mirror.patchFiles",
36314
36764
  "mirror.confirmWriteback",
36765
+ "mirror.invalidate",
36315
36766
  "mirror.scanSensitive",
36316
36767
  "codex.history.clearPlugin",
36317
36768
  "skills.list",
@@ -36812,6 +37263,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
36812
37263
  RUN_SNAPSHOT_ZIP_TIMEOUT_MS,
36813
37264
  getState: () => state,
36814
37265
  getCurrentRunView: () => currentRunView,
37266
+ appendRunRecordEvent,
36815
37267
  onMirrorRefreshSettled: runSettlementPersistence.settleMirrorRefresh
36816
37268
  });
36817
37269
  const {
@@ -37013,6 +37465,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
37013
37465
  },
37014
37466
  saveState,
37015
37467
  saveStateSoon,
37468
+ onProjectFilesReady: refreshHistoricalProjectReferences,
37016
37469
  updateActiveSession,
37017
37470
  callPageBridge,
37018
37471
  getCurrentProjectId,
@@ -38483,15 +38936,19 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
38483
38936
  setRunning(false);
38484
38937
  nativeChannel.clearActiveRequest();
38485
38938
  stopRunElapsedTick();
38486
- if (isExperimentalOtEnabled()) {
38487
- await resumeOtWarmMirror("run-settled");
38488
- }
38489
38939
  await flushQueuedSaveState().catch(() => {
38490
38940
  });
38491
38941
  currentRunView = null;
38492
38942
  runCancellationRequested = false;
38493
38943
  runCancellationController = null;
38494
38944
  activeTurnControl.release(settlement.requestId);
38945
+ if (isExperimentalOtEnabled()) {
38946
+ try {
38947
+ await resumeOtWarmMirror("run-settled");
38948
+ } catch (_error) {
38949
+ updateOtStatusDisplay("unavailable");
38950
+ }
38951
+ }
38495
38952
  if (settlement.requestId) {
38496
38953
  activeTurnControl.acknowledge(settlement.requestId).catch(() => {
38497
38954
  });
@@ -41060,6 +41517,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41060
41517
  title: session.title || "",
41061
41518
  titleSource: session.titleSource || "auto",
41062
41519
  focusFiles: session.focusFiles || [],
41520
+ projectReferenceFiles: session.projectReferenceFiles || [],
41063
41521
  codexThreadId: session.codexThreadId || "",
41064
41522
  createdAt: session.createdAt,
41065
41523
  updatedAt: session.updatedAt,
@@ -41371,15 +41829,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41371
41829
  renderSessionList();
41372
41830
  }
41373
41831
  function getRenderedModelEntries() {
41374
- return Array.from(panel?.querySelector("[data-model]")?.options || []).map((option) => ({
41375
- id: option.value,
41376
- label: option.textContent,
41377
- reasoningEfforts: (option.dataset.reasoningEfforts || "").split(",").filter(Boolean),
41378
- defaultReasoningEffort: option.dataset.defaultReasoningEffort || "",
41379
- reasoningPresentation: option.dataset.reasoningPresentation || "",
41380
- speedTiers: (option.dataset.speedTiers || "standard").split(",").filter(Boolean),
41381
- defaultSpeedTier: option.dataset.defaultSpeedTier || "standard"
41382
- }));
41832
+ return Modules.ModelPickerSupport.getRenderedModelEntries(panel);
41383
41833
  }
41384
41834
  async function selectMode(mode) {
41385
41835
  if (!["ask", "auto"].includes(mode)) {
@@ -41500,6 +41950,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41500
41950
  applySessionLabel();
41501
41951
  renderSessionList();
41502
41952
  renderRunHistory(options2);
41953
+ getTrackedChangeCaptureController().schedule();
41503
41954
  renderContextSelection();
41504
41955
  renderContextSummary();
41505
41956
  if (!composerAttachmentsRestoredOnce && Array.isArray(state.composerAttachments) && state.composerAttachments.length && !composerAttachmentController.getAttachmentsForRun?.().length && typeof composerAttachmentController.restorePersistedAttachments === "function") {
@@ -41578,6 +42029,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41578
42029
  undoStatus: "",
41579
42030
  queueItemId,
41580
42031
  nativeRequestId: "",
42032
+ codexThreadId: "",
41581
42033
  codexTurnId: "",
41582
42034
  nativeEventSeq: 0,
41583
42035
  executionSnapshot: executionSnapshot ? RunExecutionSnapshot.cloneSnapshot(executionSnapshot) : void 0,
@@ -41716,6 +42168,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41716
42168
  currentRunView.activeTurn = { threadId, turnId };
41717
42169
  const record = findRunRecord(currentRunView.recordId, currentRunView.sessionId);
41718
42170
  if (record) {
42171
+ record.codexThreadId = threadId;
41719
42172
  record.codexTurnId = turnId;
41720
42173
  record.nativeEventSeq = Math.max(Number(record.nativeEventSeq || 0), Number(journalSeq || 0));
41721
42174
  }
@@ -42029,6 +42482,12 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42029
42482
  }
42030
42483
  return files;
42031
42484
  }
42485
+ function refreshHistoricalProjectReferences(project) {
42486
+ if (!project?.id || String(project.id) !== getCurrentProjectId()) return;
42487
+ state = updateActiveSession(state, { projectReferenceFiles: captureProjectReferenceFiles(project) });
42488
+ saveStateSoon();
42489
+ if (!currentRunView && !trackedChangeInFlight.size) renderRunHistory({ preserveScroll: true });
42490
+ }
42032
42491
  function persistCurrentProjectReferenceFiles(project) {
42033
42492
  const projectFiles = captureProjectReferenceFiles(project);
42034
42493
  if (currentRunView) {
@@ -42143,6 +42602,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42143
42602
  runProjectId: getRunProjectIdForWriteback(run)
42144
42603
  });
42145
42604
  const undoApplied = isUndoResultEffectivelyApplied(run, result);
42605
+ writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
42146
42606
  appendUndoReviewingPolicyEvent(runId, result.reviewingPolicy);
42147
42607
  appendRunRecordEvent(runId, {
42148
42608
  title: tr("undoResult", { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
@@ -42195,11 +42655,14 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42195
42655
  detail: { [tr("detailWillUndo")]: trackedUndo ? formatTrackedChangeFiles(run.undoTrackedChanges) : formatTrackedUndoFiles(run) }
42196
42656
  });
42197
42657
  let result;
42658
+ let trackedUndoPostFilesAtDispatch;
42198
42659
  try {
42660
+ trackedUndoPostFilesAtDispatch = buildTrackedUndoPostFiles(run);
42199
42661
  result = await callPageBridge("rejectTrackedChanges", {
42200
42662
  trackedChanges: run.undoTrackedChanges || [],
42201
42663
  expectedFiles: run.undoExpectedFiles || [],
42202
- postFiles: buildTrackedUndoPostFiles(run),
42664
+ postFiles: trackedUndoPostFilesAtDispatch,
42665
+ untrackedUndo: run.executionSnapshot ? run.executionSnapshot.requireReviewing === false && ["submitted", "legacy-captured"].includes(run.executionSnapshot.source) : getTrackedChangeCaptureController().hasLegacyUntrackedCheckpoint(run),
42203
42666
  // Welcome-panel + write-guard:
42204
42667
  // bind the reject to the run's original project. If the user has
42205
42668
  // navigated away the page-side guard refuses with
@@ -42211,6 +42674,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42211
42674
  trackedChangeInFlight.delete(runId);
42212
42675
  }
42213
42676
  }
42677
+ writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
42214
42678
  appendRunRecordEvent(runId, {
42215
42679
  title: trackedUndo ? tr("undoTrackedResult", { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }) : tr("undoNativeResult", { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
42216
42680
  status: result.skipped?.length ? "failed" : "completed",
@@ -42228,7 +42692,8 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42228
42692
  });
42229
42693
  if (lifecycleReject) {
42230
42694
  WritebackSettlement.attachUndoNotVerifiedFailure(run, result, {
42231
- buildFailure: buildContentFailure
42695
+ buildFailure: buildContentFailure,
42696
+ postFiles: trackedUndoPostFilesAtDispatch
42232
42697
  });
42233
42698
  applyTrackedChangeSettlement(runId, "reject", result);
42234
42699
  return;
@@ -42408,6 +42873,28 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42408
42873
  function attachVerifiedContentToOperation(operation, result) {
42409
42874
  return WritebackSettlement.attachVerifiedContentToOperation(operation, result);
42410
42875
  }
42876
+ let trackedChangeCaptureController = null;
42877
+ function getTrackedChangeCaptureController() {
42878
+ return trackedChangeCaptureController || (trackedChangeCaptureController = Modules.TrackedChangeCaptureController.create({
42879
+ getRuns: () => (state.sessions || []).flatMap((session) => session.runs || []),
42880
+ getProjectId: () => window.location.pathname.match(/^\/project\/([^/]+)/)?.[1] || "",
42881
+ buildExpectedFilesAfterOperations,
42882
+ normalizeTrackedChanges: normalizeApplyTrackedChanges,
42883
+ callPageBridge,
42884
+ persist: () => flushQueuedSaveState({ preserveRunActionPayload: true }),
42885
+ refresh: (run) => refreshRunCardControls(run.id),
42886
+ notify: (run, kind, detail) => appendRunRecordEvent(run.id, {
42887
+ title: {
42888
+ pending: tx("Files written; confirming tracked changes.", "\u6587\u4EF6\u5DF2\u5199\u5165\uFF0C\u6B63\u5728\u786E\u8BA4\u7559\u75D5\u8BB0\u5F55\u3002"),
42889
+ observed: tx("Tracked changes confirmed; Accept is available.", "\u7559\u75D5\u8BB0\u5F55\u5DF2\u786E\u8BA4\uFF0C\u53EF\u4EE5\u63A5\u53D7\u6539\u52A8\u3002"),
42890
+ needs_review: tx("Tracked changes could not be safely attributed. Review them in Overleaf; Undo is preserved.", "\u7559\u75D5\u5F52\u5C5E\u5C1A\u672A\u786E\u8BA4\uFF0C\u8BF7\u5728 Overleaf \u5BA1\u9605\uFF1B\u539F\u6709\u64A4\u9500\u80FD\u529B\u5DF2\u4FDD\u7559\u3002")
42891
+ }[kind],
42892
+ status: "info",
42893
+ detail
42894
+ }),
42895
+ onError: (error) => appendPlainLog(tx("Tracked-change confirmation unavailable: ", "\u7559\u75D5\u786E\u8BA4\u6682\u4E0D\u53EF\u7528\uFF1A") + error.message)
42896
+ }));
42897
+ }
42411
42898
  function recordUndoFromApply(project, applyResult) {
42412
42899
  const appliedEntries = getAppliedEntries(applyResult);
42413
42900
  if (!currentRunView?.recordId || !appliedEntries.length) {
@@ -42444,6 +42931,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42444
42931
  if (combinedTrackedChanges.length) {
42445
42932
  record.trackedChangeStatus = "pending";
42446
42933
  }
42934
+ getTrackedChangeCaptureController().record(record, applyResult?.trackedChangeCaptures);
42447
42935
  refreshRunCardControls(record.id);
42448
42936
  if (combinedTrackedChanges.length) {
42449
42937
  appendRunEvent({
@@ -42505,28 +42993,10 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42505
42993
  return WritebackSettlement.normalizeApplyTrackedChanges(changes);
42506
42994
  }
42507
42995
  function selectExpectedFilesForTrackedUndo(project, operations = [], trackedChanges = [], previousExpectedFiles = []) {
42508
- return WritebackSettlement.selectExpectedFilesForTrackedUndo(
42509
- project,
42510
- operations,
42511
- trackedChanges,
42512
- previousExpectedFiles
42513
- );
42996
+ return WritebackSettlement.selectExpectedFilesForTrackedUndo(project, operations, trackedChanges, previousExpectedFiles);
42514
42997
  }
42515
42998
  function buildTrackedUndoPostFiles(run) {
42516
- const expectedFiles = Array.isArray(run?.undoExpectedFiles) ? run.undoExpectedFiles : [];
42517
- const appliedOperations = Array.isArray(run?.appliedOperations) ? run.appliedOperations : [];
42518
- if (!expectedFiles.length || !appliedOperations.length) {
42519
- return [];
42520
- }
42521
- const postFilesByPath = buildExpectedFilesAfterOperations(
42522
- { files: expectedFiles },
42523
- appliedOperations
42524
- );
42525
- const expectedPaths = new Set(expectedFiles.map((file) => file?.path).filter(Boolean));
42526
- return Array.from(postFilesByPath.entries()).filter(([path, content]) => expectedPaths.has(path) && typeof content === "string").map(([path, content]) => ({
42527
- path,
42528
- content
42529
- }));
42999
+ return getTrackedChangeCaptureController().buildPostFiles(run);
42530
43000
  }
42531
43001
  function hasTrackedEditorUndo(run) {
42532
43002
  return buildTrackedUndoPostFiles(run).length > 0;
@@ -43103,7 +43573,7 @@ Reason: ${item.reason || "No reason provided"}`),
43103
43573
  root.CodexOverleafContentRuntime = { init };
43104
43574
  })(typeof window !== "undefined" ? window : globalThis);
43105
43575
  }).call(globalThis, void 0, void 0, void 0);
43106
- (function runClassicContentModule100(module, exports, require2) {
43576
+ (function runClassicContentModule101(module, exports, require2) {
43107
43577
  (function initCodexOverleafLegacyGlobalRegistry(root, factory) {
43108
43578
  "use strict";
43109
43579
  const api = factory();
@@ -43179,6 +43649,7 @@ Reason: ${item.reason || "No reason provided"}`),
43179
43649
  "StorageMigration",
43180
43650
  "Summary",
43181
43651
  "Theme",
43652
+ "TrackedChangeCaptureController",
43182
43653
  "UndoOperations",
43183
43654
  "UpdateIdle",
43184
43655
  "UpdateNotice",