codex-overleaf-link 2.5.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/README.md +38 -26
  2. package/README.zh-CN.md +39 -27
  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 +206 -53
  9. package/extension/src/backgroundBootstrapMigration.js +181 -0
  10. package/extension/src/backgroundSharedSessions.js +74 -14
  11. package/extension/src/backgroundUpdateCoordinator.js +118 -21
  12. package/extension/src/backgroundUpdateSafety.js +173 -0
  13. package/extension/src/content/activeTurnControl.js +8 -2
  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 +93 -45
  17. package/extension/src/content/composerPanel.js +18 -0
  18. package/extension/src/content/contentRuntime.js +661 -676
  19. package/extension/src/content/diagnosticsController.js +122 -96
  20. package/extension/src/content/diagnosticsExport.js +19 -5
  21. package/extension/src/content/diagnosticsPanel.js +30 -0
  22. package/extension/src/content/directoryCleanupRecovery.js +54 -0
  23. package/extension/src/content/generated/content.bundle.js +6879 -2786
  24. package/extension/src/content/generated/content.bundle.meta.json +11 -3
  25. package/extension/src/content/globalPreferencesController.js +1 -1
  26. package/extension/src/content/legacyGlobalRegistry.js +6 -0
  27. package/extension/src/content/localSkillsPanel.js +102 -12
  28. package/extension/src/content/markdownText.js +5 -6
  29. package/extension/src/content/modelPicker.js +7 -4
  30. package/extension/src/content/nativeCompatibilityController.js +45 -20
  31. package/extension/src/content/otWarmMirror.js +4 -5
  32. package/extension/src/content/otWarmMirrorController.js +17 -20
  33. package/extension/src/content/pageBridgeClient.js +15 -2
  34. package/extension/src/content/panelMaintenance.js +40 -12
  35. package/extension/src/content/panelRenderer.js +6 -0
  36. package/extension/src/content/projectProviderSelection.js +13 -4
  37. package/extension/src/content/projectSessionCleanup.js +109 -62
  38. package/extension/src/content/projectSettingsCoordinator.js +39 -2
  39. package/extension/src/content/providerSettingsCoordinator.js +46 -8
  40. package/extension/src/content/providerSettingsDialog.js +71 -13
  41. package/extension/src/content/recentProjects.js +11 -5
  42. package/extension/src/content/runActivitySummary.js +40 -6
  43. package/extension/src/content/runCompletionMeta.js +344 -0
  44. package/extension/src/content/runController.js +37 -14
  45. package/extension/src/content/runFailureNotice.js +21 -12
  46. package/extension/src/content/runGuidanceController.js +72 -27
  47. package/extension/src/content/runGuidanceView.js +59 -10
  48. package/extension/src/content/runQueueScheduler.js +148 -42
  49. package/extension/src/content/runRecoveryActions.js +146 -0
  50. package/extension/src/content/runResultActions.js +8 -125
  51. package/extension/src/content/runReviewController.js +574 -0
  52. package/extension/src/content/runScrollLayout.js +21 -2
  53. package/extension/src/content/runTimelineView.js +146 -153
  54. package/extension/src/content/scopedPersistencePanelState.js +51 -6
  55. package/extension/src/content/selectionContextView.js +1 -1
  56. package/extension/src/content/sensitivePreflight.js +140 -0
  57. package/extension/src/content/sessionForkController.js +80 -17
  58. package/extension/src/content/sessionManager.js +1 -1
  59. package/extension/src/content/settingsPanel.js +7 -3
  60. package/extension/src/content/settingsWorkbench.js +5 -0
  61. package/extension/src/content/sharedSessionViewSync.js +45 -8
  62. package/extension/src/content/updateIdle.js +4 -1
  63. package/extension/src/content/updateNotice.js +60 -8
  64. package/extension/src/content/updateSafetyReceiver.js +58 -0
  65. package/extension/src/content/writebackController.js +53 -4
  66. package/extension/src/content/writebackOrchestrator.js +82 -73
  67. package/extension/src/content/writingStyleSettings.js +35 -11
  68. package/extension/src/page/binaryAssetUploader.js +136 -14
  69. package/extension/src/page/fileMover.js +256 -0
  70. package/extension/src/page/ownedDirectoryCleanup.js +94 -0
  71. package/extension/src/page/referenceProjects.js +1 -0
  72. package/extension/src/page/reviewContext.js +113 -0
  73. package/extension/src/page/saveState.js +6 -4
  74. package/extension/src/page/snapshotRouter.js +84 -39
  75. package/extension/src/page/textFileCreator.js +105 -25
  76. package/extension/src/page/trackedChangeCapture.js +12 -2
  77. package/extension/src/page/trackedChangeOwnership.js +19 -1
  78. package/extension/src/page/trackedChangeReplay.js +41 -21
  79. package/extension/src/page/trackedChangesLifecycle.js +217 -199
  80. package/extension/src/page/treeOperations.js +7 -10
  81. package/extension/src/page/updateSafetyProbe.js +41 -0
  82. package/extension/src/page/writebackReceiptJournal.js +2 -0
  83. package/extension/src/page/writebackRouter.js +80 -79
  84. package/extension/src/pageBridge.js +19 -3
  85. package/extension/src/popup.js +62 -10
  86. package/extension/src/shared/agentTranscript.js +23 -0
  87. package/extension/src/shared/auditRecords.js +2 -0
  88. package/extension/src/shared/compatibility.js +22 -6
  89. package/extension/src/shared/compileAdapter.js +79 -57
  90. package/extension/src/shared/composerAttachmentDraft.js +151 -0
  91. package/extension/src/shared/directoryCleanupProof.js +43 -0
  92. package/extension/src/shared/failureReasons.js +309 -3
  93. package/extension/src/shared/globalPreferences.js +30 -13
  94. package/extension/src/shared/governanceRules.js +4 -37
  95. package/extension/src/shared/i18n.js +298 -2
  96. package/extension/src/shared/managedUpdateProjection.js +2 -2
  97. package/extension/src/shared/nativeRequestIdentity.js +1 -0
  98. package/extension/src/shared/pageRpcContract.js +5 -4
  99. package/extension/src/shared/projectFiles.js +21 -44
  100. package/extension/src/shared/providerProfiles.js +5 -3
  101. package/extension/src/shared/runInputQueue.js +48 -5
  102. package/extension/src/shared/selectionContext.js +12 -3
  103. package/extension/src/shared/sessionState.js +72 -27
  104. package/extension/src/shared/settlementFacts.js +10 -2
  105. package/extension/src/shared/sharedSessionBridge.js +37 -14
  106. package/extension/src/shared/sharedSessionTransfer.js +140 -0
  107. package/extension/src/shared/staleGuard.js +118 -13
  108. package/extension/src/shared/storageDb.js +58 -26
  109. package/extension/src/shared/storageMigration.js +71 -111
  110. package/extension/src/shared/storageRunActions.js +209 -26
  111. package/extension/src/shared/subagentActivityModel.js +1 -1
  112. package/extension/src/shared/undoOperations.js +136 -13
  113. package/extension/src/shared/updateConsent.js +6 -4
  114. package/extension/src/shared/updateRuntimeIdentity.js +1 -1
  115. package/extension/src/shared/writebackIntent.js +16 -0
  116. package/extension/src/shared/writebackOperationEvidence.js +4 -15
  117. package/extension/src/shared/writebackSettlement.js +38 -38
  118. package/extension/styles/panel.css +19 -5
  119. package/native-host/src/anthropicBridgeRequest.js +4 -2
  120. package/native-host/src/anthropicBridgeResponse.js +6 -2
  121. package/native-host/src/anthropicMessagesBridge.js +24 -16
  122. package/native-host/src/bootstrapMigration.js +296 -0
  123. package/native-host/src/bootstrapMigrationContract.js +101 -0
  124. package/native-host/src/bootstrapUpdate.js +233 -0
  125. package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
  126. package/native-host/src/chatBridgeResponse.js +10 -0
  127. package/native-host/src/chatCompletionsBridge.js +21 -15
  128. package/native-host/src/codexConfigPaths.js +106 -0
  129. package/native-host/src/codexHome.js +66 -73
  130. package/native-host/src/codexHomeScope.js +124 -0
  131. package/native-host/src/codexInheritedAuth.js +90 -0
  132. package/native-host/src/codexLocalSkills.js +301 -0
  133. package/native-host/src/codexPromptAssembly.js +31 -12
  134. package/native-host/src/codexProviderTest.js +24 -26
  135. package/native-host/src/codexSessionRunner.js +110 -183
  136. package/native-host/src/codexSessionTiming.js +18 -8
  137. package/native-host/src/codexSkillIsolation.js +170 -0
  138. package/native-host/src/codexThreadFork.js +19 -3
  139. package/native-host/src/index.js +19 -7
  140. package/native-host/src/managedInstall.js +110 -35
  141. package/native-host/src/managedInstallRecovery.js +140 -0
  142. package/native-host/src/managedRuntimeEvidence.js +57 -0
  143. package/native-host/src/mirrorWorkspace.js +92 -22
  144. package/native-host/src/nativeDoctor.js +15 -0
  145. package/native-host/src/nativeHelperLifecycle.js +21 -0
  146. package/native-host/src/nativeHostRegistration.js +33 -0
  147. package/native-host/src/nativeRunLifecycle.js +71 -0
  148. package/native-host/src/processStopBarrier.js +42 -7
  149. package/native-host/src/projectPathPlatform.js +30 -0
  150. package/native-host/src/providerHttp.js +80 -0
  151. package/native-host/src/providerProfile.js +4 -2
  152. package/native-host/src/providerReasoning.js +2 -7
  153. package/native-host/src/providerRuntime.js +1 -0
  154. package/native-host/src/providerStore.js +72 -14
  155. package/native-host/src/responsesPassthroughBridge.js +266 -52
  156. package/native-host/src/runtimeInstaller.js +21 -1
  157. package/native-host/src/selectionScope.js +36 -8
  158. package/native-host/src/subagentAdmission.js +26 -0
  159. package/native-host/src/subagentBroker.js +79 -66
  160. package/native-host/src/subagentTelemetry.js +19 -10
  161. package/native-host/src/subagentWorkspacePath.js +5 -2
  162. package/native-host/src/taskRunnerRuntime.js +77 -54
  163. package/native-host/src/tomlStatements.js +140 -0
  164. package/native-host/src/updateManager.js +297 -39
  165. package/native-host/src/writingStyleRuntime.js +57 -29
  166. package/native-host/src/writingStyleStore.js +73 -0
  167. package/package.json +1 -1
  168. 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 = {}) {
@@ -1379,7 +1432,7 @@
1379
1432
  }
