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
@@ -1,6 +1,10 @@
1
- (function initCodexOverleafRunTimelineView() {
1
+ (function (root, factory) {
2
+ if (typeof module === 'object' && module.exports) module.exports = factory(require('./runRecoveryActions'));
3
+ else root.CodexOverleafModuleRegistry.define('RunTimelineView', ['RunRecoveryActions'], factory);
4
+ })(typeof window !== 'undefined' ? window : globalThis, function (RunRecoveryActions) {
2
5
  'use strict';
3
6
 
7
+
4
8
  // Owns scroll, cards, reports and mature run actions. The optional activity
5
9
  // presenter projects stored events without taking ownership of settlement.
6
10
  function create(deps = {}) {
@@ -10,9 +14,14 @@
10
14
  RunActivityModel,
11
15
  SubagentActivityView,
12
16
  RunGuidanceView,
17
+ getPendingGuidance = () => [],
13
18
  RunResultActions,
14
19
  RunScrollLayout,
15
20
  RunFailureNotice,
21
+ I18n,
22
+ FailureReasons,
23
+ localizeVisibleReason = value => value,
24
+ localizeCompileReason = value => value,
16
25
  tr,
17
26
  tx,
18
27
  getLocale,
@@ -47,6 +56,7 @@
47
56
  trackedChangeInFlight,
48
57
  projectRunSettlement,
49
58
  getCurrentProjectId,
59
+ getAccountScopeId,
50
60
  getProjectName
51
61
  } = deps;
52
62
 
@@ -59,6 +69,7 @@
59
69
  // drives the floating "jump to latest" button's counter.
60
70
  let scrollLogRafId = 0;
61
71
  let scrollLogPendingForce = false;
72
+ let pendingGuidanceReveal = null;
62
73
  let jumpToLatestButton = null;
63
74
  let unreadSinceDetach = 0;
64
75
  // Elapsed time belongs to the run; tool rows retain their own lifecycle.
@@ -69,9 +80,21 @@
69
80
  onLayoutChange: scroller => logAutoFollow ? scrollLogToBottom() : updateJumpToLatestButton(scroller)
70
81
  });
71
82
  const failureNotice = RunFailureNotice?.create({ tr, projectRunSettlement, sanitizeText: sanitizeAssistantVisibleText });
83
+ const { buildRecoveryButton, appendRecoveryActionForFailure } = RunRecoveryActions.create({
84
+ document: typeof document === 'undefined' ? undefined : document,
85
+ window: typeof window === 'undefined' ? undefined : window,
86
+ tx, failureNotice, refillComposerForRetry, openProjectFileForFailure, showNativeSetupGuidance,
87
+ openStorageSettings, forceCancelStuckTaskForCurrentProject
88
+ });
72
89
  const presence = RunPresence?.create({ tx, getLocale, formatElapsed });
73
90
  const displayRun = run => RunFailureNotice?.projectResolvedUndo?.(run) || run;
74
- const activitySummary = RunActivitySummary?.create({ RunActivityModel, SubagentActivityView, tx, findRunRecord: (...args) => displayRun(findRunRecord(...args)), getPanel, cssEscape,
91
+ const displayLocalizedRun = run => {
92
+ const displayed = displayRun(run);
93
+ return displayed ? { ...displayed, events: (displayed.events || []).map(sanitizeRunEventForRender) } : displayed;
94
+ };
95
+ const activitySummary = RunActivitySummary?.create({ RunActivityModel, RunGuidanceView, getPendingGuidance, SubagentActivityView, tx, findRunRecord: (...args) => displayLocalizedRun(findRunRecord(...args)), getPanel, cssEscape,
96
+ projectReviewResultEvent: sanitizeRunEventForRender,
97
+ formatReviewResultFailure,
75
98
  formatEventTime, formatElapsed, renderMarkdownBlockText, renderRunEvent, formatEventDetail,
76
99
  presence, classifyStatus: classifyRunPresence, getProjectId: () => getCurrentProjectId?.() || '',
77
100
  summarizeCommand: RunPresence?.commandSummary,
@@ -82,6 +105,7 @@
82
105
  function resetAutoFollow() {
83
106
  logAutoFollow = true;
84
107
  userScrollIntentUntil = 0;
108
+ pendingGuidanceReveal = null;
85
109
  }
86
110
 
87
111
  function bindLogAutoFollow() {
@@ -183,6 +207,7 @@
183
207
  if ((event?.type === 'wheel' && event.deltaY < 0) || ['ArrowUp', 'PageUp', 'Home'].includes(event?.key)) {
184
208
  logAutoFollow = false;
185
209
  scrollLogPendingForce = false;
210
+ pendingGuidanceReveal = null;
186
211
  }
187
212
  }
188
213
 
@@ -207,6 +232,7 @@
207
232
  userScrollIntentUntil = 0;
208
233
  unreadSinceDetach = 0;
209
234
  scrollLogPendingForce = true;
235
+ pendingGuidanceReveal = null;
210
236
  } else if (!logAutoFollow) {
211
237
  // The user has scrolled up: do not yank them. Just keep the jump button
212
238
  // state current.
@@ -241,7 +267,21 @@
241
267
  writeNow();
242
268
  });
243
269
  }
270
+ function revealGuidance(row) {
271
+ if (!logAutoFollow || activitySummary?.isSubagentViewOpen?.() || !getLogScrollContainer()?.contains?.(row)) return false;
272
+ pendingGuidanceReveal = row;
273
+ scrollLogToBottom();
274
+ return true;
275
+ }
244
276
  function setLogScrollPosition(scroller) {
277
+ const reveal = pendingGuidanceReveal;
278
+ pendingGuidanceReveal = null;
279
+ const result = reveal && scrollLayout?.revealGuidance(reveal);
280
+ if (result) {
281
+ logAutoFollow = result.following;
282
+ if (!logAutoFollow) scrollLayout.captureAnchor(true);
283
+ return;
284
+ }
245
285
  scroller.scrollTop = scroller.scrollHeight;
246
286
  }
247
287
  function collapseRunProcess(view, statusText) {
@@ -357,7 +397,7 @@
357
397
  }
358
398
 
359
399
  function renderRunCard(run) {
360
- run = displayRun(run);
400
+ run = displayLocalizedRun(run);
361
401
  const root = document.createElement('section');
362
402
  root.className = 'transcript-turn run-card';
363
403
  root.dataset.status = run.status || 'completed';
@@ -418,11 +458,7 @@
418
458
  truncated.textContent = tr('eventsTruncatedNote');
419
459
  events.append(truncated);
420
460
  }
421
- const guidanceTarget = guidance;
422
- const renderedGuidanceIds = new Set();
423
- const completedLegacyGuidance = new Set((run.events || [])
424
- .filter(event => event.kind === 'guidance' && !event.guidanceId && event.status === 'completed')
425
- .map(event => event.title));
461
+ if (!activitySummary) RunGuidanceView.reconcileGuidance?.(guidance, (run.events || []).concat(getPendingGuidance(run)), tx, { active: run.status === 'running' });
426
462
  for (const event of run.events || []) {
427
463
  if (event.kind === 'report') {
428
464
  report.hidden = false;
@@ -430,16 +466,7 @@
430
466
  } else if (activitySummary || event.kind === 'technical') {
431
467
  continue;
432
468
  } else if (RunGuidanceView.getGuidanceText(event)) {
433
- if (event.guidanceId && renderedGuidanceIds.has(event.guidanceId)) {
434
- continue;
435
- }
436
- if (!event.guidanceId && event.status === 'queued' && completedLegacyGuidance.has(event.title)) {
437
- continue;
438
- }
439
- if (event.guidanceId) {
440
- renderedGuidanceIds.add(event.guidanceId);
441
- }
442
- guidanceTarget.append(renderRunEvent(event));
469
+ continue;
443
470
  } else if (event.kind === 'stream') {
444
471
  upsertStreamEvent({ events }, event);
445
472
  } else {
@@ -458,6 +485,14 @@
458
485
 
459
486
  function getRunStatusText(run = {}) {
460
487
  if (run.retryingWriteback) return tx('Checking file sync', '正在检查文件同步');
488
+ if (['Stopped tracking after a page refresh', '页面刷新后已停止跟踪'].includes(run.statusText)) return tr('restoredRunStoppedStatus');
489
+ const knownStatus = /^(?:Done|Failed|Cancelled|Running|已处理|处理失败|已取消|处理中)(?:\s+[\d.]+(?:ms|s|m|h|秒|分钟)(?:\s+[\d.]+(?:ms|s|m|h|秒|分钟))*)?$/.test(run.statusText || '');
490
+ if (knownStatus) {
491
+ const elapsed = String(run.statusText).replace(/^(?:Done|Failed|Cancelled|Running|已处理|处理失败|已取消|处理中)\s*/, '');
492
+ const key = run.status === 'running' ? 'processing' : run.status === 'failed' ? 'processedFailed'
493
+ : ['rejected', 'cancelled'].includes(run.status) ? 'processedCancelled' : 'processed';
494
+ return tr(key, { elapsed }).trim();
495
+ }
461
496
  if (run.statusText) {
462
497
  return run.statusText;
463
498
  }
@@ -534,18 +569,61 @@
534
569
  function sanitizeRunEventForRender(event = {}) {
535
570
  const streamRole = sanitizeAssistantVisibleText(event.streamRole);
536
571
  const title = sanitizeAssistantVisibleText(event.title);
572
+ const activity = !streamRole && (!event.kind || event.kind === 'activity') && !event.guidanceId;
573
+ const localized = activity ? localizeProductActivity(title, sanitizeAssistantVisibleValue(event.detail)) : null;
537
574
  return {
538
575
  ...event,
576
+ ...(activity && event.activity?.kind === 'compile' ? { activity: { ...event.activity, output: localizeCompileReason(event.activity.output) } } : {}),
539
577
  title: streamRole === 'reasoning'
540
578
  ? stripEmptyHtmlCommentPlaceholders(title)
541
- : title,
579
+ : localized?.title || title,
542
580
  status: sanitizeAssistantVisibleText(event.status),
543
- detail: sanitizeAssistantVisibleValue(event.detail),
581
+ detail: localized?.detail ?? sanitizeAssistantVisibleValue(event.detail),
544
582
  technicalDetail: sanitizeAssistantVisibleValue(event.technicalDetail),
545
583
  streamKey: sanitizeAssistantVisibleText(event.streamKey),
546
584
  streamRole
547
585
  };
548
586
  }
587
+
588
+ function localizeProductActivity(title, detail) {
589
+ let known = false, nextTitle = title;
590
+ const keys = ['undoUnsafeFullFileTitle', 'undoNoTraceTitle', 'undoFileSelectTitle', 'undoNoTraceStarted', 'undoTrackedTitle', 'undoNativeTitle', 'undoTrackedStarted', 'undoNativeStarted', 'undoSwitchedEditing', 'undoReviewingRestoreUnverified', 'runAcceptTrackedStarted', 'runAcceptTrackedFailed', 'undoResult', 'undoTrackedResult', 'undoNativeResult', 'runAcceptTrackedResult'];
591
+ if (I18n?.t) outer: for (const key of keys) for (const locale of ['en', 'zh']) {
592
+ const template = I18n.t(locale, key, { applied: '__APPLIED__', skipped: '__SKIPPED__' });
593
+ const pattern = template.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace('__APPLIED__', '(\\d+)').replace('__SKIPPED__', '(\\d+)');
594
+ const match = new RegExp('^' + pattern + '$').exec(title);
595
+ if (match) { known = true; nextTitle = tr(key, { applied: match[1], skipped: match[2] }); break outer; }
596
+ }
597
+ let countedTitle;
598
+ const writeResult = /^(?:Write result: wrote (\d+) item\(s\), skipped (\d+)|写入结果:已写入 (\d+) 项,跳过 (\d+) 项)$/.exec(title);
599
+ const governance = /^(?:Project governance blocked (\d+) write\(s\) before review\.|项目治理规则在审核前阻止了 (\d+) 项写入。)$/.exec(title);
600
+ if (writeResult) {
601
+ const applied = writeResult[1] ?? writeResult[3], skipped = writeResult[2] ?? writeResult[4];
602
+ countedTitle = tx(`Write result: wrote ${applied} item(s), skipped ${skipped}`, `写入结果:已写入 ${applied} 项,跳过 ${skipped} 项`);
603
+ } else if (governance) {
604
+ const count = governance[1] ?? governance[2];
605
+ countedTitle = tx(`Project governance blocked ${count} write(s) before review.`, `项目治理规则在审核前阻止了 ${count} 项写入。`);
606
+ }
607
+ const labels = ['detailAccepted', 'detailRejected', 'detailUndone', 'detailSkipped', 'detailFile', 'detailRecord', 'detailReason'];
608
+ const project = (value, reason = false) => {
609
+ if (typeof value === 'string') return reason ? localizeVisibleReason(value) : value;
610
+ if (Array.isArray(value)) return value.map(item => project(item, reason));
611
+ if (!value || typeof value !== 'object') return value;
612
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => {
613
+ const label = labels.find(id => ['en', 'zh'].some(locale => I18n?.t?.(locale, id) === key));
614
+ return [label ? tr(label) : key, project(child, label === 'detailReason')];
615
+ }));
616
+ };
617
+ return { title: countedTitle || (known ? nextTitle : localizeCompileReason(localizeVisibleReason(title))),
618
+ detail: typeof detail === 'string' ? localizeVisibleReason(detail) : known ? project(detail) : detail };
619
+ }
620
+ function formatReviewResultFailure(failure) {
621
+ if (failure?.code === 'unknown_legacy_failure' && (!failure.evidence?.originalCode || failure.evidence.originalCode === 'unknown_legacy_failure')
622
+ && ['Unknown reason', '未知原因'].includes(failure.userMessage)) {
623
+ failure = { ...failure, userMessage: tr('unknownReason') };
624
+ }
625
+ return FailureReasons?.formatFailureReasonForDisplay(failure, getLocale?.(), key => { const value = tr(key, failure?.file ? undefined : { file: tr('unknownFile'), activeFile: tr('unknownFile') }); return value && value !== key ? value : undefined; });
626
+ }
549
627
  function renderActivityLine(input) {
550
628
  const event = sanitizeRunEventForRender(input);
551
629
  const row = document.createElement('div');
@@ -586,7 +664,8 @@
586
664
  // The result presenter owns parsing and current lifecycle metadata.
587
665
  const splitFlatCompletionReport = text => RunResultActions.splitFlatCompletionReport(text, tx);
588
666
  const projectCompletionMeta = (meta, run) => RunResultActions.projectCompletionMeta(meta, run, {
589
- tx, trackedChangeInFlight, isTrackedChangeLifecycleRun, projectRunSettlement
667
+ tx, getLocale, tr, trackedChangeInFlight, isTrackedChangeLifecycleRun, projectRunSettlement,
668
+ projectId: getCurrentProjectId?.(), accountScopeId: getAccountScopeId?.()
590
669
  });
591
670
 
592
671
  // The user's own open/closed choice per run survives card re-renders.
@@ -712,7 +791,7 @@
712
791
  mainSections.push(String(structured.conclusion));
713
792
  }
714
793
  if (structured.body) {
715
- mainSections.push(structured.body);
794
+ mainSections.push(RunResultActions.projectCompletionBody?.(structured.body, tx) ?? structured.body);
716
795
  }
717
796
  if (mainSections.length) {
718
797
  renderMarkdownBlockText(main, mainSections.join('\n\n'));
@@ -755,132 +834,6 @@
755
834
  // the normal cancel button is hidden because currentRunView is null).
756
835
  // Recovery-action registry: failure codes whose "next step" is executable
757
836
  // from the panel get a button, not prose. One primary action per failure.
758
- const RETRYABLE_FAILURE_CODES = new Set([
759
- 'native_request_failed', 'codex_timeout', 'codex_no_usable_result',
760
- 'codex_output_limit', 'stale_source_changed', 'patch_anchor_not_found',
761
- 'target_editor_not_ready', 'write_timeout', 'partial_write_needs_review',
762
- 'write_operation_failed'
763
- ]);
764
- const NATIVE_SETUP_FAILURE_CODES = new Set([
765
- 'native_bridge_unavailable', 'native_protocol_incompatible'
766
- ]);
767
- const OPEN_FILE_FAILURE_CODES = new Set([
768
- 'target_file_not_active', 'target_file_open_failed', 'target_file_not_found'
769
- ]);
770
- const STORAGE_FAILURE_CODES = new Set([
771
- 'storage_quota_exceeded', 'run_state_persist_failed'
772
- ]);
773
- // A missing `codex` binary is fixed in the terminal, not the panel — the
774
- // action opens the README troubleshooting section (PATH / login checks).
775
- const CODEX_INSTALL_FAILURE_CODES = new Set(['codex_not_found']);
776
- const CODEX_CLI_TROUBLESHOOTING_URL = 'https://github.com/Ghqqqq/codex-overleaf-link#faq-and-troubleshooting';
777
-
778
- function buildRecoveryButton(failureCode, label, title) {
779
- const button = document.createElement('button');
780
- button.type = 'button';
781
- button.className = 'run-final-answer__recovery-action';
782
- button.dataset.recoveryFor = failureCode;
783
- button.textContent = label;
784
- if (title) {
785
- button.title = title;
786
- }
787
- return button;
788
- }
789
-
790
- function appendEditAndResend(report, run, actionKey) {
791
- const retry = buildRecoveryButton(actionKey,
792
- tx('Edit & resend', '编辑后重发'),
793
- tx('Put this run\u2019s task back into the composer so you can adjust and resend it.', '把本轮任务填回输入框,修改后可直接重发。'));
794
- retry.addEventListener('click', clickEvent => {
795
- clickEvent.stopPropagation();
796
- refillComposerForRetry(run || null);
797
- });
798
- report.append(retry);
799
- }
800
-
801
- function appendRecoveryActionForFailure(report, event, run) {
802
- const recovery = failureNotice?.prepareRecovery(report, { openFile: openProjectFileForFailure });
803
- const failureCode = event?.failure?.code || '';
804
- if (recovery?.handled) return;
805
- report = recovery?.target || report;
806
- if (RETRYABLE_FAILURE_CODES.has(failureCode) && typeof refillComposerForRetry === 'function') {
807
- appendEditAndResend(report, run, failureCode);
808
- return;
809
- }
810
- if (NATIVE_SETUP_FAILURE_CODES.has(failureCode) && typeof showNativeSetupGuidance === 'function') {
811
- const fix = buildRecoveryButton(failureCode,
812
- tx('Fix the native host', '修复 native host'),
813
- tx('Opens the install/update guidance with the copyable command.', '打开安装/更新指引(含可复制命令)。'));
814
- fix.addEventListener('click', clickEvent => {
815
- clickEvent.stopPropagation();
816
- showNativeSetupGuidance();
817
- });
818
- report.append(fix);
819
- return;
820
- }
821
- if (OPEN_FILE_FAILURE_CODES.has(failureCode) && event?.failure?.file && typeof openProjectFileForFailure === 'function') {
822
- const open = buildRecoveryButton(failureCode,
823
- tx(`Open ${event.failure.file} in Overleaf`, `在 Overleaf 打开 ${event.failure.file}`), '');
824
- open.addEventListener('click', clickEvent => {
825
- clickEvent.stopPropagation();
826
- openProjectFileForFailure(event.failure.file);
827
- });
828
- report.append(open);
829
- return;
830
- }
831
- if (STORAGE_FAILURE_CODES.has(failureCode) && typeof openStorageSettings === 'function') {
832
- const clean = buildRecoveryButton(failureCode,
833
- tx('Open history & storage cleanup', '打开历史与存储清理'), '');
834
- clean.addEventListener('click', clickEvent => {
835
- clickEvent.stopPropagation();
836
- openStorageSettings();
837
- });
838
- report.append(clean);
839
- return;
840
- }
841
- if (CODEX_INSTALL_FAILURE_CODES.has(failureCode)) {
842
- const guide = buildRecoveryButton(failureCode,
843
- tx('Codex CLI troubleshooting guide', '查看 Codex CLI 排查指引'),
844
- tx('Opens the README section on fixing a missing `codex` command (PATH / login).', '打开 README 中「找不到 codex 命令」的排查说明(PATH/登录)。'));
845
- guide.addEventListener('click', clickEvent => {
846
- clickEvent.stopPropagation();
847
- window.open(CODEX_CLI_TROUBLESHOOTING_URL, '_blank', 'noopener');
848
- });
849
- report.append(guide);
850
- return;
851
- }
852
- if (failureCode !== 'codex_project_locked') return;
853
- const button = document.createElement('button');
854
- button.type = 'button';
855
- button.className = 'run-final-answer__recovery-action';
856
- button.dataset.recoveryFor = failureCode;
857
- button.textContent = tx('Force-release the stuck task', '强制释放卡住的任务');
858
- button.title = tx(
859
- 'Sends a force-cancel to the native host that drops the project lock so a new run can start. Use this when refreshing the tab did not free the project.',
860
- '向 native host 发送强制取消请求,释放当前 Overleaf 项目的占用,让新的任务可以启动。刷新页面没用时使用。'
861
- );
862
- button.addEventListener('click', event => {
863
- event.stopPropagation();
864
- button.disabled = true;
865
- button.textContent = tx('Releasing…', '正在释放…');
866
- forceCancelStuckTaskForCurrentProject().then(result => {
867
- if (result?.ok) {
868
- button.textContent = tx('Force-released — you can retry the run.', '已释放,可以重试本轮任务。');
869
- return;
870
- }
871
- // The helper resolves (never throws) with a non-ok result when the
872
- // request was not delivered. Report the real outcome instead of a
873
- // blanket success, and re-enable the button so the user can retry.
874
- button.disabled = false;
875
- button.textContent = tx('Release failed — refresh the tab, then try again.', '释放失败,请刷新页面后重试。');
876
- }).catch(() => {
877
- button.disabled = false;
878
- button.textContent = tx('Release failed — refresh the tab, then try again.', '释放失败,请刷新页面后重试。');
879
- });
880
- });
881
- report.append(button);
882
- }
883
-
884
837
  // A run belongs to the tracked-change lifecycle iff trackedChangeStatus is set
885
838
  // or it still has tracked-change refs. Every other run is a legacy-undo run.
886
839
  function isTrackedChangeLifecycleRun(run) {
@@ -956,13 +909,16 @@
956
909
  }
957
910
 
958
911
  button.hidden = false;
959
- button.disabled = run.undoStatus === 'running' || run.undoStatus === 'applied';
960
- button.textContent = run.undoStatus === 'applied'
912
+ const inFlight = trackedChangeInFlight?.get(run.id) === 'reject';
913
+ const applied = run.undoStatus === 'applied' && !inFlight;
914
+ button.disabled = inFlight || (run.undoStatus === 'running' && !run.undoRecoveryCheckRequired) || run.undoStatus === 'applied';
915
+ button.textContent = applied
961
916
  ? tr('undoApplied')
962
917
  : (run.partialWriteback ? tr('undoPartialRun') : tr('undoRun'));
963
- button.title = run.undoStatus === 'applied'
918
+ button.title = applied
964
919
  ? tr('undoAppliedTitle')
965
920
  : (run.partialWriteback ? tr('undoPartialRunTitle') : tr('undoRunTitle'));
921
+ if (run.undoRecoveryUnavailable) button.title = tx('Undo recovery checks are unavailable. Reload this page after the connection recovers.', '撤销恢复检查暂不可用,请在连接恢复后重新加载此页面。');
966
922
  button.addEventListener('click', event => {
967
923
  event.stopPropagation();
968
924
  undoRun(run.id);
@@ -1158,7 +1114,43 @@
1158
1114
  return true;
1159
1115
  }
1160
1116
 
1117
+ function refreshLocale() {
1118
+ const log = getPanel()?.querySelector('[data-log]');
1119
+ if (!log) return;
1120
+ if (!getState().runs?.length) { renderRunHistory({ preserveScroll: true }); return; }
1121
+ const reading = scrollLayout?.snapshot() || { scrollTop: log.scrollTop };
1122
+ for (const run of getState().runs) {
1123
+ const existing = log.querySelector(`[data-run-id="${cssEscape(run.id)}"]`);
1124
+ if (!existing) continue;
1125
+ const current = getCurrentRunView();
1126
+ if (current?.root === existing) {
1127
+ // Streaming owners retain their DOM references and buffered model text.
1128
+ const status = existing.querySelector('[data-run-status]');
1129
+ if (status) status.textContent = getRunStatusText(run);
1130
+ configureAcceptButton(existing, run); configureUndoButton(existing, run);
1131
+ configureEditButton(existing, run); configureWritebackButton(existing, run);
1132
+ RunResultActions.configureResultActions(existing, run);
1133
+ const reportEvent = (run.events || []).filter(event => event.kind === 'report').at(-1);
1134
+ const report = current.report || existing.querySelector('[data-run-report]');
1135
+ if (reportEvent && report) report.replaceChildren(renderCompletionReport(reportEvent, displayLocalizedRun(run)));
1136
+ activitySummary?.update(current);
1137
+ if (!activitySummary) RunGuidanceView.reconcileGuidance?.(existing.querySelector('[data-run-guidance]'),
1138
+ (run.events || []).concat(getPendingGuidance(run)), tx, { active: run.status === 'running' });
1139
+ continue;
1140
+ }
1141
+ const open = existing.querySelector('[data-run-process]')?.open;
1142
+ const next = renderRunCard(run);
1143
+ const process = next.querySelector('[data-run-process]');
1144
+ if (process && typeof open === 'boolean') process.open = open;
1145
+ existing.replaceWith(next);
1146
+ }
1147
+ log.scrollTop = reading.scrollTop;
1148
+ scrollLayout?.restore(reading);
1149
+ updateJumpToLatestButton(log);
1150
+ }
1151
+
1161
1152
  return {
1153
+ refreshLocale,
1162
1154
  refreshResolvedUndo: refreshRunCard,
1163
1155
  refreshActivitySummary: activitySummary?.update,
1164
1156
  appendActivityDetail,
@@ -1166,6 +1158,7 @@
1166
1158
  bindLogAutoFollow,
1167
1159
  bumpUnreadIfDetached,
1168
1160
  scrollLogToBottom,
1161
+ revealGuidance,
1169
1162
  collapseRunProcess,
1170
1163
  startRunElapsedTick,
1171
1164
  stopRunElapsedTick,
@@ -1184,5 +1177,5 @@
1184
1177
  };
1185
1178
  }
1186
1179
 
1187
- window.CodexOverleafRunTimelineView = { create };
1188
- })();
1180
+ return { create };
1181
+ });
@@ -15,11 +15,13 @@
15
15
  return JSON.parse(JSON.stringify(value));
16
16
  }
