codex-overleaf-link 2.5.1 → 2.6.5

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 (111) hide show
  1. package/README.md +149 -398
  2. package/README.zh-CN.md +153 -404
  3. package/extension/bootstrap/manifest.template.json +1 -1
  4. package/extension/src/background.js +148 -3
  5. package/extension/src/backgroundSharedSessions.js +55 -2
  6. package/extension/src/backgroundUpdateCoordinator.js +128 -12
  7. package/extension/src/content/activeTurnControl.js +164 -7
  8. package/extension/src/content/composerAttachments.js +46 -10
  9. package/extension/src/content/composerPanel.js +92 -12
  10. package/extension/src/content/contentRuntime.js +1114 -321
  11. package/extension/src/content/contextTray.js +8 -2
  12. package/extension/src/content/contextUsageView.js +267 -0
  13. package/extension/src/content/diagnosticsController.js +77 -34
  14. package/extension/src/content/diagnosticsExport.js +1 -1
  15. package/extension/src/content/diagnosticsPanel.js +147 -42
  16. package/extension/src/content/generated/content.bundle.js +5761 -1237
  17. package/extension/src/content/generated/content.bundle.meta.json +6 -3
  18. package/extension/src/content/legacyGlobalRegistry.js +4 -0
  19. package/extension/src/content/localSkillsPanel.js +6 -4
  20. package/extension/src/content/markdownDomRenderer.js +5 -3
  21. package/extension/src/content/markdownText.js +53 -14
  22. package/extension/src/content/modelPicker.js +29 -19
  23. package/extension/src/content/nativeCompatibilityController.js +52 -19
  24. package/extension/src/content/otWarmMirror.js +1 -1
  25. package/extension/src/content/pageBridgeClient.js +7 -7
  26. package/extension/src/content/panelMaintenance.js +10 -2
  27. package/extension/src/content/panelRenderer.js +55 -28
  28. package/extension/src/content/pendingInputView.js +1 -1
  29. package/extension/src/content/projectSettingsCoordinator.js +30 -6
  30. package/extension/src/content/providerSettingsDialog.js +28 -19
  31. package/extension/src/content/recentProjects.js +30 -19
  32. package/extension/src/content/runActivitySummary.js +122 -14
  33. package/extension/src/content/runCompletionMeta.js +194 -6
  34. package/extension/src/content/runController.js +2 -1
  35. package/extension/src/content/runFailureNotice.js +8 -1
  36. package/extension/src/content/runPresence.js +18 -8
  37. package/extension/src/content/runQueueScheduler.js +27 -13
  38. package/extension/src/content/runRecoveryActions.js +2 -1
  39. package/extension/src/content/runResultActions.js +73 -24
  40. package/extension/src/content/runReviewController.js +102 -37
  41. package/extension/src/content/runTimelineView.js +224 -25
  42. package/extension/src/content/scopedPersistencePanelState.js +124 -46
  43. package/extension/src/content/selectionContextView.js +30 -3
  44. package/extension/src/content/sensitivePreflight.js +14 -1
  45. package/extension/src/content/sessionManager.js +49 -3
  46. package/extension/src/content/sessionMenuView.js +54 -18
  47. package/extension/src/content/sessionPersistence.js +25 -5
  48. package/extension/src/content/settingsPanel.js +15 -2
  49. package/extension/src/content/settingsWorkbench.js +127 -89
  50. package/extension/src/content/sharedSessionViewSync.js +13 -4
  51. package/extension/src/content/subagentActivityView.js +23 -7
  52. package/extension/src/content/updateNotice.js +60 -6
  53. package/extension/src/content/writebackOrchestrator.js +301 -72
  54. package/extension/src/content/writingStyleSettings.js +70 -17
  55. package/extension/src/page/compileBridge.js +193 -180
  56. package/extension/src/page/overleafCapabilities.js +4 -1
  57. package/extension/src/page/overleafEditor.js +12 -3
  58. package/extension/src/page/overleafProjectSnapshot.js +56 -3
  59. package/extension/src/page/snapshotRouter.js +75 -66
  60. package/extension/src/page/textCoordinates.js +17 -1
  61. package/extension/src/page/textFileCreator.js +9 -1
  62. package/extension/src/page/treeOperations.js +200 -19
  63. package/extension/src/page/writebackRouter.js +172 -113
  64. package/extension/src/pageBridge.js +124 -73
  65. package/extension/src/popup.js +1 -1
  66. package/extension/src/shared/agentTranscript.js +372 -18
  67. package/extension/src/shared/auditRecords.js +3 -0
  68. package/extension/src/shared/compatibility.js +1 -1
  69. package/extension/src/shared/compileAdapter.js +58 -17
  70. package/extension/src/shared/composerAttachmentDraft.js +27 -7
  71. package/extension/src/shared/contextUsage.js +125 -0
  72. package/extension/src/shared/failureReasons.js +57 -3
  73. package/extension/src/shared/i18n.js +315 -29
  74. package/extension/src/shared/lineReferences.js +38 -31
  75. package/extension/src/shared/pageRpcContract.js +1 -1
  76. package/extension/src/shared/panelIcons.js +39 -0
  77. package/extension/src/shared/projectFiles.js +2 -1
  78. package/extension/src/shared/reviewing.js +130 -1
  79. package/extension/src/shared/runActivityModel.js +70 -4
  80. package/extension/src/shared/runExecutionSnapshot.js +13 -0
  81. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +34 -0
  82. package/extension/src/shared/sessionState.js +63 -4
  83. package/extension/src/shared/sessionTitle.js +14 -1
  84. package/extension/src/shared/sharedSessionBridge.js +81 -13
  85. package/extension/src/shared/staleGuard.js +130 -63
  86. package/extension/src/shared/storageDb.js +35 -3
  87. package/extension/src/shared/storageMigration.js +111 -2
  88. package/extension/src/shared/storageRunActions.js +62 -8
  89. package/extension/src/shared/undoOperations.js +126 -28
  90. package/extension/src/shared/updateRuntimeIdentity.js +16 -2
  91. package/extension/src/shared/writebackSettlement.js +21 -3
  92. package/extension/styles/panel.css +1293 -549
  93. package/native-host/src/bootstrapUpdate.js +52 -16
  94. package/native-host/src/chatBridgeRequest.js +60 -6
  95. package/native-host/src/chatBridgeResponse.js +10 -1
  96. package/native-host/src/chatCompletionsBridge.js +29 -2
  97. package/native-host/src/codexPromptAssembly.js +86 -118
  98. package/native-host/src/codexSessionRunner.js +127 -17
  99. package/native-host/src/debugLog.js +27 -3
  100. package/native-host/src/index.js +42 -11
  101. package/native-host/src/localSkills.js +1 -1
  102. package/native-host/src/mirrorWorkspace.js +47 -3
  103. package/native-host/src/nativeEnvironment.js +27 -5
  104. package/native-host/src/providerContextError.js +20 -0
  105. package/native-host/src/providerRuntime.js +1 -0
  106. package/native-host/src/skills/reviewer-response/SKILL.md +65 -0
  107. package/native-host/src/taskRunnerRuntime.js +28 -8
  108. package/native-host/src/textPatch.js +473 -377
  109. package/native-host/src/updateManager.js +144 -21
  110. package/native-host/src/writingStyleRuntime.js +1 -1
  111. package/package.json +5 -8