1380
1433
  }
1381
1434
 
1382
- function openModelEditor(instance, id) {
1435
+ function openModelEditor(instance, id, options = {}) {
1383
1436
  const provider = readContext(instance).draft;
1384
1437
  const existing = provider.models.find(model => model.id === id);
1385
1438
  if (id && !existing) return;
@@ -1450,7 +1503,7 @@
1450
1503
  + escapeHtml(tx('Cancel', '取消')) + '</button><button type="button" class="codex-provider-primary-button" data-provider-model-action="apply-editor">'
1451
1504
  + escapeHtml(id ? tx('Apply', '应用') : tx('Add model', '添加模型')) + '</button></footer>';
1452
1505
  updateModelReasoningSummary(instance);
1453
- if (modal.showModal) modal.showModal(); else modal.setAttribute('open', '');
1506
+ if (options.show !== false) { if (modal.showModal) modal.showModal(); else modal.setAttribute('open', ''); }
1454
1507
  }
1455
1508
 
1456
1509
  function updateModelReasoningSummary(instance) {
@@ -1499,16 +1552,21 @@
1499
1552
  closeModelEditor(instance, false);
1500
1553
  if (!unchanged) replaceModelDraft(instance, next);
1501
1554
  } 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;
1555
+ editor.error = { field: error.field, message: error.message };
1556
+ modal.querySelector('[data-model-error]').textContent = formatModelEditorError(instance, editor.error);
1508
1557
  if (error.field) get(error.field)?.focus();
1509
1558
  }
