codex-overleaf-link 2.5.0 → 2.6.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 (189) hide show
  1. package/README.md +155 -392
  2. package/README.zh-CN.md +154 -393
  3. package/extension/bootstrap/background.js +52 -9
  4. package/extension/bootstrap/manifest.template.json +1 -1
  5. package/extension/bootstrap/popup.html +1 -0
  6. package/extension/bootstrap/popupFallback.js +55 -0
  7. package/extension/bootstrap/updateStatus.js +1 -0
  8. package/extension/src/background.js +283 -55
  9. package/extension/src/backgroundBootstrapMigration.js +181 -0
  10. package/extension/src/backgroundSharedSessions.js +129 -16
  11. package/extension/src/backgroundUpdateCoordinator.js +242 -29
  12. package/extension/src/backgroundUpdateSafety.js +173 -0
  13. package/extension/src/content/activeTurnControl.js +171 -8
  14. package/extension/src/content/applyResultFormatters.js +46 -73
  15. package/extension/src/content/assetTransferBroker.js +72 -14
  16. package/extension/src/content/composerAttachments.js +104 -47
  17. package/extension/src/content/composerPanel.js +110 -12
  18. package/extension/src/content/contentRuntime.js +1555 -926
  19. package/extension/src/content/contextTray.js +8 -2
  20. package/extension/src/content/contextUsageView.js +267 -0
  21. package/extension/src/content/diagnosticsController.js +122 -96
  22. package/extension/src/content/diagnosticsExport.js +19 -5
  23. package/extension/src/content/diagnosticsPanel.js +48 -0
  24. package/extension/src/content/directoryCleanupRecovery.js +54 -0
  25. package/extension/src/content/generated/content.bundle.js +11077 -3412
  26. package/extension/src/content/generated/content.bundle.meta.json +14 -3
  27. package/extension/src/content/globalPreferencesController.js +1 -1
  28. package/extension/src/content/legacyGlobalRegistry.js +10 -0
  29. package/extension/src/content/localSkillsPanel.js +104 -14
  30. package/extension/src/content/markdownDomRenderer.js +5 -3
  31. package/extension/src/content/markdownText.js +47 -11
  32. package/extension/src/content/modelPicker.js +8 -4
  33. package/extension/src/content/nativeCompatibilityController.js +74 -30
  34. package/extension/src/content/otWarmMirror.js +5 -6
  35. package/extension/src/content/otWarmMirrorController.js +17 -20
  36. package/extension/src/content/pageBridgeClient.js +17 -4
  37. package/extension/src/content/panelMaintenance.js +46 -14
  38. package/extension/src/content/panelRenderer.js +57 -27
  39. package/extension/src/content/pendingInputView.js +1 -1
  40. package/extension/src/content/projectProviderSelection.js +13 -4
  41. package/extension/src/content/projectSessionCleanup.js +109 -62
  42. package/extension/src/content/projectSettingsCoordinator.js +67 -6
  43. package/extension/src/content/providerSettingsCoordinator.js +46 -8
  44. package/extension/src/content/providerSettingsDialog.js +73 -14
  45. package/extension/src/content/recentProjects.js +15 -8
  46. package/extension/src/content/runActivitySummary.js +160 -18
  47. package/extension/src/content/runCompletionMeta.js +532 -0
  48. package/extension/src/content/runController.js +39 -15
  49. package/extension/src/content/runFailureNotice.js +29 -13
  50. package/extension/src/content/runGuidanceController.js +72 -27
  51. package/extension/src/content/runGuidanceView.js +59 -10
  52. package/extension/src/content/runPresence.js +18 -8
  53. package/extension/src/content/runQueueScheduler.js +157 -47
  54. package/extension/src/content/runRecoveryActions.js +147 -0
  55. package/extension/src/content/runResultActions.js +28 -143
  56. package/extension/src/content/runReviewController.js +633 -0
  57. package/extension/src/content/runScrollLayout.js +21 -2
  58. package/extension/src/content/runTimelineView.js +367 -175
  59. package/extension/src/content/scopedPersistencePanelState.js +134 -20
  60. package/extension/src/content/selectionContextView.js +1 -1
  61. package/extension/src/content/sensitivePreflight.js +153 -0
  62. package/extension/src/content/sessionForkController.js +80 -17
  63. package/extension/src/content/sessionManager.js +50 -4
  64. package/extension/src/content/sessionMenuView.js +54 -18
  65. package/extension/src/content/sessionPersistence.js +25 -5
  66. package/extension/src/content/settingsPanel.js +22 -5
  67. package/extension/src/content/settingsWorkbench.js +132 -89
  68. package/extension/src/content/sharedSessionViewSync.js +49 -11
  69. package/extension/src/content/subagentActivityView.js +23 -7
  70. package/extension/src/content/updateIdle.js +4 -1
  71. package/extension/src/content/updateNotice.js +118 -12
  72. package/extension/src/content/updateSafetyReceiver.js +58 -0
  73. package/extension/src/content/writebackController.js +53 -4
  74. package/extension/src/content/writebackOrchestrator.js +116 -94
  75. package/extension/src/content/writingStyleSettings.js +35 -11
  76. package/extension/src/page/binaryAssetUploader.js +136 -14
  77. package/extension/src/page/compileBridge.js +193 -180
  78. package/extension/src/page/fileMover.js +256 -0
  79. package/extension/src/page/overleafCapabilities.js +4 -1
  80. package/extension/src/page/ownedDirectoryCleanup.js +94 -0
  81. package/extension/src/page/referenceProjects.js +1 -0
  82. package/extension/src/page/reviewContext.js +113 -0
  83. package/extension/src/page/saveState.js +6 -4
  84. package/extension/src/page/snapshotRouter.js +84 -39
  85. package/extension/src/page/textCoordinates.js +17 -1
  86. package/extension/src/page/textFileCreator.js +114 -26
  87. package/extension/src/page/trackedChangeCapture.js +12 -2
  88. package/extension/src/page/trackedChangeOwnership.js +19 -1
  89. package/extension/src/page/trackedChangeReplay.js +41 -21
  90. package/extension/src/page/trackedChangesLifecycle.js +217 -199
  91. package/extension/src/page/treeOperations.js +7 -10
  92. package/extension/src/page/updateSafetyProbe.js +41 -0
  93. package/extension/src/page/writebackReceiptJournal.js +2 -0
  94. package/extension/src/page/writebackRouter.js +103 -155
  95. package/extension/src/pageBridge.js +126 -69
  96. package/extension/src/popup.js +62 -10
  97. package/extension/src/shared/agentTranscript.js +373 -17
  98. package/extension/src/shared/auditRecords.js +4 -0
  99. package/extension/src/shared/compatibility.js +22 -6
  100. package/extension/src/shared/compileAdapter.js +129 -66
  101. package/extension/src/shared/composerAttachmentDraft.js +156 -0
  102. package/extension/src/shared/contextUsage.js +125 -0
  103. package/extension/src/shared/directoryCleanupProof.js +43 -0
  104. package/extension/src/shared/failureReasons.js +349 -4
  105. package/extension/src/shared/globalPreferences.js +30 -13
  106. package/extension/src/shared/governanceRules.js +4 -37
  107. package/extension/src/shared/i18n.js +590 -24
  108. package/extension/src/shared/lineReferences.js +38 -31
  109. package/extension/src/shared/managedUpdateProjection.js +2 -2
  110. package/extension/src/shared/nativeRequestIdentity.js +1 -0
  111. package/extension/src/shared/pageRpcContract.js +5 -4
  112. package/extension/src/shared/panelIcons.js +39 -0
  113. package/extension/src/shared/projectFiles.js +21 -44
  114. package/extension/src/shared/providerProfiles.js +5 -3
  115. package/extension/src/shared/reviewing.js +130 -1
  116. package/extension/src/shared/runActivityModel.js +70 -4
  117. package/extension/src/shared/runExecutionSnapshot.js +13 -0
  118. package/extension/src/shared/runInputQueue.js +48 -5
  119. package/extension/src/shared/selectionContext.js +12 -3
  120. package/extension/src/shared/sessionState.js +132 -28
  121. package/extension/src/shared/sessionTitle.js +14 -1
  122. package/extension/src/shared/settlementFacts.js +10 -2
  123. package/extension/src/shared/sharedSessionBridge.js +118 -27
  124. package/extension/src/shared/sharedSessionTransfer.js +140 -0
  125. package/extension/src/shared/staleGuard.js +166 -52
  126. package/extension/src/shared/storageDb.js +90 -26
  127. package/extension/src/shared/storageMigration.js +78 -112
  128. package/extension/src/shared/storageRunActions.js +267 -30
  129. package/extension/src/shared/subagentActivityModel.js +1 -1
  130. package/extension/src/shared/undoOperations.js +253 -32
  131. package/extension/src/shared/updateConsent.js +6 -4
  132. package/extension/src/shared/updateRuntimeIdentity.js +17 -3
  133. package/extension/src/shared/writebackIntent.js +16 -0
  134. package/extension/src/shared/writebackOperationEvidence.js +4 -15
  135. package/extension/src/shared/writebackSettlement.js +58 -40
  136. package/extension/styles/panel.css +1123 -493
  137. package/native-host/src/anthropicBridgeRequest.js +4 -2
  138. package/native-host/src/anthropicBridgeResponse.js +6 -2
  139. package/native-host/src/anthropicMessagesBridge.js +24 -16
  140. package/native-host/src/bootstrapMigration.js +296 -0
  141. package/native-host/src/bootstrapMigrationContract.js +101 -0
  142. package/native-host/src/bootstrapUpdate.js +269 -0
  143. package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
  144. package/native-host/src/chatBridgeRequest.js +60 -6
  145. package/native-host/src/chatBridgeResponse.js +20 -1
  146. package/native-host/src/chatCompletionsBridge.js +48 -15
  147. package/native-host/src/codexConfigPaths.js +106 -0
  148. package/native-host/src/codexHome.js +66 -73
  149. package/native-host/src/codexHomeScope.js +124 -0
  150. package/native-host/src/codexInheritedAuth.js +90 -0
  151. package/native-host/src/codexLocalSkills.js +301 -0
  152. package/native-host/src/codexPromptAssembly.js +114 -127
  153. package/native-host/src/codexProviderTest.js +24 -26
  154. package/native-host/src/codexSessionRunner.js +234 -198
  155. package/native-host/src/codexSessionTiming.js +18 -8
  156. package/native-host/src/codexSkillIsolation.js +170 -0
  157. package/native-host/src/codexThreadFork.js +19 -3
  158. package/native-host/src/index.js +19 -7
  159. package/native-host/src/managedInstall.js +110 -35
  160. package/native-host/src/managedInstallRecovery.js +140 -0
  161. package/native-host/src/managedRuntimeEvidence.js +57 -0
  162. package/native-host/src/mirrorWorkspace.js +92 -22
  163. package/native-host/src/nativeDoctor.js +15 -0
  164. package/native-host/src/nativeHelperLifecycle.js +21 -0
  165. package/native-host/src/nativeHostRegistration.js +33 -0
  166. package/native-host/src/nativeRunLifecycle.js +71 -0
  167. package/native-host/src/processStopBarrier.js +42 -7
  168. package/native-host/src/projectPathPlatform.js +30 -0
  169. package/native-host/src/providerContextError.js +20 -0
  170. package/native-host/src/providerHttp.js +80 -0
  171. package/native-host/src/providerProfile.js +4 -2
  172. package/native-host/src/providerReasoning.js +2 -7
  173. package/native-host/src/providerRuntime.js +1 -0
  174. package/native-host/src/providerStore.js +72 -14
  175. package/native-host/src/responsesPassthroughBridge.js +266 -52
  176. package/native-host/src/runtimeInstaller.js +21 -1
  177. package/native-host/src/selectionScope.js +36 -8
  178. package/native-host/src/subagentAdmission.js +26 -0
  179. package/native-host/src/subagentBroker.js +79 -66
  180. package/native-host/src/subagentTelemetry.js +19 -10
  181. package/native-host/src/subagentWorkspacePath.js +5 -2
  182. package/native-host/src/taskRunnerRuntime.js +92 -57
  183. package/native-host/src/textPatch.js +473 -377
  184. package/native-host/src/tomlStatements.js +140 -0
  185. package/native-host/src/updateManager.js +409 -46
  186. package/native-host/src/writingStyleRuntime.js +57 -29
  187. package/native-host/src/writingStyleStore.js +73 -0
  188. package/package.json +5 -8
  189. package/scripts/install-managed.mjs +1 -0