@@ -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": "548acfc19dd7be907e0c5494f2da2e11fa68dfc501e0552846ad63d4e5a35dba",
6
- "outputDigest": "404e0523fc534b56e87ea96e62cd112514c06ce034209ec7a05feef83d7a64a9",
7
- "byteLength": 3162346,
5
+ "sourceDigest": "91739cf894fed56c2880986120922d3b54bb423ab2d116705385da1d0fe518be",
6
+ "outputDigest": "a52bf46e64085f1a64339769d64ab59246323c3a51c65851f3385750727b0624",
7
+ "byteLength": 3443627,
8
8
  "inputs": [
9
9
  "extension/entries/content-entry.mjs",
10
10
  "extension/src/content/activeTurnControl.js",
@@ -15,6 +15,7 @@
15
15
  "extension/src/content/composerPanel.js",
16
16
  "extension/src/content/contentRuntime.js",
17
17
  "extension/src/content/contextTray.js",
18
+ "extension/src/content/contextUsageView.js",
18
19
  "extension/src/content/diagnosticsController.js",
19
20
  "extension/src/content/diagnosticsExport.js",
20
21
  "extension/src/content/diagnosticsPanel.js",
@@ -89,6 +90,7 @@
89
90
  "extension/src/shared/compatibility.js",
90
91
  "extension/src/shared/compileAdapter.js",
91
92
  "extension/src/shared/composerAttachmentDraft.js",
93
+ "extension/src/shared/contextUsage.js",
92
94
  "extension/src/shared/directoryCleanupProof.js",
93
95
  "extension/src/shared/failureReasons.js",
94
96
  "extension/src/shared/globalPreferences.js",
@@ -99,6 +101,7 @@
99
101
  "extension/src/shared/models.js",
100
102
  "extension/src/shared/otText.js",
101
103
  "extension/src/shared/pageRpcContract.js",
104
+ "extension/src/shared/panelIcons.js",
102
105
  "extension/src/shared/pathRedaction.js",
103
106
  "extension/src/shared/projectFiles.js",
104
107
  "extension/src/shared/providerProfiles.js",
@@ -29,6 +29,8 @@
29
29
  'ComposerPanel',
30
30
  'ContentRuntime',
31
31
  'ContextTray',
32
+ 'ContextUsage',
33
+ 'ContextUsageView',
32
34
  'DiagnosticsController',
33
35
  'DiagnosticsExport',
34
36
  'DiagnosticsPanel',
@@ -55,10 +57,12 @@
55
57
  'PostNavigationSettlementPersistence',
56
58
  'ProjectFiles',
57
59
  'ProjectProviderSelection',
60
+ 'PanelIcons',
58
61
  'ProjectSettingsCoordinator',
59
62
  'ProviderSettingsCoordinator',
60
63
  'RecentProjects',
61
64
  'ReviewHunks',
65
+ 'Reviewing',
62
66
  'RunActivitySummary',
63
67
  'RunPresence',
64
68
  'RunActivityModel',
@@ -204,7 +204,8 @@
204
204
  copy.append(text);
205
205
  const descriptions = {
206
206
  'annotated-rewrite': tx('Guided editing for document revisions.', '用于文稿修订的编辑流程。'),
207
- 'parallel-subagents': tx('Split independent subtasks across multiple agents.', '将相互独立的子任务分配给多个 agent。')
207
+ 'parallel-subagents': tx('Split independent subtasks across multiple agents.', '将相互独立的子任务分配给多个 agent。'),
208
+ 'reviewer-response': tx('Map reviewer comments to evidence, edits and reply drafts.', '把审稿意见对应到论文证据、修改和回复草稿。')
208
209
  };
209
210
  const description = (!local && descriptions[id])
210
211
  || (typeof skill.description === 'string' ? skill.description.replace(/\s+/g, ' ').trim() : '');
@@ -249,7 +250,8 @@
249
250
  if (sourceLabel) sourceLabel.textContent = sourceText();
250
251
  toggle.setAttribute('aria-label', accessibleSkillName + ': ' + tr('codexOverleafSkillEnableToggle'));
251
252
  const label = !local && id === 'annotated-rewrite' ? tx('Guided editing for document revisions.', '用于文稿修订的编辑流程。')
252
- : !local && id === 'parallel-subagents' ? tx('Split independent subtasks across multiple agents.', '将相互独立的子任务分配给多个 agent。') : '';
253
+ : !local && id === 'parallel-subagents' ? tx('Split independent subtasks across multiple agents.', '将相互独立的子任务分配给多个 agent。')
254
+ : !local && id === 'reviewer-response' ? tx('Map reviewer comments to evidence, edits and reply drafts.', '把审稿意见对应到论文证据、修改和回复草稿。') : '';
253
255
  const help = copy.querySelector('.codex-local-skill-description');
254
256
  if (label && help) { help.textContent = label; help.title = label; }
255
257
  };