17
17
 
18
- async function applyReviewTransition({ getState, runId, transition, persist }) {
18
+ async function applyReviewTransition({ getState, runId, transition, persist, legacyUndo = false, assertCurrent }) {
19
19
  const targets = () => Array.from(new Set([
20
20
  ...(getState().runs || []),
21
21
  ...(getState().sessions || []).flatMap(session => session.runs || [])
22
22
  ])).filter(run => run.id === runId);
23
+ assertCurrent?.();
24
+ const capturedState = getState(), capturedSessionId = capturedState.activeSessionId;
23
25
  const records = targets();
24
26
  const sessions = (getState().sessions || []).filter(session =>
25
27
  (session.runs || []).some(run => run.id === runId));
@@ -31,12 +33,19 @@
31
33
  (Date.parse(session.lastActivityAt) || 0) + 1)).toISOString();
32
34
  session.lastActivityAt = session.updatedAt;
33
35
  }
36
+ const staged = records.map(record => JSON.stringify(record));
34
37
  try {
35
- await persist({ preserveRunActionPayload: true, reviewRunIds: [runId] });
38
+ await persist({ preserveRunActionPayload: true, reviewRunIds: [runId], requireDurable: true });
39
+ assertCurrent?.();
40
+ if (getState() !== capturedState || getState().activeSessionId !== capturedSessionId
41
+ || records.some(record => !targets().includes(record))) throw reviewPersistenceError();
36
42
  } catch (error) {
43
+ try { assertCurrent?.(); } catch (_stale) { throw error; }
37
44
  // The remote action may have succeeded. Keep its recovery data and expose uncertainty.
38
- for (const record of targets()) {
39
- Object.assign(record, cloneSnapshot(previous), { trackedChangeStatus: 'needs_review' });
45
+ for (const record of records) {
46
+ if (getState() !== capturedState || !targets().includes(record)
47
+ || JSON.stringify(record) !== staged[records.indexOf(record)]) continue;
48
+ Object.assign(record, cloneSnapshot(previous), legacyUndo ? { undoStatus: 'partial' } : { trackedChangeStatus: 'needs_review' });
40
49
  for (const field of ['settlement', 'settlementFacts']) {
41
50
  if (!Object.prototype.hasOwnProperty.call(previous, field)) delete record[field];
42
51
  }
@@ -51,6 +60,28 @@
51
60
  });
52
61
  }