@@ -7,16 +7,23 @@
7
7
  if (!Profiles || !Dialog?.create) {
8
8
  throw new Error('Provider settings require explicit profile and dialog dependencies.');
9
9
  }
10
+ const statusTranslations = new Map();
11
+ const translate = options.tx || (english => english);
10
12
  const instance = {
11
13
  Profiles,
12
- tx: options.tx || ((english) => english),
14
+ tx: (english, chinese) => {
15
+ statusTranslations.set(english, [english, chinese]); statusTranslations.set(chinese, [english, chinese]);
16
+ return translate(english, chinese);
17
+ },
13
18
  sendBackgroundNative: options.sendBackgroundNative,
14
19
  getSettingsPanelInstance: options.getSettingsPanelInstance || (() => null),
15
20
  getSelectedModel: options.getSelectedModel || (() => ''),
16
21
  getSelectedProviderId: options.getSelectedProviderId || (() => 'builtin'),
22
+ getSelectedProviderRevision: options.getSelectedProviderRevision || (() => undefined),
17
23
  hasCurrentProject: options.hasCurrentProject || (() => true),
18
24
  setSelectedProviderId: options.setSelectedProviderId || (() => {}),
19
25
  confirmProviderSwitch: options.confirmProviderSwitch || (() => true),
26
+ assertProviderSwitchAllowed: options.assertProviderSwitchAllowed || (() => {}),
20
27
  clearSelectedModel: options.clearSelectedModel || (() => {}),
21
28
  refreshModelOptions: options.refreshModelOptions || (() => {}),
22
29
  persistInputs: options.persistInputs || (() => {}),
@@ -42,6 +49,7 @@
42
49
  instance.dialog = Dialog.create({
43
50
  document: options.document || document,
44
51
  tx: instance.tx,
52
+ translateStatusText: text => statusTranslations.has(text) ? translate(...statusTranslations.get(text)) : text,
45
53
  ProviderProfiles: Profiles,
46
54
  callbacks: {
47
55
  hasCurrentProject: instance.hasCurrentProject,
@@ -61,6 +69,7 @@
61
69
  open: () => open(instance),
62
70
  refreshSummary: () => refreshSummary(instance),
63
71
  renderSummary: () => updateSettingsSummary(instance),
72
+ refreshLocale: () => instance.dialog.refreshLocale?.(),
64
73
  ensureLoaded: () => ensureLoaded(instance),
65
74
  getRunSelection: providerId => instance.loaded
66
75
  ? Profiles.buildRunSelection(instance.catalog, providerId || instance.getSelectedProviderId())
@@ -195,15 +204,20 @@
195
204
  }
196
205
  }
197
206
  const changedProviderIds = Array.isArray(change.changedProviderIds) ? change.changedProviderIds : [];
198
- const providerConfigurationChanged = changedProviderIds.includes(requestedProviderId);
207
+ const selectedRevision = instance.getSelectedProviderRevision();
208
+ const providerConfigurationChanged = changedProviderIds.includes(requestedProviderId)
209
+ || (requestedProviderId === previousProviderId && selectedRevision !== undefined
210
+ && Number(selectedRevision) !== Number(instance.Profiles.getProviderById(catalog, requestedProviderId)?.revision || 0));
199
211
  if (!change.forceSessionRefresh && !projectProviderChanged && !providerConfigurationChanged) {
200
212
  return;
201
213
  }
214
+ if (projectProviderChanged || providerConfigurationChanged) instance.assertProviderSwitchAllowed();
202
215
  const loadResult = change.modelsPreloaded === true
203
216
  ? { stale: false, selectedModel: change.preloadedModelId || '' }
204
217
  : await instance.refreshModelOptions(
205
218
  instance.Profiles.buildRunSelection(catalog, requestedProviderId),
206
- { persist: false }
219
+ { persist: false, ...(projectProviderChanged || providerConfigurationChanged
220
+ ? { assertCurrent: instance.assertProviderSwitchAllowed } : {}) }
207
221
  );
208
222
  if (loadResult?.stale || loadResult?.error) {
209
223
  const error = createClientError(
@@ -223,6 +237,7 @@
223
237
 
224
238
  async function prepareProviderActivation(instance, providerId) {
225
239
  requireCurrentProject(instance);
240
+ instance.assertProviderSwitchAllowed();
226
241
  const previousProviderId = instance.getSelectedProviderId() || 'builtin';
227
242
  const provider = instance.Profiles.getProviderById(instance.catalog, providerId);
228
243
  if (providerId !== previousProviderId) {
@@ -234,9 +249,10 @@
234
249
  return { cancelled: true, previousProviderId };
235
250
  }
236
251
  }
252
+ instance.assertProviderSwitchAllowed();
237
253
  const result = await instance.refreshModelOptions(
238
254
  instance.Profiles.buildRunSelection(instance.catalog, providerId),
239
- { persist: false }
255
+ { persist: false, assertCurrent: instance.assertProviderSwitchAllowed }
240
256
  );
241
257
  if (result?.stale || result?.error) {
242
258
  if (providerId !== previousProviderId) {
@@ -252,6 +268,7 @@
252
268
  error.details = result?.error || {};
253
269
  throw error;
254
270
  }
271
+ instance.assertProviderSwitchAllowed();
255
272
  return {
256
273
  cancelled: false,
257
274
  previousProviderId,
@@ -316,7 +333,10 @@
316
333
  async function saveProvider(instance, context, action = {}) {
317
334
  instance.dialog.setBusy('saving', instance.tx('Saving provider…', '正在保存模型服务…'));
318
335
  try {
319
- if (action.activate === true) requireCurrentProject(instance);
336
+ if (action.activate === true) {
337
+ requireCurrentProject(instance);
338
+ instance.assertProviderSwitchAllowed();
339
+ }
320
340
  const result = await request(instance, 'codex.providers.upsert', {
321
341
  profileId: context.profileId,
322
342
  expectedRevision: context.expectedRevision,
@@ -347,8 +367,8 @@
347
367
  tone: fellBackToBuiltin ? 'warning' : 'success',
348
368
  title: fellBackToBuiltin
349
369
  ? instance.tx(
350
- 'Provider saved. Its endpoint changed, so Built-in Codex is active until the new endpoint is approved.',
351
- '模型服务已保存。由于端点发生变化,确认新端点前将使用内置 Codex。'
370
+ 'Provider saved. Its endpoint changed, so the Native Host default is Built-in Codex. The project provider is unchanged; approve the new endpoint before using it.',
371
+ '模型服务已保存。端点变更使 Native Host 默认项回退到内置 Codex,当前项目的服务选择保持不变;使用新端点前仍需确认披露。'
352
372
  )
353
373
  : action.activate
354
374
  ? instance.tx('Provider saved and activated.', '模型服务已保存并启用。')
@@ -376,7 +396,7 @@
376
396
  instance.dialog.setStatus({
377
397
  tone: 'success',
378
398
  title: result.activeProviderId === 'builtin'
379
- ? instance.tx('API key removed. Built-in Codex is now active.', 'API 密钥已删除,当前已切换到内置 Codex。')
399
+ ? instance.tx('API key removed. The Native Host default is Built-in Codex; the project provider is unchanged.', 'API 密钥已删除。Native Host 默认项为内置 Codex,当前项目的服务选择保持不变。')
380
400
  : instance.tx('Stored API key removed.', '已删除存储的 API 密钥。')
381
401
  });
382
402
  } catch (error) {
@@ -387,6 +407,7 @@
387
407
  async function deleteProvider(instance, context) {
388
408
  instance.dialog.setBusy('deleting', instance.tx('Deleting provider…', '正在删除模型服务…'));
389
409
  try {
410
+ if (context.profileId === instance.getSelectedProviderId()) instance.assertProviderSwitchAllowed();
390
411
  const result = await request(instance, 'codex.providers.delete', {
391
412
  providerId: context.profileId,
392
413
  expectedRevision: context.expectedRevision
@@ -665,6 +686,23 @@
665
686
  provider_revision_conflict: instance.tx('This provider changed in another tab. Reload it and retry.', '此模型服务已在其他标签页发生变化,请刷新后重试。'),
666
687
  provider_protocol_negotiation_required: instance.tx('The Auto protocol could not be negotiated for this model.', '无法为此模型自动协商 API 协议。')
667
688
  };
689
+ Object.assign(messages, {
690
+ provider_store_busy: instance.tx('Provider settings are being updated by another Native Host. Retry shortly.', '其他 Native Host 正在更新模型服务设置,请稍后重试。'),
691
+ provider_store_too_large: instance.tx('Provider settings exceed the storage limit. Reduce the configuration and retry.', '模型服务设置超过存储上限,请精简配置后重试。'),
692
+ provider_store_unavailable: instance.tx('Provider settings could not be read or saved. Check the local service and retry.', '无法读取或保存模型服务设置,请检查本机服务后重试。'),
693
+ provider_store_corrupt: instance.tx('Stored provider settings are inconsistent or invalid. Repair the local configuration before retrying.', '已存储的模型服务设置不一致或无效,请修复本机配置后重试。'),
694
+ provider_not_found: instance.tx('The selected provider no longer exists. Refresh the provider list.', '所选模型服务已不存在,请刷新服务列表。'),
695
+ provider_dns_failed: instance.tx('The provider hostname could not be resolved. Check the endpoint and network.', '无法解析模型服务域名,请检查端点和网络。'),
696
+ provider_tls_failed: instance.tx('A secure connection to the provider could not be established. Check the endpoint certificate.', '无法与模型服务建立安全连接,请检查端点证书。'),
697
+ provider_rate_limited: instance.tx('The provider is rate limiting requests. Retry later.', '模型服务已限制请求频率,请稍后重试。'),
698
+ provider_model_not_configured: instance.tx('The selected model is not configured for this provider.', '当前模型服务未配置所选模型。'),
699
+ provider_selection_unavailable: instance.tx('The selected provider or model is unavailable. Review the project selection.', '所选模型服务或模型不可用,请检查当前项目的选择。'),
700
+ provider_disclosure_required: instance.tx('Confirm the provider endpoint before activation.', '启用前请确认模型服务端点。'),
701
+ provider_secret_invalid: instance.tx('The API key change could not be accepted. Review the key settings.', '无法接受此次 API 密钥变更,请检查密钥设置。'),
702
+ provider_model_catalog_unavailable: instance.tx('The provider model catalog could not be loaded. Retry shortly.', '无法加载模型服务的模型目录,请稍后重试。'),
703
+ native_unavailable: instance.tx('The local service is unavailable. Check the Native Host connection.', '本机服务不可用,请检查 Native Host 连接。'),
704
+ provider_request_failed: instance.tx('The provider operation failed. Review the settings and retry.', '模型服务操作失败,请检查设置后重试。')
705
+ });
668
706
  return messages[code] || error?.message || instance.tx('Provider operation failed.', '模型服务操作失败。');
669
707
  }
670
708
 
@@ -47,10 +47,16 @@
47
47
  if (!Profiles) {
48
48
  throw new Error('Provider settings dialog requires provider profiles.');
49
49
  }
50
+ const translations = new Map();
51
+ const translate = options.tx || (english => english);
50
52
  const instance = {
51
53
  Profiles,
52
54
  document: options.document || document,
53
- tx: options.tx || ((english) => english),
55
+ tx: (english, chinese) => {
56
+ translations.set(english, [english, chinese]); translations.set(chinese, [english, chinese]);
57
+ return translate(english, chinese);
58
+ },
59
+ translateStatusText: text => translations.has(text) ? translate(...translations.get(text)) : options.translateStatusText?.(text) || text,
54
60
  callbacks: options.callbacks || {},
55
61
  catalog: Profiles.normalizeCatalog({}),
56
62
  selectedId: 'builtin',
@@ -83,6 +89,7 @@
83
89
  setVerification: verification => setVerification(instance, verification),
84
90
  setVerificationFailure: failure => setVerificationFailure(instance, failure),
85
91
  setTestProgress: progress => setTestProgress(instance, progress),
92
+ refreshLocale: () => refreshLocale(instance),
86
93
  hasUnsavedChanges: () => instance.dirty || Boolean(instance.modelEditor || instance.confirmation),
87
94
  isOpen: () => Boolean(instance.root && !instance.root.hidden),
88
95
  destroy: () => destroy(instance),
@@ -245,6 +252,7 @@
245
252
 
246
253
  function render(instance) {
247
254
  const tx = instance.tx;
255
+ instance.root.querySelector('[data-provider-action="close"]')?.setAttribute('aria-label', tx('Close', '关闭'));
248
256
  const reload = instance.root.querySelector('[data-provider-action="reload"]');
249
257
  if (reload) {
250
258
  reload.hidden = !instance.embedded;
@@ -292,6 +300,45 @@
292
300
  + escapeHtml(tx('Add provider', '添加模型服务')) + '</button>';
293
301
  }
294
302
 
303
+ // Render a detached reference, then change only owned copy in the live DOM.
304
+ // Inputs, focus, selection, modal ownership, listeners and secret values stay put.
305
+ function refreshLocale(instance) {
306
+ if (!instance.root || instance.root.hidden) return;
307
+ const reference = { ...instance, root: instance.root.cloneNode(true) };
308
+ if (instance.root.querySelector('[data-provider-field="name"]')) reference.draft = readContext(instance).draft;
309
+ render(reference);
310
+ if (instance.modelEditor) openModelEditor(reference, instance.modelEditor.id, { show: false });
311
+ patchLocaleCopy(instance.root, reference.root);
312
+ if (instance.lastStatus) setStatus(instance, instance.lastStatus);
313
+ const confirmation = instance.root.querySelector('[data-provider-confirmation]');
314
+ if (instance.confirmation && confirmation) {
315
+ for (const node of confirmation.querySelectorAll('h3, p, button')) node.textContent = instance.translateStatusText(node.textContent);
316
+ }
317
+ const secret = instance.root.querySelector('[data-provider-field="apiKey"]');
318
+ const toggle = instance.root.querySelector('[data-provider-action="toggle-secret"]');
319
+ if (toggle) toggle.textContent = instance.tx(secret?.type === 'text' ? 'Hide' : 'Show', secret?.type === 'text' ? '隐藏' : '显示');
320
+ refreshTestState(instance); updateDisclosureText(instance);
321
+ if (instance.modelEditor) {
322
+ updateModelReasoningSummary(instance);
323
+ const error = instance.root.querySelector('[data-model-error]');
324
+ if (error && instance.modelEditor.error) error.textContent = formatModelEditorError(instance, instance.modelEditor.error);
325
+ }
326
+ }
327
+
328
+ function patchLocaleCopy(current, reference) {
329
+ if (!current || !reference || current.nodeType !== reference.nodeType) return;
330
+ if (current.nodeType === 3) { if (current.nodeValue !== reference.nodeValue) current.nodeValue = reference.nodeValue; return; }
331
+ if (current.nodeType !== 1 || current.tagName !== reference.tagName) return;
332
+ for (const attr of ['title', 'aria-label', 'placeholder']) {
333
+ const next = reference.getAttribute(attr);
334
+ if (next !== null && current.getAttribute(attr) !== next) current.setAttribute(attr, next);
335
+ }
336
+ if (['INPUT', 'TEXTAREA'].includes(current.tagName)) return;
337
+ const actual = current.childNodes, expected = reference.childNodes;
338
+ if (actual.length !== expected.length) return;
339
+ for (let index = 0; index < actual.length; index++) patchLocaleCopy(actual[index], expected[index]);
340
+ }
341
+
295
342
  function renderDetail(instance) {
296
343
  const detail = instance.root.querySelector('[data-provider-detail]');
297
344
  if (instance.templatePicker) { renderTemplatePicker(instance, detail); return; }
@@ -303,6 +350,10 @@
303
350
  const tx = instance.tx;
304
351
  const draft = instance.draft || provider;
305
352
  const active = provider.id && provider.id === getCurrentProjectProviderId(instance);
353
+ const profileStatus = !provider.id
354
+ ? tx('Unsaved provider profile', '尚未保存的模型服务配置')
355
+ : active ? tx('Used by new runs in this project', '当前项目的新任务将使用此服务')
356
+ : tx('Saved provider profile', '已保存的模型服务配置');
306
357
  const acceptedHost = getEndpointHost(draft.baseUrl);
307
358
  const secretSavedAt = formatSecretSavedAt(instance, provider.secretUpdatedAt);
308
359
  const disclosureSatisfied = Boolean(
@@ -314,7 +365,7 @@
314
365
  <div class="codex-provider-detail-titleline">
315
366
  <div>
316
367
  <h3>${escapeHtml(draft.name || tx('Custom provider', '自定义模型服务'))}</h3>
317
- <p>${escapeHtml(active ? tx('Used by new runs in this project', '当前项目的新任务将使用此服务') : tx('Saved provider profile', '已保存的模型服务配置'))}</p>
368
+ <p>${escapeHtml(profileStatus)}</p>
318
369
  </div>
319
370
  ${active ? `<span class="codex-provider-active-badge">${escapeHtml(tx('Current project', '当前项目'))}</span>` : ''}
320
371
  </div>
@@ -444,12 +495,12 @@
444
495
  <small>${escapeHtml(tx('Vendor-specific fields only; core model, messages, tools, and stream fields are protected.', '仅填写服务商特有字段;model、messages、tools 和 stream 等核心字段不可覆盖。'))}</small>
445
496
  </label>
446
497
  <label class="codex-provider-field">
447
- <span>Anthropic version</span>
498
+ <span>${escapeHtml(tx('Anthropic version', 'Anthropic 版本'))}</span>
448
499
  <input type="text" data-provider-field="anthropicVersion" value="${escapeAttr(draft.anthropicVersion || '2023-06-01')}" spellcheck="false">
449
500
  </label>
450
501
  <label class="codex-provider-field">
451
502
  <span>Anthropic beta</span>
452
- <input type="text" data-provider-field="anthropicBeta" value="${escapeAttr(draft.anthropicBeta || '')}" placeholder="optional" spellcheck="false">
503
+ <input type="text" data-provider-field="anthropicBeta" value="${escapeAttr(draft.anthropicBeta || '')}" placeholder="${escapeAttr(tx('optional', '可选'))}" spellcheck="false">
453
504
  </label>
454
505
  <label class="codex-provider-field">
455
506
  <span>${escapeHtml(tx('Anthropic thinking mode', 'Anthropic 思考模式'))}</span>
@@ -910,6 +961,7 @@
910
961
  ,anthropicPromptCaching: Boolean(get('anthropicPromptCaching')?.checked)
911
962
  ,impersonateClaudeCode: Boolean(get('impersonateClaudeCode')?.checked)
912
963
  ,maxOutputTokens: Number(get('maxOutputTokens')?.value || 65536)
964
+ ,outputBudgetVersion: 1
913
965
  };
914
966
  const apiKey = String(get('apiKey')?.value || '');
915
967
  const secretMutation = instance.secretAction === 'replace' && apiKey
@@ -992,9 +1044,10 @@
992
1044
  }
993
1045
 
994
1046
  function setStatus(instance, status = {}) {
1047
+ instance.lastStatus = status;
995
1048
  const element = instance.root.querySelector('[data-provider-status]');
996
1049
  element.dataset.tone = status.tone || '';
997
- element.textContent = [status.title, status.detail].filter(Boolean).join(' ');
1050
+ element.textContent = [status.title, status.detail].filter(Boolean).map(text => instance.translateStatusText?.(text) || text).join(' ');
998
1051
  }
999
1052
 
1000
1053
  function setVerification(instance, verification = {}) {
@@ -1243,7 +1296,8 @@
1243
1296
 
1244
1297
  function renderTemplatePicker(instance, detail) {
1245
1298
  const tx = instance.tx;
1246
- detail.innerHTML = '<div class="codex-provider-template-head"><button type="button" class="codex-provider-inline-button" data-provider-action="templates-back">'
1299
+ detail.innerHTML = '<div class="codex-provider-template-head"><button type="button" class="codex-provider-inline-button codex-provider-back-button" data-provider-action="templates-back">'
1300
+ + '<svg viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="m9.8 3.6-4.4 4.4 4.4 4.4"/></svg>'
1247
1301
  + escapeHtml(tx('Back', '返回')) + '</button><h3>' + escapeHtml(tx('Add a connection', '添加连接')) + '</h3></div>'
1248
1302
  + '<p class="codex-provider-help">' + escapeHtml(tx(
1249
1303
  'Service shortcuts default to Anthropic Messages. Chat Completions remains available; preset addresses follow the selected protocol. Add the matching API key and model IDs separately.',
@@ -1379,7 +1433,7 @@
1379
1433
  }
1380
1434
  }
1381
1435
 
1382
- function openModelEditor(instance, id) {
1436
+ function openModelEditor(instance, id, options = {}) {
1383
1437
  const provider = readContext(instance).draft;
1384
1438
  const existing = provider.models.find(model => model.id === id);
1385
1439
  if (id && !existing) return;
@@ -1450,7 +1504,7 @@
1450
1504
  + escapeHtml(tx('Cancel', '取消')) + '</button><button type="button" class="codex-provider-primary-button" data-provider-model-action="apply-editor">'
1451
1505
  + escapeHtml(id ? tx('Apply', '应用') : tx('Add model', '添加模型')) + '</button></footer>';
1452
1506
  updateModelReasoningSummary(instance);
1453
- if (modal.showModal) modal.showModal(); else modal.setAttribute('open', '');
1507
+ if (options.show !== false) { if (modal.showModal) modal.showModal(); else modal.setAttribute('open', ''); }
1454
1508
  }
1455
1509
 
1456
1510
  function updateModelReasoningSummary(instance) {
@@ -1499,16 +1553,21 @@
1499
1553
  closeModelEditor(instance, false);
1500
1554
  if (!unchanged) replaceModelDraft(instance, next);
1501
1555
  } catch (error) {
1502
- const translated = {
1503
- id: instance.tx(error.message, '请检查模型 ID:必填、最多 200 字符、不可重复,且单个服务最多 32 个模型。'),
1504
- contextWindow: instance.tx(error.message, '上下文窗口必须为 8192 至 4000000 之间的整数。'),
1505
- maxOutputTokens: instance.tx(error.message, '输出上限须为 256 至 65536 之间的整数,并小于上下文窗口。')
1506
- };
1507
- modal.querySelector('[data-model-error]').textContent = translated[error.field] || error.message;
1556
+ editor.error = { field: error.field, message: error.message };
1557
+ modal.querySelector('[data-model-error]').textContent = formatModelEditorError(instance, editor.error);
1508
1558
  if (error.field) get(error.field)?.focus();
1509
1559
  }
1510
1560
  }
1511
1561
 
1562
+ function formatModelEditorError(instance, error) {
1563
+ const chinese = {
1564
+ id: '请检查模型 ID:必填、最多 200 字符、不可重复,且单个服务最多 32 个模型。',
1565
+ contextWindow: '上下文窗口必须为 8192 至 4000000 之间的整数。',
1566
+ maxOutputTokens: '输出上限须为 256 至 65536 之间的整数,并小于上下文窗口。'
1567
+ }[error.field];
1568
+ return chinese ? instance.tx(error.message, chinese) : error.message;
1569
+ }
1570
+
1512
1571
  function validateConnectionForm(instance) {
1513
1572
  const context = readContext(instance), tx = instance.tx;
1514
1573
  let message = '';
@@ -21,7 +21,8 @@
21
21
  ProjectSessionCleanup, SessionPersistence, SessionState, StorageDb, StorageKeys, StorageMigration
22
22
  } = deps;
23
23
  const projectSessionCleanup = ProjectSessionCleanup.create({
24
- tr, getAccountScopeId: getCachedAccountScopeId, showPluginConfirm, showPluginToast,
24
+ tr, tx, getAccountScopeId: getCachedAccountScopeId, showPluginConfirm, showPluginToast,
25
+ getViewGeneration: () => dashboardRenderGeneration,
25
26
  sendBackgroundNative, mutateProjectPanelState, renderRecentProjectsVariant,
26
27
  SessionState, StorageDb, StorageMigration
27
28
  });
@@ -198,10 +199,11 @@
198
199
  } catch (_error) { /* swallow */ }
199
200
  try {
200
201
  var docTitle = (document.title || '').trim();
201
- // Overleaf typically formats as "<Project Name> - Overleaf" or
202
- // "<Project Name> - Online LaTeX Editor"; strip the " - " suffix.
202
+ // Overleaf formats the title as "<Project Name> - Overleaf" or
203
+ // "<Project Name> - Online LaTeX Editor Overleaf" (localized); drop the
204
+ // last " - " segment when it names Overleaf.
203
205
  if (docTitle) {
204
- var stripped = docTitle.replace(/\s*[-–]\s*Overleaf.*$/i, '').trim();
206
+ var stripped = docTitle.replace(/\s+[-–|]\s+[^-–|]*Overleaf[^-–|]*$/i, '').trim();
205
207
  if (stripped && stripped.toLowerCase() !== 'overleaf') {
206
208
  return stripped;
207
209
  }
@@ -492,6 +494,7 @@
492
494
  // Kept as a compatibility entry point and as the recovery path for invalid project links.
493
495
  function renderRecentProjectRow(row) {
494
496
  var projectId = row && row.projectId;
497
+ var cleanupScope = { accountScopeId: getCachedAccountScopeId(), generation: dashboardRenderGeneration };
495
498
  var valid = isValidProjectId(projectId);
496
499
  var wrap = createViewElement('div', 'recent-projects-row-wrap recent-projects-unavailable-row');
497
500
  wrap.setAttribute('data-project-id', projectId || '');
@@ -501,7 +504,7 @@
501
504
  link.appendChild(textNode(projectDisplayName(projectId), 'recent-projects-row-name'));
502
505
  if (valid) link.addEventListener('click', function () { openProjectFromRow(projectId); });
503
506
  var cleanup = createViewButton(tr(valid ? 'recentProjects_clearProject' : 'recentProjects_cleanup'), function () {
504
- if (valid) projectSessionCleanup.clearProjectSessions(projectId, projectDisplayName(projectId)).catch(function () {});
507
+ if (valid) projectSessionCleanup.clearProjectSessions(projectId, projectDisplayName(projectId), cleanupScope).catch(function () {});
505
508
  else cleanupDeadProjectEntry(projectId).catch(function () {});
506
509
  }, valid ? 'data-project-clear' : 'data-row-cleanup');
507
510
  wrap.appendChild(link);
@@ -533,6 +536,7 @@
533
536
  }
534
537
 
535
538
  function renderProjectSessionRow(sessionsEl, projectId, record, options) {
539
+ var cleanupScope = { accountScopeId: getCachedAccountScopeId(), generation: dashboardRenderGeneration };
536
540
  var featured = Boolean(options && options.featured);
537
541
  var running = Boolean(StorageDb)
538
542
  && settleDashboardRunStatus(StorageDb.derivePrimaryStatusBadge(record), record.updatedAt || record.lastActivityAt) === 'running';
@@ -586,7 +590,7 @@
586
590
  }, 'data-project-conversations');
587
591
  var openProject = createViewButton(tr('recentProjects_openProject'), function () { openProjectFromRow(projectId); });
588
592
  var clear = createViewButton(tr('recentProjects_clearProject'), function () {
589
- projectSessionCleanup.clearProjectSessions(projectId, projectDisplayName(projectId)).catch(function () {});
593
+ projectSessionCleanup.clearProjectSessions(projectId, projectDisplayName(projectId), cleanupScope).catch(function () {});
590
594
  }, 'data-project-clear');
591
595
  row.appendChild(renderActionsMenu([rename, del, projectConversations, openProject, clear]));
592
596
 
@@ -697,9 +701,10 @@
697
701
  // saveState uses, so opening the project later sees a coherent state.
698
702
  // NOTE: if the project is open in another tab, that tab's in-memory state
699
703
  // wins on its next save; the IndexedDB record mutation below still holds.
700
- async function mutateProjectPanelState(projectId, mutate) {
704
+ async function mutateProjectPanelState(projectId, mutate, options = {}) {
701
705
  var key = projectPanelStateKey(projectId);
702
706
  var stored = await chrome.storage.local.get(key);
707
+ options.assertCurrent?.();
703
708
  var blob = stored && stored[key];
704
709
  if (!blob || !SessionState) {
705
710
  return false;
@@ -707,7 +712,9 @@
707
712
  var nextState = mutate(SessionState.normalizePanelState(blob));
708
713
  var payload = {};
709
714
  payload[key] = SessionState.prepareStateForStorage(nextState);
715
+ options.assertCurrent?.();
710
716
  await chrome.storage.local.set(payload);
717
+ options.assertCurrent?.();
711
718
  return true;
712
719
  }
713
720
 
@@ -752,7 +759,7 @@
752
759
  }
753
760
  });
754
761
  if (!response || !response.ok) {
755
- showPluginToast(tr('deleteSessionHistoryFailedToast', { message: (response && response.error && response.error.message) || 'native host did not return success' }), { status: 'warning', sticky: true });
762
+ showPluginToast(tr('deleteSessionHistoryFailedToast', { message: (response && response.error && response.error.message) || tr('deleteSessionNativeFailureDetail') }), { status: 'warning', sticky: true });
756
763
  } else if (response.result && response.result.skipped) {
757
764
  showPluginToast(tr('deleteSessionNoThreadToast'), { status: 'info' });
758
765
  } else {