@@ -320,7 +322,7 @@
320
322
  toggle.disabled = true;
321
323
  confirmBtn.textContent = tr('localSkillRemoving');
322
324
  removeCodexOverleafSkill(id).catch(error => {
323
- setProjectSettingsStatus(tx(`Failed to remove Codex Overleaf skill: ${error.message}`, `删除 Codex Overleaf 技能失败:${error.message}`), 'failed');
325
+ setProjectSettingsStatus(tx(`Failed to remove Codex Overleaf skill: ${error.message}`, `删除 Codex Overleaf 技能失败:${error.message}`), 'failed', { page: 'context' });
324
326
  // Restore row on failure so the user can retry.
325
327
  confirmBtn.disabled = false;
326
328
  cancelBtn.disabled = false;
@@ -381,7 +383,7 @@
381
383
  if (activeSkill?.scope === 'codex-overleaf' && activeSkill.id === id) {
382
384
  clearComposerSkillInvocation();
383
385
  }
384
- setProjectSettingsStatus(tr('codexOverleafSkillRemoveDone'), 'completed');
386
+ setProjectSettingsStatus(tr('codexOverleafSkillRemoveDone'), 'completed', { page: 'context' });
385
387
  clearSlashCodexOverleafSkills();
386
388
  await refreshLocalSkills();
387
389
  await saveState?.();
@@ -468,10 +468,10 @@
468
468
  textNode.textContent = `${match[1]}${text.slice(match[0].length)}`;
469
469
  textNode.parentNode.insertBefore(label, textNode);
470
470
  }
471
- decorateChangeReceipts(container, document, tx);
471
+ decorateChangeReceipts(container, document, tx, options.formatOperationType);
472
472
  }
473
473
 
474
- function decorateChangeReceipts(container, document, tx) {
474
+ function decorateChangeReceipts(container, document, tx, formatOperationType = type => type) {
475
475
  const label = container.querySelector?.('.run-system-label[data-system-label="changes"]');
476
476
  const list = label?.closest?.('p')?.nextElementSibling;
477
477
  if (!list || !['UL', 'OL'].includes(list.tagName)) return;
@@ -508,7 +508,9 @@
508
508
  file.textContent = receipt.file;
509
509
  const operation = document.createElement('span');
510
510
  operation.className = 'run-change-receipt__operation';
511
- operation.textContent = receipt.operation;
511
+ const type = ({ 编辑: 'edit', 新建: 'create', 重命名: 'rename', 移动: 'move', 删除: 'delete' })[receipt.operation]
512
+ || receipt.operation.toLowerCase();
513
+ operation.textContent = formatOperationType(type);
512
514
  receipt.item.classList.add('run-change-receipt');
513
515
  receipt.item.setAttribute('aria-label', receipt.original);
514
516
  receipt.item.title = receipt.original;
@@ -70,6 +70,15 @@
70
70
  macros: getMathMacros(), renderText: buildMarkdownInlineNodesWithoutMath });
71
71
  }
72
72
 
73
+ function buildParsedTextNodes(value) {
74
+ const source = String(value || '');
75
+ if (!MathText?.buildMathNodes) {
76
+ return buildLineReferenceInlineNodes(source);
77
+ }
78
+ return MathText.buildMathNodes(source, { document, katex: window.katex,
79
+ macros: getMathMacros(), renderText: buildLineReferenceInlineNodes });
80
+ }
81
+
73
82
  function buildMarkdownInlineNodesWithoutMath(value) {
74
83
  const source = String(value || '');
75
84
  const nodes = [];
@@ -99,7 +108,7 @@
99
108
  index = next.end;
100
109
  }
101
110
 
102
- return nodes;
111
+ return groupLineReferenceNodes(nodes);
103
112
  }
104
113
 