1510
1559
  }
1511
1560
 
1561
+ function formatModelEditorError(instance, error) {
1562
+ const chinese = {
1563
+ id: '请检查模型 ID:必填、最多 200 字符、不可重复,且单个服务最多 32 个模型。',
1564
+ contextWindow: '上下文窗口必须为 8192 至 4000000 之间的整数。',
1565
+ maxOutputTokens: '输出上限须为 256 至 65536 之间的整数,并小于上下文窗口。'
1566
+ }[error.field];
1567
+ return chinese ? instance.tx(error.message, chinese) : error.message;
1568
+ }
1569
+
1512
1570
  function validateConnectionForm(instance) {
1513
1571
  const context = readContext(instance), tx = instance.tx;
1514
1572
  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
  });
@@ -492,6 +493,7 @@
492
493
  // Kept as a compatibility entry point and as the recovery path for invalid project links.
493
494
  function renderRecentProjectRow(row) {
494
495
  var projectId = row && row.projectId;
496
+ var cleanupScope = { accountScopeId: getCachedAccountScopeId(), generation: dashboardRenderGeneration };
495
497
  var valid = isValidProjectId(projectId);
496
498
  var wrap = createViewElement('div', 'recent-projects-row-wrap recent-projects-unavailable-row');
497
499
  wrap.setAttribute('data-project-id', projectId || '');
@@ -501,7 +503,7 @@
501
503
  link.appendChild(textNode(projectDisplayName(projectId), 'recent-projects-row-name'));
502
504
  if (valid) link.addEventListener('click', function () { openProjectFromRow(projectId); });
503
505
  var cleanup = createViewButton(tr(valid ? 'recentProjects_clearProject' : 'recentProjects_cleanup'), function () {
504
- if (valid) projectSessionCleanup.clearProjectSessions(projectId, projectDisplayName(projectId)).catch(function () {});
506
+ if (valid) projectSessionCleanup.clearProjectSessions(projectId, projectDisplayName(projectId), cleanupScope).catch(function () {});
505
507
  else cleanupDeadProjectEntry(projectId).catch(function () {});
506
508
  }, valid ? 'data-project-clear' : 'data-row-cleanup');
507
509
  wrap.appendChild(link);
@@ -533,6 +535,7 @@
533
535
  }