53
62
 
63
+ function verifyRecoveredRuns(recoveredRuns, proposed, written) {
64
+ for (var target of recoveredRuns || []) {
65
+ var expectedSession = proposed.find(session => session.id === target.sessionId);
66
+ var storedSession = (written || []).find(session => session.id === target.sessionId
67
+ && session.projectId === expectedSession?.projectId
68
+ && session.accountScopeId === expectedSession?.accountScopeId);
69
+ var expected = expectedSession?.runs?.find(run => run.id === target.runId);
70
+ var stored = storedSession?.runs?.find(run => run.id === target.runId);
71
+ var retained = expected && stored
72
+ && expected.runProjectId === target.projectId && stored.runProjectId === target.projectId
73
+ && Number(stored.nativeEventSeq || 0) >= Number(expected.nativeEventSeq || 0)
74
+ && stored.codexThreadId === expected.codexThreadId
75
+ && stored.codexTurnId === expected.codexTurnId
76
+ && JSON.stringify(stored.interruptedDraft) === JSON.stringify(expected.interruptedDraft)
77
+ && (expected.events || []).every(event => (stored.events || [])
78
+ .some(saved => JSON.stringify(saved) === JSON.stringify(event)));
79
+ if (!retained) throw Object.assign(new Error('Recovered run was not durably saved; its replay journal is retained.'), {
80
+ code: 'recovery_state_not_persisted'
81
+ });
82
+ }
83
+ }
84
+
54
85
  async function persistPanelState(input) {
55
86
  var state = cloneSnapshot(input.state || {});
56
87
  var compactState = cloneSnapshot(input.compactState || {});
@@ -71,6 +102,12 @@
71
102
  code: 'stale_view'
72
103
  });