105
114
  function buildInlineCodeNodes(value) {
@@ -152,7 +161,7 @@
152
161
  cursor = start + rawText.length;
153
162
  }
154
163
  appendSanitizedTextNode(nodes, source.slice(cursor));
155
- return nodes;
164
+ return groupLineReferenceNodes(nodes);
156
165
  }
157
166
 
158
167
  function buildMarkdownLinkNodes(text, href) {
@@ -187,6 +196,24 @@
187
196
  }
188
197
  }
189
198
 
199
+ function groupLineReferenceNodes(nodes) {
200
+ const grouped = [];
201
+ for (const node of nodes) {
202
+ const previous = grouped[grouped.length - 1];
203
+ if (node.classList?.contains('codex-line-reference') && previous?.nodeType === 3 &&
204
+ previous.textContent.endsWith('(')) {
205
+ previous.textContent = previous.textContent.slice(0, -1);
206
+ const group = document.createElement('span');
207
+ group.className = 'codex-line-reference-group';
208
+ group.append(document.createTextNode('('), node);
209
+ grouped.push(group);
210
+ } else {
211
+ grouped.push(node);
212
+ }
213
+ }
214
+ return grouped;
215
+ }
216
+
190
217
  function resolveMarkdownLineReference(text, href) {
191
218
  const candidates = [
192
219
  { value: href, mode: 'markdown-link-target' },
@@ -230,12 +257,16 @@
230
257
  return null;
231
258
  }
232
259
  const line = Number(ref?.line);
260
+ const endLine = ref?.endLine === undefined ? null : Number(ref.endLine);
233
261
  const column = ref?.column === null || ref?.column === undefined || ref?.column === ''
234
262
  ? null
235
263
  : Number(ref.column);
236
264
  if (!Number.isSafeInteger(line) || line < 1 || (column !== null && (!Number.isSafeInteger(column) || column < 1))) {
237
265
  return null;
238
266
  }
267
+ if (endLine !== null && (!Number.isSafeInteger(endLine) || endLine < line)) {
268
+ return null;
269
+ }
239
270
  let resolved;
240
271
  try {
241
272
  resolved = LineReferences.resolveProjectReference({
@@ -251,6 +282,7 @@
251
282
  return {
252
283
  path: resolved.path,
253
284
  line,
285
+ ...(endLine > line ? { endLine } : {}),
254
286
  column
255
287
  };
256
288
  }
@@ -262,10 +294,15 @@
262
294
  button.textContent = formatLineReferenceText(reference);
263
295
  button.dataset.path = reference.path;
264
296
  button.dataset.line = String(reference.line);
297
+ if (reference.endLine) {
298
+ button.dataset.endLine = String(reference.endLine);
299
+ }
265
300
  if (reference.column) {
266
301
  button.dataset.column = String(reference.column);
267
302
  }
268
- const ariaLabel = reference.column
303
+ const ariaLabel = reference.endLine
304
+ ? `Open ${reference.path} lines ${reference.line} to ${reference.endLine}`
305
+ : reference.column
269
306
  ? `Open ${reference.path} line ${reference.line} column ${reference.column}`
270
307
  : `Open ${reference.path} line ${reference.line}`;
271
308
  button.setAttribute('aria-label', ariaLabel);
@@ -278,7 +315,10 @@
278
315
  line: reference.line,
279
316
  selectLine: !reference.column
280
317
  };
281
- if (reference.column) {
318
+ if (reference.endLine) {
319
+ params.endLine = reference.endLine;
320
+ params.selectLine = true;
321
+ } else if (reference.column) {
282
322
  params.column = reference.column;
283
323
  params.selectLine = false;
284
324
  }
@@ -310,7 +350,7 @@
310
350
  }
311
351
 
312
352
  function formatLineReferenceText(reference) {
313
- return `${reference.path}:${reference.line}${reference.column ? `:${reference.column}` : ''}`;
353
+ return `${reference.path}:${reference.line}${reference.endLine ? `–${reference.endLine}` : reference.column ? `:${reference.column}` : ''}`;
314
354
  }
315
355
 
316
356
  function normalizeReferencePathForRuntime(value) {
@@ -483,15 +523,8 @@
483
523
  document,
484
524
  markdownIt: runtimeRoot.markdownit,
485
525
  mathText: runtimeRoot.CodexOverleafMathText,
486
- renderTextNodes: text => buildMarkdownInlineNodes(text),
487
- renderLinkNodes: (label, href) => {
488
- const safeLabel = String(label || '');
489
- const target = String(href || '').trim();
490
- if (!/^https?:\/\//i.test(target) && !resolveMarkdownLineReference(safeLabel, target)) return buildMarkdownInlineNodes(safeLabel);
491
- return buildMarkdownInlineNodes(
492
- `[${safeLabel.replace(/]/g, '\\]')}](${target.replace(/\)/g, '\\)')})`
493
- );
494
- },
526
+ renderTextNodes: buildParsedTextNodes,
527
+ renderLinkNodes: buildMarkdownLinkNodes,
495
528
  renderCodeNodes: text => buildInlineCodeNodes(text),
496
529
  sanitizeText: sanitizeAssistantVisibleText,
497
530
  createMathNode: segment => runtimeRoot.CodexOverleafMathText.createMathNode(segment,
@@ -499,6 +532,12 @@
499
532
  renderText: text => buildMarkdownInlineNodes(text) }),
500
533
  recordDiagnostic: recordRenderingDiagnostic
501
534
  });
535
+ // The DOM renderer emits text and links from separate Markdown tokens.
536
+ const parents = new Set(Array.from(target.querySelectorAll('.codex-line-reference'), button => button.parentNode)
537
+ .filter(parent => !parent.classList.contains('codex-line-reference-group')));
538
+ for (const parent of parents) {
539
+ parent.replaceChildren(...groupLineReferenceNodes(Array.from(parent.childNodes)));
540
+ }
502
541
  return;
503
542
  } catch (error) {
504
543
  recordRenderingDiagnostic({
@@ -33,7 +33,7 @@
33
33
  retainSelectedModel
34
34
  } = Support;
35
35
 
36
- let modelDiscovery = { status: 'fallback', source: 'fallback', fetchedAt: '' };
36
+ let modelDiscovery = { status: 'fallback', source: 'fallback', fetchedAt: '', provisional: true };
37
37
  let modelLoadGeneration = 0;
38
38
 
39
39
  function getModelDiscovery() {
@@ -83,24 +83,21 @@
83
83
  const modelSelect = getPanel()?.querySelector('[data-model]');
84
84
  const reasoningSelect = getPanel()?.querySelector('[data-reasoning]');
85
85
  const speedSelect = getPanel()?.querySelector('[data-speed]');
86
- let changedInput = null;
87
-
88
- if (choice.dataset.reasoningChoice && reasoningSelect) {
89
- reasoningSelect.value = choice.dataset.reasoningChoice;
90
- changedInput = reasoningSelect;
91
- } else if (choice.dataset.modelChoice && modelSelect) {
92
- modelSelect.value = choice.dataset.modelChoice;
93
- changedInput = modelSelect;
86
+ const changedInput = choice.dataset.reasoningChoice ? reasoningSelect
87
+ : choice.dataset.modelChoice ? modelSelect : choice.dataset.speedChoice ? speedSelect : null;
88
+ const value = choice.dataset.reasoningChoice || choice.dataset.modelChoice || choice.dataset.speedChoice;
89
+ const option = Array.from(changedInput?.options || []).find(item => item.value === value);
90
+ if (!changedInput || changedInput.disabled || !option || option.disabled) return;
91
+
92
+ changedInput.value = value;
93
+ if (changedInput === modelSelect) {
94
94
  renderReasoningOptions(getRenderedModelEntries());
95
95
  renderSpeedOptions(getRenderedModelEntries());
96
- } else if (choice.dataset.speedChoice && speedSelect) {
97
- speedSelect.value = choice.dataset.speedChoice;
98
- changedInput = speedSelect;
99
96
  }
100
97
 
101
98
  renderModelConfigChoices();
102
99
  updateModelDisplay();
103
- await persistPanelInputs(changedInput ? { type: 'change', target: changedInput } : undefined);
100
+ await persistPanelInputs({ type: 'change', target: changedInput });
104
101
  }
105
102
  async function loadModelOptions(providerSelection, options = {}) {
106
103
  const generation = ++modelLoadGeneration;
@@ -136,7 +133,9 @@
136
133
  }
137
134
  const sourceModels = hasDiscoveredModels ? response.result.models : fallbackModels;
138
135
  const preserveBuiltin = (selection?.providerId || 'builtin') === 'builtin' && (getState()?.providerId || 'builtin') === 'builtin';
139
- const retainedSelectedModel = retainSelectedModel(sourceModels, currentSelectedModel, { preserveUnavailable: preserveBuiltin });
136
+ const retainedSelectedModel = retainSelectedModel(sourceModels, currentSelectedModel, { preserveUnavailable: preserveBuiltin })
137
+ || (responseProviderId && responseProviderId !== 'builtin'
138
+ ? retainSelectedModel(sourceModels, response.result?.defaultModelId) : '');
140
139
  const normalized = modelCatalog.normalizeDiscoveredModels({ models: sourceModels, selectedModel: retainedSelectedModel });
141
140
  if (normalized.usedFallback && response.result?.providerId && response.result.providerId !== 'builtin') {
142
141
  applyUnavailableModelOptions({ code: 'provider_model_catalog_invalid', message: 'The active provider returned no usable models.' });
@@ -238,6 +237,13 @@
238
237
  }
239
238
 
240
239
  const selectedId = normalizeModelOptionId(selectedModel);
240
+ if (options.provisionalSelected === true) {
241
+ models = [...(Array.isArray(models) ? models : [])];
242
+ if (selectedId && !models.some(model => normalizeModelOptionId(model?.id) === selectedId)) {
243
+ models.push({ id: selectedId, label: selectedId, unverified: true });
244
+ }
245
+ models = Support.preserveSelectedCapabilities(models, selectedId, getState(), true);
246
+ }
241
247
  modelSelect.textContent = '';
242
248
  modelSelect.disabled = false;
243
249
  let renderedSelected = false;
@@ -373,7 +379,8 @@
373
379
  list.append(createModelConfigChoice({
374
380
  value: option.value,
375
381
  label: formatReasoningEffortLabel(option.value, reasoningSelect.dataset.presentation),
376
- datasetName: 'reasoningChoice'
382
+ datasetName: 'reasoningChoice',
383
+ disabled: reasoningSelect.disabled || option.disabled
377
384
  }));
378
385
  }
379
386
  }
@@ -390,7 +397,8 @@
390
397
  const choice = createModelConfigChoice({
391
398
  value: option.value,
392
399
  label: option.textContent || option.value,
393
- datasetName: 'modelChoice'
400
+ datasetName: 'modelChoice',
401
+ disabled: modelSelect.disabled || option.disabled
394
402
  });
395
403
  choice.title = option.textContent || option.value;
396
404
  list.append(choice);
@@ -408,16 +416,17 @@
408
416
  const choice = createModelConfigChoice({
409
417
  value: option.value,
410
418
  label: formatSpeedTierLabel(option.value),
411
- datasetName: 'speedChoice'
419
+ datasetName: 'speedChoice',
420
+ disabled: speedSelect.disabled || option.disabled
412
421
  });
413
- choice.disabled = speedSelect.disabled && speedSelect.options.length <= 1;
414
422
  list.append(choice);
415
423
  }
