codex-overleaf-link 2.3.2 → 2.3.4

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.
@@ -11368,7 +11368,7 @@ ${block}`;
11368
11368
  const MIN_NATIVE_VERSION = "1.0.0";
11369
11369
  const MIN_COMPATIBLE_NATIVE_VERSION = "1.0.0";
11370
11370
  const MIN_COMPATIBLE_EXTENSION_VERSION = "1.0.0";
11371
- const BUILD_TARGET_VERSION = "2.3.2";
11371
+ const BUILD_TARGET_VERSION = "2.3.4";
11372
11372
  const DEFAULT_CHROME_EXTENSION_ID = "illdpneeeopfffmiepaejglgmhpmdhdc";
11373
11373
  const REQUIRED_CAPABILITIES = Object.freeze([
11374
11374
  "bridgePing",
@@ -16058,11 +16058,39 @@ ${block}`;
16058
16058
  function defaultIsUsableProjectFileContent(content) {
16059
16059
  return String(content || "").trim().length > 0;
16060
16060
  }
16061
+ function isRetryReplacementEligible(run = {}, projection = {}) {
16062
+ if (!run.id || !["failed", "interrupted"].includes(run.status)) return false;
16063
+ if (run.trackedChangeStatus || run.undoStatus) return false;
16064
+ if (["undoOperations", "undoTrackedChanges", "undoExpectedFiles", "undoBaseFiles", "appliedOperations"].some((field) => Array.isArray(run[field]) && run[field].length)) return false;
16065
+ if (projection.canAccept || projection.canUndo || projection.changedDocument === true) return false;
16066
+ if (projection.failureTerminalState === "needs_review" || projection.terminalState === "needs_review") return false;
16067
+ return projection.changedDocument === false || run.mode === "ask" || hasZeroWriteReport(run.events);
16068
+ }
16069
+ function hasZeroWriteReport(events = []) {
16070
+ return (Array.isArray(events) ? events : []).some((event) => {
16071
+ const rows = Array.isArray(event?.detailStructured?.meta) ? event.detailStructured.meta : [];
16072
+ return rows.some((row) => row?.key === "writeResult" && /(?:wrote|写入)\s*0\b[\s\S]*(?:skipped|跳过)\s*0\b/i.test(String(row.value || "")));
16073
+ });
16074
+ }
16075
+ function resolveRetryReplacement({ candidate, activeSessionId, runs, projectRunSettlement } = {}) {
16076
+ if (!candidate?.runId || candidate.sessionId !== activeSessionId) return "";
16077
+ const source = (Array.isArray(runs) ? runs : []).find((run) => run?.id === candidate.runId);
16078
+ if (!source) return "";
16079
+ const projection = typeof projectRunSettlement === "function" ? projectRunSettlement(source) : {};
16080
+ return isRetryReplacementEligible(source, projection) ? source.id : "";
16081
+ }
16082
+ function replaceRunForRetry(runs, sourceRunId, nextRun, maxRuns = 20) {
16083
+ const retained = (Array.isArray(runs) ? runs : []).filter((run) => !sourceRunId || run?.id !== sourceRunId);
16084
+ return [...retained, nextRun].slice(-Math.max(1, maxRuns));
16085
+ }
16061
16086
  return {
16062
16087
  buildCodexRunParams,
16063
16088
  canUseFocusedPartialSnapshot,
16064
16089
  shouldRestrictWritebackToFocus,
16065
16090
  buildSessionHistoryResult,
16091
+ isRetryReplacementEligible,
16092
+ replaceRunForRetry,
16093
+ resolveRetryReplacement,
16066
16094
  truncateSessionHistoryText,
16067
16095
  normalizeComposerAttachments,
16068
16096
  normalizeSkillInvocation
@@ -16772,10 +16800,13 @@ ${block}`;
16772
16800
  const ROOT_CLASS = "codex-overleaf-panel-mounted";
16773
16801
  const RESIZE_CLASS = "codex-overleaf-panel-resizing";
16774
16802
  const COMPACT_CLASS = "codex-overleaf-panel-compact";
16803
+ const LAUNCHER_VISIBILITY_STORAGE_KEY = "codex-overleaf:launcher-visible";
16775
16804
  function codexIcon(name) {
16776
16805
  const icons = {
16777
16806
  refresh: '<path d="M4.2 5.3A5 5 0 1 1 3.6 10"/><path d="M4.2 2.2v3.1H1.1"/>',
16778
16807
  plus: '<path d="M8 3.2v9.6"/><path d="M3.2 8h9.6"/>',
16808
+ panel: '<rect x="2.3" y="2.3" width="11.4" height="11.4" rx="2"/><path d="M9.2 2.5v11"/><path d="M5.1 6.1h1.5M5.1 8h1.5M5.1 9.9h1.5"/>',
16809
+ collapse: '<path d="m6.1 3.3 4.7 4.7-4.7 4.7"/>',
16779
16810
  settings: '<path d="M3.4 5.2h5.9"/><path d="M11.7 5.2h.9"/><path d="M10.1 3.6v3.2"/><path d="M3.4 10.8h1"/><path d="M6.8 10.8h5.8"/><path d="M5.9 9.2v3.2"/>'
16780
16811
  };
16781
16812
  const safeName = Object.prototype.hasOwnProperty.call(icons, name) ? name : "settings";
@@ -16785,9 +16816,11 @@ ${block}`;
16785
16816
  const doc = options2.document || document;
16786
16817
  const container = options2.container || doc.documentElement;
16787
16818
  const panelEl = doc.createElement("aside");
16819
+ const launcherEl = doc.createElement("button");
16788
16820
  const callbacks = options2.callbacks || {};
16789
16821
  const instance = {
16790
16822
  panelEl,
16823
+ launcherEl,
16791
16824
  callbacks,
16792
16825
  defaultWidth: options2.defaultWidth || 380,
16793
16826
  minWidth: options2.minWidth || 340,
@@ -16796,8 +16829,16 @@ ${block}`;
16796
16829
  lastDesktopWidth: options2.initialWidth || options2.defaultWidth || 380,
16797
16830
  listeners: [],
16798
16831
  document: doc,
16799
- window: options2.window || window
16832
+ window: options2.window || window,
16833
+ chromeApi: options2.chromeApi || (typeof chrome !== "undefined" ? chrome : null),
16834
+ storageChangeListener: null
16800
16835
  };
16836
+ launcherEl.id = "codex-overleaf-launcher";
16837
+ launcherEl.type = "button";
16838
+ launcherEl.title = options2.launcherLabel || "Show or hide Codex panel";
16839
+ launcherEl.setAttribute("aria-label", launcherEl.title);
16840
+ launcherEl.setAttribute("aria-expanded", "false");
16841
+ launcherEl.innerHTML = codexIcon("panel");
16801
16842
  panelEl.id = options2.panelId || "codex-overleaf-panel";
16802
16843
  panelEl.dataset.view = "session";
16803
16844
  panelEl.innerHTML = `
@@ -16809,6 +16850,7 @@ ${block}`;
16809
16850
  <div data-diagnostics-slot></div>
16810
16851
  <button type="button" data-new-session title="New Session" aria-label="New Session">${codexIcon("plus")}</button>
16811
16852
  <button type="button" data-custom-instructions-settings title="Project Settings" aria-label="Project Settings" aria-expanded="false">${codexIcon("settings")}</button>
16853
+ <button type="button" data-close-panel title="Close Codex panel" aria-label="Close Codex panel">${codexIcon("collapse")}</button>
16812
16854
  </div>
16813
16855
  </div>
16814
16856
  <div data-settings-slot></div>
@@ -16823,6 +16865,7 @@ ${block}`;
16823
16865
  <div data-composer-slot></div>
16824
16866
  `;
16825
16867
  container.append(panelEl);
16868
+ container.append(launcherEl);
16826
16869
  doc.documentElement.classList.add(ROOT_CLASS);
16827
16870
  instance.headerEl = panelEl.querySelector("[data-panel-header]");
16828
16871
  instance.bodyEl = panelEl.querySelector("[data-main]");
@@ -16835,6 +16878,8 @@ ${block}`;
16835
16878
  bind(instance, panelEl.querySelector("[data-refresh]"), "click", () => callbacks.onRefresh?.());
16836
16879
  bind(instance, panelEl.querySelector("[data-new-session]"), "click", () => callbacks.onNewSession?.());
16837
16880
  bind(instance, panelEl.querySelector("[data-custom-instructions-settings]"), "click", () => callbacks.onSettingsClick?.());
16881
+ bind(instance, panelEl.querySelector("[data-close-panel]"), "click", () => callbacks.onClosePanel?.());
16882
+ bind(instance, launcherEl, "click", () => callbacks.onLauncherToggle?.());
16838
16883
  bind(instance, panelEl.querySelector("[data-panel-resize-handle]"), "pointerdown", (event) => startResize(instance, event));
16839
16884
  bind(instance, panelEl.querySelector("[data-panel-resize-handle]"), "dblclick", (event) => {
16840
16885
  event?.preventDefault?.();
@@ -16848,8 +16893,10 @@ ${block}`;
16848
16893
  setWidth(instance, instance.lastDesktopWidth || instance.defaultWidth, { notify: true, persist: false });
16849
16894
  });
16850
16895
  setWidth(instance, options2.initialWidth || instance.defaultWidth, { notify: false });
16896
+ initializeLauncherVisibility(instance);
16851
16897
  return {
16852
16898
  panelEl,
16899
+ launcherEl,
16853
16900
  headerEl: instance.headerEl,
16854
16901
  bodyEl: instance.bodyEl,
16855
16902
  diagnosticsSlot: instance.diagnosticsSlot,
@@ -16944,10 +16991,58 @@ ${block}`;
16944
16991
  function setVisible(panelEl, visible) {
16945
16992
  panelEl?.classList?.toggle("is-open", Boolean(visible));
16946
16993
  document.documentElement.classList.toggle(ROOT_CLASS, Boolean(visible));
16994
+ const launcherEl = panelEl?.ownerDocument?.getElementById?.("codex-overleaf-launcher");
16995
+ launcherEl?.classList?.toggle("is-panel-open", Boolean(visible));
16996
+ launcherEl?.setAttribute?.("aria-expanded", visible ? "true" : "false");
16997
+ if (launcherEl) {
16998
+ launcherEl.hidden = Boolean(visible) || launcherEl.dataset.enabled === "false";
16999
+ launcherEl.setAttribute("aria-hidden", launcherEl.hidden ? "true" : "false");
17000
+ }
16947
17001
  if (!visible) {
16948
17002
  document.documentElement.classList.remove(RESIZE_CLASS, COMPACT_CLASS);
16949
17003
  }
16950
17004
  }
17005
+ function initializeLauncherVisibility(instance) {
17006
+ const storage = instance.chromeApi?.storage;
17007
+ if (!storage?.local?.get) {
17008
+ setLauncherVisible(instance, true, { persist: false });
17009
+ return;
17010
+ }
17011
+ Promise.resolve(storage.local.get({ [LAUNCHER_VISIBILITY_STORAGE_KEY]: true })).then((values) => applyLauncherVisibility(instance, values?.[LAUNCHER_VISIBILITY_STORAGE_KEY])).catch(() => applyLauncherVisibility(instance, true));
17012
+ if (storage.onChanged?.addListener) {
17013
+ instance.storageChangeListener = (changes, areaName) => {
17014
+ if (areaName === "local" && changes?.[LAUNCHER_VISIBILITY_STORAGE_KEY]) {
17015
+ applyLauncherVisibility(instance, changes[LAUNCHER_VISIBILITY_STORAGE_KEY].newValue);
17016
+ }
17017
+ };
17018
+ storage.onChanged.addListener(instance.storageChangeListener);
17019
+ }
17020
+ }
17021
+ function applyLauncherVisibility(instance, value) {
17022
+ const visible = value !== false;
17023
+ setLauncherVisible(instance, visible, { persist: false });
17024
+ instance.callbacks.onLauncherVisibilityChange?.(visible);
17025
+ }
17026
+ function setLauncherVisible(target, visible, options2 = {}) {
17027
+ const instance = target?._instance || target;
17028
+ const launcherEl = instance?.launcherEl;
17029
+ if (!launcherEl) return false;
17030
+ const nextVisible = Boolean(visible);
17031
+ launcherEl.dataset.enabled = nextVisible ? "true" : "false";
17032
+ launcherEl.hidden = !nextVisible || instance.panelEl?.classList?.contains("is-open") === true;
17033
+ launcherEl.setAttribute("aria-hidden", launcherEl.hidden ? "true" : "false");
17034
+ if (options2.persist === true) {
17035
+ Promise.resolve(instance.chromeApi?.storage?.local?.set?.({
17036
+ [LAUNCHER_VISIBILITY_STORAGE_KEY]: nextVisible
17037
+ })).catch(() => {
17038
+ });
17039
+ }
17040
+ return nextVisible;
17041
+ }
17042
+ function isLauncherVisible(target) {
17043
+ const instance = target?._instance || target;
17044
+ return Boolean(instance?.launcherEl && instance.launcherEl.dataset.enabled !== "false");
17045
+ }
16951
17046
  function setBadge(headerEl, badge = {}) {
16952
17047
  const actions = headerEl?.querySelector?.(".codex-vscode-head-actions");
16953
17048
  if (!actions) {
@@ -17075,12 +17170,18 @@ ${block}`;
17075
17170
  target.removeEventListener?.(type, listener, options2);
17076
17171
  }
17077
17172
  instance.panelEl?.remove?.();
17173
+ instance.launcherEl?.remove?.();
17174
+ if (instance.storageChangeListener) {
17175
+ instance.chromeApi?.storage?.onChanged?.removeListener?.(instance.storageChangeListener);
17176
+ }
17078
17177
  instance.document.documentElement.classList.remove(ROOT_CLASS, RESIZE_CLASS, COMPACT_CLASS);
17079
17178
  }
17080
17179
  window.CodexOverleafPanelRenderer = {
17081
17180
  create,
17082
17181
  createConfirmController,
17083
17182
  setVisible,
17183
+ setLauncherVisible,
17184
+ isLauncherVisible,
17084
17185
  setBadge,
17085
17186
  setWidth
17086
17187
  };
@@ -18735,6 +18836,7 @@ ${block}`;
18735
18836
  return;
18736
18837
  }
18737
18838
  const style = instance.document.defaultView.getComputedStyle(panel);
18839
+ instance.root.dataset.theme = panel.dataset.theme === "light" ? "light" : "dark";
18738
18840
  instance.root.style.setProperty("--provider-panel-bg", style.backgroundColor || "#151716");
18739
18841
  instance.root.style.setProperty("--provider-panel-color", style.color || "#eceeea");
18740
18842
  }
@@ -20687,10 +20789,17 @@ ${block}`;
20687
20789
  }
20688
20790
  container.replaceChildren();
20689
20791
  container.hidden = !items.length;
20792
+ if (!items.length) {
20793
+ return;
20794
+ }
20690
20795
  for (const item of items) {
20691
20796
  const row = document.createElement("article");
20692
20797
  row.className = "codex-pending-input";
20693
20798
  row.dataset.status = item.status || "queued";
20799
+ const marker = document.createElement("span");
20800
+ marker.className = "codex-pending-input-marker";
20801
+ marker.setAttribute("aria-hidden", "true");
20802
+ marker.append(createIcon("queue"));
20694
20803
  const copy = document.createElement("div");
20695
20804
  copy.className = "codex-pending-input-copy";
20696
20805
  const status = document.createElement("span");
@@ -20699,7 +20808,10 @@ ${block}`;
20699
20808
  const text = document.createElement("span");
20700
20809
  text.className = "codex-pending-input-text";
20701
20810
  text.textContent = item.text;
20702
- copy.append(status, text);
20811
+ copy.append(text);
20812
+ if (item.status && item.status !== "queued") {
20813
+ copy.append(status);
20814
+ }
20703
20815
  const actions = document.createElement("div");
20704
20816
  actions.className = "codex-pending-input-actions";
20705
20817
  if (item.status === "paused") {
@@ -20723,7 +20835,8 @@ ${block}`;
20723
20835
  remove.disabled = item.status === "steering" || item.status === "executing";
20724
20836
  text.title = item.text;
20725
20837
  actions.append(remove);
20726
- row.append(copy, actions);
20838
+ row.setAttribute("aria-label", `${status.textContent || options2.tr?.("queuedInputQueued") || "Queued"}: ${item.text}`);
20839
+ row.append(marker, copy, actions);
20727
20840
  container.append(row);
20728
20841
  }
20729
20842
  }
@@ -20750,7 +20863,7 @@ ${block}`;
20750
20863
  svg.setAttribute("viewBox", "0 0 20 20");
20751
20864
  svg.setAttribute("aria-hidden", "true");
20752
20865
  svg.setAttribute("focusable", "false");
20753
- const paths = icon === "guide" ? ["M4 4.5v4a4 4 0 0 0 4 4h7", "m12 9 3.5 3.5L12 16"] : ["M6 6h8", "M8 6V4.5h4V6", "m7.2 8 0.6 7.5h4.4l.6-7.5"];
20866
+ const paths = icon === "guide" ? ["M4 4.5v4a4 4 0 0 0 4 4h7", "m12 9 3.5 3.5L12 16"] : icon === "queue" ? ["M4 5.5h12", "M4 10h12", "M4 14.5h8"] : ["M6 6h8", "M8 6V4.5h4V6", "m7.2 8 0.6 7.5h4.4l.6-7.5"];
20754
20867
  for (const value of paths) {
20755
20868
  const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
20756
20869
  path.setAttribute("d", value);
@@ -27385,9 +27498,69 @@ ${block}`;
27385
27498
  error.code = code;
27386
27499
  return error;
27387
27500
  }
27501
+ function decorateCompletionReport(container, options2 = {}) {
27502
+ const tx = typeof options2.tx === "function" ? options2.tx : (en) => en;
27503
+ const document2 = container?.ownerDocument || globalThis.document;
27504
+ if (!container || !document2) return;
27505
+ for (const block of Array.from(container.children || [])) {
27506
+ const textNode = Array.from(block.childNodes || []).find((node) => node?.nodeType === 3 && String(node.textContent || "").trim());
27507
+ if (!textNode) continue;
27508
+ const text = String(textNode.textContent || "");
27509
+ const match = text.match(/^(\s*)(Conclusion:|结论:|Changes:|修改:)(\s*)/);
27510
+ if (!match || !textNode.parentNode) continue;
27511
+ const label = document2.createElement("span");
27512
+ label.className = "run-system-label";
27513
+ label.dataset.systemLabel = ["Conclusion:", "\u7ED3\u8BBA\uFF1A"].includes(match[2]) ? "conclusion" : "changes";
27514
+ label.textContent = `${match[2]}${match[3]}`;
27515
+ textNode.textContent = `${match[1]}${text.slice(match[0].length)}`;
27516
+ textNode.parentNode.insertBefore(label, textNode);
27517
+ }
27518
+ decorateChangeReceipts(container, document2, tx);
27519
+ }
27520
+ function decorateChangeReceipts(container, document2, tx) {
27521
+ const label = container.querySelector?.('.run-system-label[data-system-label="changes"]');
27522
+ const list = label?.closest?.("p")?.nextElementSibling;
27523
+ if (!list || !["UL", "OL"].includes(list.tagName)) return;
27524
+ const receipts = [];
27525
+ for (const item of Array.from(list.children || [])) {
27526
+ const original = String(item.textContent || "").trim();
27527
+ const match = original.match(/^(.+?):\s+(edit|create|rename|move|delete)(?:\s+\((.+)\))?$/i) || original.match(/^(.+?):\s*(编辑|新建|重命名|移动|删除)(?:((.+)))?$/);
27528
+ if (!match) return;
27529
+ receipts.push({ item, original, file: match[1], operation: match[2] });
27530
+ }
27531
+ if (!receipts.length) return;
27532
+ const syncedFiles = new Set(receipts.map((receipt) => receipt.file)).size;
27533
+ const summary = document2.createElement("div");
27534
+ summary.className = "run-change-summary";
27535
+ const check = document2.createElement("span");
27536
+ check.className = "run-change-summary__check";
27537
+ check.setAttribute("aria-hidden", "true");
27538
+ check.textContent = "\u2713";
27539
+ const text = document2.createElement("span");
27540
+ text.textContent = tx(
27541
+ `${syncedFiles} ${syncedFiles === 1 ? "file" : "files"} synced`,
27542
+ `\u5DF2\u540C\u6B65 ${syncedFiles} \u4E2A\u6587\u4EF6`
27543
+ );
27544
+ summary.append(check, text);
27545
+ list.parentNode?.insertBefore(summary, list);
27546
+ list.classList.add("run-change-receipts");
27547
+ for (const receipt of receipts) {
27548
+ const file = document2.createElement("span");
27549
+ file.className = "run-change-receipt__file";
27550
+ file.textContent = receipt.file;
27551
+ const operation = document2.createElement("span");
27552
+ operation.className = "run-change-receipt__operation";
27553
+ operation.textContent = receipt.operation;
27554
+ receipt.item.classList.add("run-change-receipt");
27555
+ receipt.item.setAttribute("aria-label", receipt.original);
27556
+ receipt.item.title = receipt.original;
27557
+ receipt.item.replaceChildren(file, operation);
27558
+ }
27559
+ }
27388
27560
  return {
27389
27561
  DEFAULT_LIMITS,
27390
27562
  countTokens,
27563
+ decorateCompletionReport,
27391
27564
  renderMarkdown,
27392
27565
  stripEmptyHtmlComments
27393
27566
  };
@@ -28820,6 +28993,7 @@ ${block}`;
28820
28993
  cssEscape,
28821
28994
  renderMarkdownInlineText,
28822
28995
  renderMarkdownBlockText,
28996
+ decorateCompletionReport,
28823
28997
  sanitizeAssistantVisibleText,
28824
28998
  sanitizeAssistantVisibleValue,
28825
28999
  stripEmptyHtmlCommentPlaceholders = (value) => value,
@@ -29400,6 +29574,7 @@ ${lines}`
29400
29574
  }
29401
29575
  if (mainSections.length) {
29402
29576
  renderMarkdownBlockText(main, mainSections.join("\n\n"));
29577
+ decorateCompletionReport(main, { tx });
29403
29578
  report.append(main);
29404
29579
  }
29405
29580
  appendCompletionMetaBlock(report, structured.meta);
@@ -29413,6 +29588,7 @@ ${lines}`
29413
29588
  const body = document.createElement("div");
29414
29589
  body.className = "run-final-answer";
29415
29590
  renderMarkdownBlockText(body, split.body || flatText);
29591
+ decorateCompletionReport(body, { tx });
29416
29592
  report.append(body);
29417
29593
  appendCompileFix(report);
29418
29594
  appendRejectedRedo(report);
@@ -31395,7 +31571,7 @@ ${conclusion}` : `${label} ${conclusion}`;
31395
31571
  if (!efforts.length) {
31396
31572
  efforts = presentation === "none" ? ["none"] : ["low", "medium", "high", "xhigh"];
31397
31573
  }
31398
- const current = reasoningSelect.value || getState()?.reasoningEffort || "";
31574
+ const current = getState()?.reasoningEffort || reasoningSelect.value || "";
31399
31575
  const fallback = efforts.includes(model?.defaultReasoningEffort) ? model.defaultReasoningEffort : efforts.includes("high") ? "high" : efforts[0];
31400
31576
  reasoningSelect.textContent = "";
31401
31577
  for (const effort of efforts) {
@@ -34381,6 +34557,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
34381
34557
  setState,
34382
34558
  getCurrentRunView,
34383
34559
  getSettingsPanelInstance,
34560
+ prepareRetryReplacement,
34384
34561
  StorageDb
34385
34562
  } = deps;
34386
34563
  let auditHistoryRecords = [];
@@ -34533,6 +34710,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
34533
34710
  showPluginToast(tx("Nothing to refill: the original task text is unavailable.", "\u65E0\u6CD5\u56DE\u586B\uFF1A\u539F\u4EFB\u52A1\u6587\u672C\u4E0D\u53EF\u7528\u3002"));
34534
34711
  return;
34535
34712
  }
34713
+ prepareRetryReplacement?.(run || null);
34536
34714
  input.value = task;
34537
34715
  getState().task = task;
34538
34716
  autosizeTaskTextarea();
@@ -35543,7 +35721,8 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35543
35721
  state = next;
35544
35722
  },
35545
35723
  getCurrentRunView: () => currentRunView,
35546
- getSettingsPanelInstance: () => settingsPanelInstance
35724
+ getSettingsPanelInstance: () => settingsPanelInstance,
35725
+ prepareRetryReplacement: (run) => prepareRetryReplacement(run)
35547
35726
  });
35548
35727
  const {
35549
35728
  showUndoFileSelection,
@@ -35593,6 +35772,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35593
35772
  cssEscape,
35594
35773
  renderMarkdownInlineText,
35595
35774
  renderMarkdownBlockText,
35775
+ decorateCompletionReport: Modules.MarkdownDomRenderer.decorateCompletionReport,
35596
35776
  sanitizeAssistantVisibleText,
35597
35777
  sanitizeAssistantVisibleValue,
35598
35778
  buildMarkdownInlineNodes,
@@ -35984,6 +36164,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35984
36164
  let state = null;
35985
36165
  let storageKey = LEGACY_STORAGE_KEY;
35986
36166
  let currentRunView = null;
36167
+ let pendingRetryReplacement = null;
35987
36168
  let runQueueScheduler = null;
35988
36169
  let runInputCoordinator = null;
35989
36170
  const activeTurnControl = ActiveTurnControl.create({ chrome });
@@ -36074,6 +36255,13 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
36074
36255
  sendResponse?.(getPanelStateResponse());
36075
36256
  return;
36076
36257
  }
36258
+ if (message?.type === "codex-overleaf/toggle-launcher") {
36259
+ const visible = !PanelRenderer.isLauncherVisible(panelRendererInstance);
36260
+ PanelRenderer.setLauncherVisible(panelRendererInstance, visible, { persist: true });
36261
+ if (!visible) closePanel();
36262
+ sendResponse?.(getPanelStateResponse());
36263
+ return;
36264
+ }
36077
36265
  if (message?.type === "codex-overleaf/toggle-panel") {
36078
36266
  if (isPanelOpen()) {
36079
36267
  closePanel();
@@ -36287,7 +36475,14 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
36287
36475
  maxWidth: PANEL_MAX_WIDTH,
36288
36476
  pageMinWidth: PAGE_MIN_WIDTH,
36289
36477
  initialWidth: state?.panelWidth || PANEL_DEFAULT_WIDTH,
36478
+ chromeApi: chrome,
36479
+ launcherLabel: tx("Show or hide Codex panel", "\u663E\u793A\u6216\u9690\u85CF Codex \u8FB9\u680F"),
36290
36480
  callbacks: {
36481
+ onLauncherToggle: () => ensurePanelOpen(),
36482
+ onClosePanel: () => closePanel(),
36483
+ onLauncherVisibilityChange: (visible) => {
36484
+ if (!visible) closePanel();
36485
+ },
36291
36486
  onRefresh: () => refreshProbe({ userInitiated: true }),
36292
36487
  onNewSession: () => startNewSession(),
36293
36488
  onChangeHistory: () => openChangeHistory(),
@@ -36451,7 +36646,8 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
36451
36646
  function getPanelStateResponse() {
36452
36647
  return {
36453
36648
  ok: true,
36454
- open: isPanelOpen()
36649
+ open: isPanelOpen(),
36650
+ launcherVisible: PanelRenderer.isLauncherVisible(panelRendererInstance)
36455
36651
  };
36456
36652
  }
36457
36653
  function toggleDiagnosticsMenu() {
@@ -36811,6 +37007,12 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
36811
37007
  ) : captureCurrentExecutionSnapshot(submittedPanelState);
36812
37008
  const submittedMode = executionSnapshot.mode;
36813
37009
  const submittedRequireReviewing = executionSnapshot.requireReviewing === true;
37010
+ const replaceRunId = queuedInput ? "" : runController.resolveRetryReplacement({
37011
+ candidate: pendingRetryReplacement,
37012
+ activeSessionId: state.activeSessionId,
37013
+ runs: state.runs,
37014
+ projectRunSettlement: (run) => WritebackSettlement.projectRunSettlement(run)
37015
+ });
36814
37016
  runCancellationRequested = false;
36815
37017
  runCancellationController = new AbortController();
36816
37018
  setRunning(true);
@@ -36823,8 +37025,10 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
36823
37025
  executionSnapshot,
36824
37026
  attachments: submittedAttachments,
36825
37027
  skillInvocation: submittedSkillInvocation,
36826
- queueItemId: queuedInput?.id || ""
37028
+ queueItemId: queuedInput?.id || "",
37029
+ replaceRunId
36827
37030
  });
37031
+ if (!queuedInput) pendingRetryReplacement = null;
36828
37032
  const runSessionId = currentRunView.sessionId;
36829
37033
  let runAuditDraft = null;
36830
37034
  try {
@@ -40397,6 +40601,16 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
40397
40601
  panel.dataset.cancelling = running && runCancellationRequested ? "true" : "false";
40398
40602
  renderPendingInputs();
40399
40603
  }
40604
+ function prepareRetryReplacement(run) {
40605
+ const sessionId = state?.activeSessionId || "";
40606
+ const source = findRunRecord(run?.id, sessionId);
40607
+ const eligible = source && runController.isRetryReplacementEligible(
40608
+ source,
40609
+ WritebackSettlement.projectRunSettlement(source)
40610
+ );
40611
+ pendingRetryReplacement = eligible ? { sessionId, runId: source.id } : null;
40612
+ return Boolean(eligible);
40613
+ }
40400
40614
  function startRunView({
40401
40615
  task,
40402
40616
  mode,
@@ -40404,6 +40618,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
40404
40618
  reasoningEffort,
40405
40619
  speedTier,
40406
40620
  queueItemId = "",
40621
+ replaceRunId = "",
40407
40622
  executionSnapshot = null
40408
40623
  }) {
40409
40624
  let attachments = [];
@@ -40451,12 +40666,13 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
40451
40666
  titleSource: "auto"
40452
40667
  } : {};
40453
40668
  state = updateActiveSession(state, {
40454
- runs: [...state.runs || [], record].slice(-20),
40669
+ runs: runController.replaceRunForRetry(state.runs, replaceRunId, record),
40455
40670
  ...titlePatch
40456
40671
  });
40457
- saveStateSoon();
40672
+ saveStateSoon(replaceRunId ? 0 : 120);
40458
40673
  const log = panel.querySelector("[data-log]");
40459
40674
  removeEmptyRunsMessage(log);
40675
+ if (replaceRunId) log.querySelector(`[data-run-id="${cssEscape(replaceRunId)}"]`)?.remove();
40460
40676
  const root2 = renderRunCard(record);
40461
40677
  log.append(root2);
40462
40678
  scrollLogToBottom({ force: true });
@@ -2,9 +2,9 @@
2
2
  "schemaVersion": 1,
3
3
  "entry": "extension/entries/content-entry.mjs",
4
4
  "output": "extension/src/content/generated/content.bundle.js",
5
- "sourceDigest": "d2167fca49721d0ef5e477dc76b11a0cedb33c9f51b920b55ea666ab4668dc4c",
6
- "outputDigest": "87cbcf7d9877860b7c1d047cd3fa6e985e28812c8df81c9105ec9312a5e45ea6",
7
- "byteLength": 2309285,
5
+ "sourceDigest": "48e7ba193b0723f8e135c4b52e3af1416b1fe35b3cb780207fb0600e7e0f8019",
6
+ "outputDigest": "866811d95e8bd58d369e0790bf2af5becc93946eb8ad987b80f0be0f80fa965f",
7
+ "byteLength": 2321914,
8
8
  "inputs": [
9
9
  "extension/entries/content-entry.mjs",
10
10
  "extension/src/content/activeTurnControl.js",
@@ -442,9 +442,79 @@
442
442
  return error;
443
443
  }
444
444
 
445
+ function decorateCompletionReport(container, options = {}) {
446
+ const tx = typeof options.tx === 'function' ? options.tx : en => en;
447
+ const document = container?.ownerDocument || globalThis.document;
448
+ if (!container || !document) return;
449
+
450
+ for (const block of Array.from(container.children || [])) {
451
+ const textNode = Array.from(block.childNodes || [])
452
+ .find(node => node?.nodeType === 3 && String(node.textContent || '').trim());
453
+ if (!textNode) continue;
454
+ const text = String(textNode.textContent || '');
455
+ const match = text.match(/^(\s*)(Conclusion:|结论:|Changes:|修改:)(\s*)/);
456
+ if (!match || !textNode.parentNode) continue;
457
+ const label = document.createElement('span');
458
+ label.className = 'run-system-label';
459
+ label.dataset.systemLabel = ['Conclusion:', '结论:'].includes(match[2])
460
+ ? 'conclusion'
461
+ : 'changes';
462
+ label.textContent = `${match[2]}${match[3]}`;
463
+ textNode.textContent = `${match[1]}${text.slice(match[0].length)}`;
464
+ textNode.parentNode.insertBefore(label, textNode);
465
+ }
466
+ decorateChangeReceipts(container, document, tx);
467
+ }
468
+
469
+ function decorateChangeReceipts(container, document, tx) {
470
+ const label = container.querySelector?.('.run-system-label[data-system-label="changes"]');
471
+ const list = label?.closest?.('p')?.nextElementSibling;
472
+ if (!list || !['UL', 'OL'].includes(list.tagName)) return;
473
+
474
+ const receipts = [];
475
+ for (const item of Array.from(list.children || [])) {
476
+ const original = String(item.textContent || '').trim();
477
+ const match = original.match(/^(.+?):\s+(edit|create|rename|move|delete)(?:\s+\((.+)\))?$/i)
478
+ || original.match(/^(.+?):\s*(编辑|新建|重命名|移动|删除)(?:((.+)))?$/);
479
+ if (!match) return;
480
+ receipts.push({ item, original, file: match[1], operation: match[2] });
481
+ }
482
+ if (!receipts.length) return;
483
+
484
+ const syncedFiles = new Set(receipts.map(receipt => receipt.file)).size;
485
+ const summary = document.createElement('div');
486
+ summary.className = 'run-change-summary';
487
+ const check = document.createElement('span');
488
+ check.className = 'run-change-summary__check';
489
+ check.setAttribute('aria-hidden', 'true');
490
+ check.textContent = '✓';
491
+ const text = document.createElement('span');
492
+ text.textContent = tx(
493
+ `${syncedFiles} ${syncedFiles === 1 ? 'file' : 'files'} synced`,
494
+ `已同步 ${syncedFiles} 个文件`
495
+ );
496
+ summary.append(check, text);
497
+ list.parentNode?.insertBefore(summary, list);
498
+ list.classList.add('run-change-receipts');
499
+
500
+ for (const receipt of receipts) {
501
+ const file = document.createElement('span');
502
+ file.className = 'run-change-receipt__file';
503
+ file.textContent = receipt.file;
504
+ const operation = document.createElement('span');
505
+ operation.className = 'run-change-receipt__operation';
506
+ operation.textContent = receipt.operation;
507
+ receipt.item.classList.add('run-change-receipt');
508
+ receipt.item.setAttribute('aria-label', receipt.original);
509
+ receipt.item.title = receipt.original;
510
+ receipt.item.replaceChildren(file, operation);
511
+ }
512
+ }
513
+
445
514
  return {
446
515
  DEFAULT_LIMITS,
447
516
  countTokens,
517
+ decorateCompletionReport,
448
518
  renderMarkdown,
449
519
  stripEmptyHtmlComments
450
520
  };
@@ -319,7 +319,7 @@
319
319
  if (!efforts.length) {
320
320
  efforts = presentation === 'none' ? ['none'] : ['low', 'medium', 'high', 'xhigh'];
321
321
  }
322
- const current = reasoningSelect.value || getState()?.reasoningEffort || '';
322
+ const current = getState()?.reasoningEffort || reasoningSelect.value || '';
323
323
  const fallback = efforts.includes(model?.defaultReasoningEffort)
324
324
  ? model.defaultReasoningEffort
325
325
  : efforts.includes('high') ? 'high' : efforts[0];
@@ -29,6 +29,7 @@
29
29
  setState,
30
30
  getCurrentRunView,
31
31
  getSettingsPanelInstance,
32
+ prepareRetryReplacement,
32
33
  StorageDb
33
34
  } = deps;
34
35
 
@@ -218,6 +219,7 @@
218
219
  showPluginToast(tx('Nothing to refill: the original task text is unavailable.', '无法回填:原任务文本不可用。'));
219
220
  return;
220
221
  }
222
+ prepareRetryReplacement?.(run || null);
221
223
  input.value = task;
222
224
  getState().task = task;
223
225
  autosizeTaskTextarea();