73
104
  }
105
+ if ((input.recoveryRuns || []).some(target => target.projectId !== projectId
106
+ || target.accountScopeId !== accountScopeId)) {
107
+ throw Object.assign(new Error('Journal recovery scope changed before persistence.'), {
108
+ code: 'recovery_scope_changed'
109
+ });
110
+ }
74
111
  var reviewRuns = (input.reviewRunIds || []).map(id =>
75
112
  (state.sessions || []).flatMap(session => session.runs || []).find(run => run.id === id));
76
113
  if (reviewRuns.some(run => !run)) throw reviewPersistenceError();
@@ -129,10 +166,11 @@
129
166
  focusFiles: Array.isArray(session.focusFiles) ? session.focusFiles : [],
130
167
  createdAt: session.createdAt,
131
168
  updatedAt: session.updatedAt,
132
- pendingInputs: (Array.isArray(session.pendingInputs) ? session.pendingInputs : [])
169
+ pendingInputs: (Array.isArray(session.pendingInputs) ? session.pendingInputs : [])
133
170
  .filter(function (item) { return !queueTombstones[item?.id]; })
134
171
  }), { preserveRunActionPayload: true });
135
172
  });
173
+ sessionRecords = StorageDb.compactSessionBatchActions?.(sessionRecords, { activeSessionId: state.activeSessionId }) || sessionRecords;
136
174
  var written = await input.SessionPersistence.writeSessions({
137
175
  Migration: Migration,
138
176
  StorageDb: StorageDb,
@@ -146,8 +184,15 @@
146
184
  writerId: input.persistenceContext?.writerId || ''
147
185
  });