534
536
 
535
537
  function renderProjectSessionRow(sessionsEl, projectId, record, options) {
538
+ var cleanupScope = { accountScopeId: getCachedAccountScopeId(), generation: dashboardRenderGeneration };
536
539
  var featured = Boolean(options && options.featured);
537
540
  var running = Boolean(StorageDb)
538
541
  && settleDashboardRunStatus(StorageDb.derivePrimaryStatusBadge(record), record.updatedAt || record.lastActivityAt) === 'running';
@@ -586,7 +589,7 @@
586
589
  }, 'data-project-conversations');
587
590
  var openProject = createViewButton(tr('recentProjects_openProject'), function () { openProjectFromRow(projectId); });
588
591
  var clear = createViewButton(tr('recentProjects_clearProject'), function () {
589
- projectSessionCleanup.clearProjectSessions(projectId, projectDisplayName(projectId)).catch(function () {});
592
+ projectSessionCleanup.clearProjectSessions(projectId, projectDisplayName(projectId), cleanupScope).catch(function () {});
590
593
  }, 'data-project-clear');
591
594
  row.appendChild(renderActionsMenu([rename, del, projectConversations, openProject, clear]));
592
595
 
@@ -697,9 +700,10 @@
697
700
  // saveState uses, so opening the project later sees a coherent state.
698
701
  // NOTE: if the project is open in another tab, that tab's in-memory state
699
702
  // wins on its next save; the IndexedDB record mutation below still holds.