416
424
  }
417
425
 
418
- function createModelConfigChoice({ value, label, datasetName }) {
426
+ function createModelConfigChoice({ value, label, datasetName, disabled = false }) {
419
427
  const button = document.createElement('button');
420
428
  button.type = 'button';
429
+ button.disabled = disabled;
421
430
  button.className = 'codex-model-config-choice';
422
431
  button.dataset[datasetName] = value;
423
432
  button.setAttribute('role', 'menuitemradio');
@@ -525,6 +534,7 @@
525
534
  readSelectedSpeedInput() === 'fast' ? tx('Fast mode', '快速模式') : tx('Standard speed', '标准速度')
526
535
  ].filter(Boolean).join(' · ');
527
536
  }
537
+ deps.onSelectionRendered?.();
528
538
  }
529
539
 
530
540
  return {
@@ -37,8 +37,10 @@
37
37
  try {
38
38
  compatibilityGate = await ensureForMethod(payload?.method, context);
39
39
  if (!compatibilityGate.ok) return compatibilityGate.response;
40
+ // Catalog reads belong to the selected session, not a retained cancelled run.
41
+ const independentCatalog = payload?.method === 'codex.models' || payload?.method === 'codex.providers.list';
40
42
  // Post-Undo cache bookkeeping must survive the ended run's cancellation.
41
- if (gatedMethods.has(payload?.method) && payload.method !== 'mirror.invalidate') {
43
+ if (gatedMethods.has(payload?.method) && payload.method !== 'mirror.invalidate' && !independentCatalog) {
42
44
  options.throwIfCancellationRequested?.();
43
45
  }
44
46
  context.assertCurrent?.();
@@ -88,7 +90,7 @@
88
90
  requiredVersion: compatibility.requiredVersion || Compatibility?.BUILD_TARGET_VERSION || '',
89
91
  releaseUrl: compatibility.releaseUrl || ''
90
92
  };
91
- notifyBlocked(error, compatibility);
93
+ notifyBlocked(error, compatibility, method);
92
94
  return { ok: false, compatibility, response: { ok: false, error } };
93
95
  }
94
96
 
@@ -103,7 +105,7 @@
103
105
  };