148
186
  if (!reviewRuns.every(reviewRun => (written || []).some(session => (session.runs || []).some(run =>
149
- run.id === reviewRun.id && run.trackedChangeStatus === reviewRun.trackedChangeStatus
187
+ run.id === reviewRun.id
188
+ && (!reviewRun.trackedChangeStatus && ['applied', 'partial'].includes(reviewRun.undoStatus)
189
+ ? run.undoStatus === reviewRun.undoStatus && (reviewRun.undoStatus === 'applied'
190
+ || ['undoOperations', 'undoBaseFiles', 'undoExpectedFiles', 'undoTrackedChanges']
191
+ .every(field => JSON.stringify(run[field] || []) === JSON.stringify(reviewRun[field] || [])))
192
+ : run.trackedChangeStatus === reviewRun.trackedChangeStatus)
150
193
  )))) throw reviewPersistenceError();
194
+ verifyRecoveredRuns(input.recoveryRuns, sessionRecords, written);
195
+ return written;
151
196
  }
152
197
 
153
198
  return Object.freeze({ persistPanelState: persistPanelState, applyReviewTransition: applyReviewTransition });
@@ -293,7 +293,7 @@
293
293
  toolbar = null;
294
294
  }
295
295
 
296
- return { attach, capture, clear, render, start, destroy };
296
+ return { attach, capture, clear, render, start, destroy, getRevision: () => generation };
297
297
  }
298
298
 
299
299
  return { create, buildChip, renderSent };