700
- async function mutateProjectPanelState(projectId, mutate) {
703
+ async function mutateProjectPanelState(projectId, mutate, options = {}) {
701
704
  var key = projectPanelStateKey(projectId);
702
705
  var stored = await chrome.storage.local.get(key);
706
+ options.assertCurrent?.();
703
707
  var blob = stored && stored[key];
704
708
  if (!blob || !SessionState) {
705
709
  return false;
@@ -707,7 +711,9 @@
707
711
  var nextState = mutate(SessionState.normalizePanelState(blob));
708
712
  var payload = {};
709
713
  payload[key] = SessionState.prepareStateForStorage(nextState);
714
+ options.assertCurrent?.();
710
715
  await chrome.storage.local.set(payload);
716
+ options.assertCurrent?.();
711
717
  return true;
712
718
  }
713
719
 
@@ -752,7 +758,7 @@
752
758
  }
753
759
  });
754
760
  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 });
761
+ showPluginToast(tr('deleteSessionHistoryFailedToast', { message: (response && response.error && response.error.message) || tr('deleteSessionNativeFailureDetail') }), { status: 'warning', sticky: true });
756
762
  } else if (response.result && response.result.skipped) {
757
763
  showPluginToast(tr('deleteSessionNoThreadToast'), { status: 'info' });
758
764
  } else {
@@ -3,8 +3,8 @@
3
3
 
4
4
  function create(deps = {}) {
5
5
  if (!deps.RunActivityModel) return null;
6
- const { RunActivityModel: Model, tx, findRunRecord, getPanel, cssEscape, isGuidance,
7
- formatElapsed, renderMarkdownBlockText, renderRunEvent, presence, classifyStatus, getProjectId, summarizeCommand,
6
+ const { RunActivityModel: Model, RunGuidanceView, getPendingGuidance = () => [], tx, findRunRecord, getPanel, cssEscape, isGuidance,
7
+ formatElapsed, renderMarkdownBlockText, renderRunEvent, presence, classifyStatus, getProjectId, summarizeCommand, projectReviewResultEvent, formatReviewResultFailure,
8
8
  sanitizeAssistantVisibleText: sanitize = value => String(value || '') } = deps;
9
9
  const cards = new WeakMap();
10
10
  const choices = new Map();
@@ -115,6 +115,31 @@
115
115
  disclosure(el, key);
116
116
  return { el, label, target, status, diff, body, key, bodyStamp: '' };
117
117
  }
118
+ function reviewResultLines(item) {
119
+ const source = item.event || {};
120
+ if (item.kind !== 'notice' || source.streamRole || source.guidanceId || source.subagent || source.technicalDetail
121
+ || (source.kind && source.kind !== 'activity') || item.meta?.noticeSource || source.activity?.noticeSource || source.activity?.id) return [];
122
+ const event = projectReviewResultEvent?.(source) || source;
123
+ const english = /^(?:Undo result: (?:undone \d+ item\(s\), skipped \d+ item\(s\)|undone \d+ file\(s\), skipped \d+|rejected \d+ tracked change\(s\), skipped \d+)|Accept result: accepted \d+ tracked change\(s\), skipped \d+)$/;
124
+ const chinese = /^(?:撤销结果:(?:已撤销 \d+ 项,跳过 \d+ 项|已撤销 \d+ 个文件,跳过 \d+ 项|已拒绝 \d+ 条留痕,跳过 \d+ 条)|接受结果:已接受 \d+ 条留痕,跳过 \d+ 条)$/;
125
+ if (!english.test(event.title || '') && !chinese.test(event.title || '')) return [];
126
+ const skipped = event.detail?.Skipped || event.detail?.['跳过'];
127
+ if (!Array.isArray(skipped) && source.failure) {
128
+ const message = plain(formatReviewResultFailure?.(source.failure)?.message).slice(0, 1000);
129
+ return message ? [message] : [];
130
+ }
131
+ if (!Array.isArray(skipped)) return [];
132
+ return skipped.flatMap(entry => {
133
+ const fileValue = entry?.File ?? entry?.['文件'], reasonValue = entry?.Reason ?? entry?.['原因'];
134
+ if (typeof fileValue !== 'string' || typeof reasonValue !== 'string') return [];
135
+ if (source.failure?.file === fileValue && [source.failure.userMessage, fileValue + ': ' + source.failure.userMessage].includes(reasonValue)) {
136
+ const message = plain(formatReviewResultFailure?.(source.failure)?.message).slice(0, 1000);
137
+ if (message) return [message];
138
+ }
139
+ const file = plain(fileValue).slice(0, 300), reason = plain(reasonValue).slice(0, 1000);
140
+ return file && reason ? [reason.startsWith(file + ': ') ? reason : file + ': ' + reason] : [];
141
+ });
142
+ }
118
143
  function renderTool(row, item, card, run) {
119
144
  const meta = item.meta || {};
120
145
  const state = effectiveState(item, run);
@@ -140,7 +165,8 @@
140
165
  row.status.hidden = true;
141
166
  } else row.status.hidden = false;
142
167
  const publicText = plain(item.event?.title);
143
- const stamp = [publicText, meta.output, meta.command, meta.exitCode, file?.preview, state, meta.clipped].join('\0');
168
+ const reviewLines = reviewResultLines(item);
169
+ const stamp = [publicText, meta.output, meta.command, meta.exitCode, file?.preview, state, meta.clipped, JSON.stringify(reviewLines)].join('\0');
144
170
  if (stamp !== row.bodyStamp) {
145
171
  row.bodyStamp = stamp;
146
172
  row.body.replaceChildren();
@@ -155,9 +181,11 @@
155
181
  } else {
156
182
  if (meta.command) { const command = make('pre', 'run-workflow-command'); command.textContent = plain(meta.command); row.body.append(command); }
157
183
  if (meta.output) { const output = make('pre', 'run-workflow-output'); output.textContent = plain(meta.output); row.body.append(output); }
158
- const note = make('div', 'run-workflow-tool-note');
159
- note.textContent = publicText;
160
- row.body.append(note);
184
+ for (const value of reviewLines.length ? reviewLines : [publicText]) {
185
+ const note = make('div', 'run-workflow-tool-note');
186
+ note.textContent = value;
187
+ row.body.append(note);
188
+ }
161
189
  }
162
190
  if (meta.exitCode !== undefined) { const exit = make('div', 'run-workflow-tool-note'); exit.textContent = tx('Exit code: ', '退出码:') + meta.exitCode; row.body.append(exit); }
163
191
  if (meta.clipped || file?.clipped) { const more = make('div', 'run-workflow-tool-note'); more.textContent = tx('Preview only. More detail may be available in diagnostics.', '当前为摘要预览,更多信息可通过诊断查看。'); row.body.append(more); }
@@ -502,6 +530,7 @@
502
530
  busy.setAttribute('role', 'status');
503
531
  events.after(busy);
504
532
  const card = { id: String(run.id), root, process, summary, events, metrics, attention,
533
+ guidance: root.querySelector('[data-run-guidance]'),
505
534
  busy, rows: new Map(), run, setup: setupEl, setupSummary, setupFold, setupSteps,
506
535
  peek, peekTrack, peekMore, peekRows: new Map() };
507
536
  const expand = () => { remember(card.id + ':run', true); process.open = true; update({ root, recordId: run.id }); };
@@ -524,6 +553,11 @@
524
553
  function render(card, run, incoming) {
525
554
  card.run = run;
526
555
  const result = Model.project(run, incoming, { isGuidance });
556
+ const guidanceEvents = (run.events || []).concat(incoming && !(run.events || []).includes(incoming) ? [incoming] : []);
557
+ RunGuidanceView?.reconcileGuidance(card.guidance,
558
+ guidanceEvents.concat(getPendingGuidance(run)),
559
+ tx, { active: run.status === 'running' });
560
+ result.blocks = result.blocks.filter(block => block.kind !== 'guidance');
527
561
  card.projection = result;
528
562
  subagentView?.update(card);
529
563
  const active = run.status === 'running';