104
106
  }
105
107
 
106
- function notifyBlocked(error, compatibility = {}) {
108
+ function notifyBlocked(error, compatibility = {}, method) {
107
109
  const message = error?.message || String(error || '');
108
110
  if (options.getCurrentRunView?.()) {
109
111
  options.appendRunEvent?.({ title: message, status: 'failed' });
@@ -111,7 +113,17 @@
111
113
  return;
112
114
  }
113
115
  showUpdateGuidance(compatibility);
114
- options.showPluginToast?.(message, { status: 'failed', sticky: true });
116
+ const missing = compatibility.status === 'native_missing';
117
+ options.showPluginToast?.(missing ? tr('nativeCompatibilityMissingGuidance') : message, {
118
+ status: 'failed',
119
+ sticky: true,
120
+ ...(missing ? {
121
+ title: tr('nativeCompatibilityMissingTitle'),
122
+ groupKey: 'native-missing',
123
+ groupItem: method || 'native request',
124
+ groupSummary: count => tr('nativeCompatibilityUnavailableFeatures', { count })
125
+ } : {})
126
+ });
115
127
  }
116
128
 
117
129
  async function refreshBadge() {
@@ -137,7 +149,9 @@
137
149
  options.setDiagnosticsHealth?.(classification === 'update-available' ? 'warn' : 'fail');
138
150
  options.setBadge?.(renderer.headerEl, {
139
151
  type: 'update',
140
- tooltip: tx('Native host update available', 'Native host 可更新'),
152
+ tooltip: compatibility.status === 'native_unhealthy'
153
+ ? tx('Codex Unavailable', 'Codex 不可用')
154
+ : tx('Native host update available', 'Native host 可更新'),
141
155
  onClick: () => showUpdateGuidance(compatibility)
142
156
  });
143
157
  if (!compatibility?.native?.version) maybePromptFirstRunSetup();
@@ -181,7 +195,9 @@
181
195
  || compatibility.nativeVersion
182
196
  || native.version
183
197
  || tx('missing', '未安装');
184
- const command = resolveInstallCommand(compatibility);
198
+ const codexUnavailable = compatibility.status === 'native_unhealthy';
199
+ const resolvedCommand = resolveInstallCommand(compatibility);
200
+ const command = codexUnavailable ? '' : resolvedCommand;
185
201
  const releaseUrl = compatibility.releaseUrl || '';
186
202
  const overlay = options.document.createElement('div');
187
203
  overlay.className = 'codex-plugin-confirm';
@@ -191,20 +207,40 @@
191
207
  const nativeMissing = !native.version;
192
208
  overlay.setAttribute(
193
209
  'aria-label',
194
- nativeMissing
210
+ codexUnavailable
211
+ ? tx('Codex Unavailable', 'Codex 不可用')
212
+ : nativeMissing
195
213
  ? tx('Set up the native host', '安装 Native host')
196
214
  : tx('Native host update available', 'Native host 可更新')
197
215
  );
198
216
  const card = options.document.createElement('section');
199
217
  card.className = 'codex-plugin-confirm-card';
218
+ const head = options.document.createElement('div');
219
+ head.className = 'codex-plugin-confirm-head';
220
+ const icon = options.document.createElement('img');
221
+ icon.className = 'codex-plugin-confirm-icon';
222
+ icon.alt = '';
223
+ icon.setAttribute('aria-hidden', 'true');
224
+ icon.src = options.getIconUrl?.() || '';
225
+ const titleWrap = options.document.createElement('div');
226
+ const brand = options.document.createElement('div');
227
+ brand.className = 'codex-plugin-confirm-brand';
228
+ brand.textContent = 'Codex Overleaf Link';
200
229
  const title = options.document.createElement('div');
201
230
  title.className = 'codex-plugin-confirm-title';
202
- title.textContent = nativeMissing
231
+ title.textContent = codexUnavailable
232
+ ? tx('Codex Unavailable', 'Codex 不可用')
233
+ : nativeMissing
203
234
  ? tx('One step left: install the native host', '还差一步:安装 Native host')
204
235
  : tx('Native host update available', 'Native host 可更新');
236
+ titleWrap.append(brand, title);
237
+ head.append(icon, titleWrap);
205
238
  const body = options.document.createElement('div');
206
239
  body.className = 'codex-plugin-confirm-body';
207
- body.textContent = [
240
+ body.textContent = codexUnavailable ? [
241
+ tx('The extension reached the native host, but local Codex was not found, so tasks cannot start.', '插件已经连上 native host,但没有找到本机 Codex,所以不能启动任务。'),
242
+ tx('Make sure codex works in Terminal, then reload the extension.', '请确认终端里可以运行 codex,然后重新加载扩展。')
243
+ ].join('\n\n') : [
208
244
  nativeMissing
209
245
  ? tx(
210
246
  `Extension v${extensionVersion} is ready; Codex still needs its local bridge to read and edit this project.`,
@@ -222,20 +258,16 @@
222
258
  ? tx(' Use PowerShell.', ' 请使用 PowerShell。') : '')
223
259
  ].join('\n\n');
224
260
  const commandCode = options.document.createElement('code');
261
+ commandCode.className = 'codex-native-guidance-command';
225
262
  commandCode.textContent = command;
226
- Object.assign(commandCode.style, {
227
- display: 'block',
228
- whiteSpace: 'pre-wrap',
229
- marginTop: '0.75rem'
230
- });
231
- body.append(commandCode);
232
- if (releaseUrl) {
263
+ if (!codexUnavailable) body.append(commandCode);
264
+ if (!codexUnavailable && releaseUrl) {
233
265
  const link = options.document.createElement('a');
266
+ link.className = 'codex-native-guidance-link';
234
267
  link.href = releaseUrl;
235
268
  link.target = '_blank';
236
269
  link.rel = 'noopener noreferrer';
237
270
  link.textContent = tx('Open GitHub Release', '打开 GitHub Release');
238
- Object.assign(link.style, { display: 'block', marginTop: '0.75rem' });
239
271
  body.append(link);
240
272
  }
241
273
  const actions = options.document.createElement('div');
@@ -255,8 +287,9 @@
255
287
  close.className = 'codex-plugin-confirm-cancel';
256
288
  close.textContent = tx('Close', '关闭');
257
289
  close.addEventListener('click', () => overlay.remove());
258
- actions.append(close, copy);
259
- card.append(title, body, actions);
290
+ actions.append(close);
291
+ if (!codexUnavailable) actions.append(copy);
292
+ card.append(head, body, actions);
260
293
  overlay.append(card);
261
294
  overlay.addEventListener('click', event => {
262
295
  if (event.target === overlay) overlay.remove();
@@ -895,7 +895,7 @@
895
895
  appendLog(tx(`Cannot continue: ${formatProjectSnapshotWarning(warning)}`, `无法继续:${formatProjectSnapshotWarning(warning)}`));
896
896
  }
897
897
  appendCompletionReport({
898
- conclusion: tr('warmMirrorStaleBlockedConclusion'),
898
+ conclusion: tr('warmMirrorStaleBlockedConclusion'), conclusionKey: 'warm_mirror_stale',
899
899
  status: 'blocked',
900
900
  operations: [],
901
901
  applyResults: [],
@@ -6,25 +6,26 @@
6
6
  'use strict';
7
7
 
8
8
  const PAGE_WORLD_SCRIPTS = Object.freeze([
9
- ['src/shared/reviewing.js', 'codex-overleaf-reviewing-script'],
9
+ ['src/shared/reviewing.js', 'codex-overleaf-reviewing-script', true],
10
10
  ['src/shared/projectFiles.js', 'codex-overleaf-project-files-script'],
11
11
  ['src/shared/compatibility.js', 'codex-overleaf-compatibility-page-script'],
12
12
  ['src/shared/pageRpcContract.js', 'codex-overleaf-page-rpc-contract-script', true],
13
- ['src/shared/staleGuard.js', 'codex-overleaf-stale-guard-script'],
13
+ ['src/shared/staleGuard.js', 'codex-overleaf-stale-guard-script', true],
14
+ ['src/shared/otText.js', 'codex-overleaf-ot-text-script'],
14
15
  ['src/shared/compileAdapter.js', 'codex-overleaf-compile-adapter-script'],
15
16
  ['src/shared/sensitiveScan.js', 'codex-overleaf-sensitive-scan-script'],
16
17
  ['src/shared/auditRecords.js', 'codex-overleaf-audit-records-script'],
17
18
  ['src/page/saveState.js', 'codex-overleaf-save-state-script', true],
18
- ['src/page/overleafCapabilities.js', 'codex-overleaf-capabilities-script'],
19
+ ['src/page/overleafCapabilities.js', 'codex-overleaf-capabilities-script', true],
19
20
  ['src/page/compileBridge.js', 'codex-overleaf-compile-bridge-script'],
20
- ['src/page/overleafEditor.js', 'codex-overleaf-editor-script'],
21
- ['src/page/overleafProjectSnapshot.js', 'codex-overleaf-project-snapshot-script'],
21
+ ['src/page/overleafEditor.js', 'codex-overleaf-editor-script', true],
22
+ ['src/page/overleafProjectSnapshot.js', 'codex-overleaf-project-snapshot-script', true],
22
23
  ['src/page/pageBridgeCapability.js', 'codex-overleaf-page-bridge-capability-script'],
23
24
  ['src/page/treeOperations.js', 'codex-overleaf-tree-operations-script', true],
24
25
  ['src/page/ownedDirectoryCleanup.js', 'codex-overleaf-owned-directory-cleanup-script', true],
25
26
  ['src/page/textFileCreator.js', 'codex-overleaf-text-file-creator-script', true],
26
27
  ['src/page/binaryAssetUploader.js', 'codex-overleaf-binary-asset-uploader-script', true],
27
- ['src/page/snapshotRouter.js', 'codex-overleaf-snapshot-router-script'],
28
+ ['src/page/snapshotRouter.js', 'codex-overleaf-snapshot-router-script', true],
28
29
  ['src/page/referenceProjects.js', 'codex-overleaf-reference-projects-script', true],
29
30
  ['src/page/fileMover.js', 'codex-overleaf-file-mover-script', true],
30
31
  ['src/page/writeGuard.js', 'codex-overleaf-write-guard-script', true],
@@ -39,7 +40,6 @@
39
40
  ['src/pageBridge.js', 'codex-overleaf-page-bridge-script', true]
40
41
  ]);
41
42
  const OPTIONAL_OT_SCRIPTS = Object.freeze([
42
- ['src/shared/otText.js', 'codex-overleaf-ot-text-script'],
43
43
  ['src/page/overleafRealtimeObserver.js', 'codex-overleaf-realtime-observer-script']
44
44
  ]);
45
45
 
@@ -181,6 +181,7 @@
181
181
  // the failed task usually needs a tweak before it is worth resending.
182
182
  function refillComposerForRetry(run, textOverride) {
183
183
  const input = getPanel()?.querySelector('[data-task]');
184
+ if (input?.readOnly) return;
184
185
  const task = typeof textOverride === 'string' && textOverride.trim()
185
186
  ? textOverride
186
187
  : String(run?.task || '');
@@ -188,6 +189,10 @@
188
189
  showPluginToast(tx('Nothing to refill: the original task text is unavailable.', '无法回填:原任务文本不可用。'));
189
190
  return;
190
191
  }
192
+ if (input.value.trim() && input.value.trim() !== task.trim()) {
193
+ showPluginToast(tx('Kept your draft. Clear the composer, then try again to refill this task.', '已保留你的草稿。清空输入框后再试一次,即可回填这条任务。'));
194
+ return;
195
+ }
191
196
  prepareRetryReplacement?.(run || null, task);
192
197
  input.value = task;
193
198
  getState().task = task;
@@ -310,7 +315,9 @@
310
315
  // Settings history card, expanded and loaded.
311
316
  function openChangeHistory() {
312
317
  openCustomInstructionsSettings();
313
- const card = getSettingsPanelInstance()?.container?.querySelector('[data-history-card]');
318
+ // The workbench shows one page at a time; switch to the page holding the card.
319
+ const card = getSettingsPanelInstance()?.revealGroup?.('history')
320
+ || getSettingsPanelInstance()?.container?.querySelector('[data-history-card]');
314
321
  if (card) {
315
322
  if (!card.open) {
316
323
  card.open = true;
@@ -322,7 +329,8 @@
322
329
 
323
330
  function openStorageSettings() {
324
331
  openCustomInstructionsSettings();
325
- const card = getPanel()?.querySelector('[data-storage-card]');
332
+ const card = getSettingsPanelInstance()?.revealGroup?.('storage')
333
+ || getPanel()?.querySelector('[data-storage-card]');
326
334
  if (card) {
327
335
  card.open = true;
328
336
  card.scrollIntoView({ block: 'center' });