codex-overleaf-link 2.4.0 → 2.5.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 (327) hide show
  1. package/README.md +272 -311
  2. package/README.zh-CN.md +287 -326
  3. package/extension/bootstrap/background.js +47 -10
  4. package/extension/bootstrap/manifest.template.json +9 -4
  5. package/extension/bootstrap/popup.html +10 -16
  6. package/extension/bootstrap/update.js +25 -2
  7. package/extension/runtime-manifest.json +3 -1
  8. package/extension/src/background.js +28 -3
  9. package/extension/src/backgroundSharedSessions.js +380 -0
  10. package/extension/src/backgroundUpdateCoordinator.js +442 -379
  11. package/extension/src/content/activeTurnControl.js +8 -0
  12. package/extension/src/content/assetTransferBroker.js +186 -12
  13. package/extension/src/content/changeHistoryView.js +175 -0
  14. package/extension/src/content/composerAttachments.js +24 -22
  15. package/extension/src/content/composerPanel.js +23 -8
  16. package/extension/src/content/contentRuntime.js +572 -571
  17. package/extension/src/content/diagnosticsController.js +1 -2
  18. package/extension/src/content/diagnosticsExport.js +222 -0
  19. package/extension/src/content/diagnosticsPanel.js +48 -19
  20. package/extension/src/content/diffReviewPanel.js +8 -5
  21. package/extension/src/content/generated/content.bundle.js +8974 -1781
  22. package/extension/src/content/generated/content.bundle.meta.json +22 -3
  23. package/extension/src/content/legacyGlobalRegistry.js +19 -1
  24. package/extension/src/content/localSkillsPanel.js +42 -7
  25. package/extension/src/content/markdownDomRenderer.js +9 -4
  26. package/extension/src/content/modelPicker.js +1 -1
  27. package/extension/src/content/pageBridgeClient.js +18 -3
  28. package/extension/src/content/panelMaintenance.js +51 -120
  29. package/extension/src/content/panelRenderer.js +4 -0
  30. package/extension/src/content/pendingInputView.js +109 -76
  31. package/extension/src/content/providerSettingsCoordinator.js +17 -6
  32. package/extension/src/content/providerSettingsDialog.js +661 -108
  33. package/extension/src/content/recentProjects.js +364 -356
  34. package/extension/src/content/runActivitySummary.js +631 -0
  35. package/extension/src/content/runController.js +3 -1
  36. package/extension/src/content/runFailureNotice.js +14 -1
  37. package/extension/src/content/runGuidanceController.js +4 -3
  38. package/extension/src/content/runPresence.js +306 -0
  39. package/extension/src/content/runQueueScheduler.js +2 -1
  40. package/extension/src/content/runResultActions.js +123 -3
  41. package/extension/src/content/runTimelineView.js +194 -164
  42. package/extension/src/content/scopedPersistenceCoordinator.js +2 -1
  43. package/extension/src/content/scopedPersistencePanelState.js +45 -2
  44. package/extension/src/content/selectionContextView.js +300 -0
  45. package/extension/src/content/sessionPersistence.js +10 -2
  46. package/extension/src/content/settingsPanel.js +93 -54
  47. package/extension/src/content/settingsWorkbench.js +374 -0
  48. package/extension/src/content/sharedSessionViewSync.js +72 -0
  49. package/extension/src/content/subagentActivityView.js +195 -0
  50. package/extension/src/content/trackedChangeCaptureController.js +7 -3
  51. package/extension/src/content/updateNotice.js +121 -50
  52. package/extension/src/content/writebackController.js +257 -0
  53. package/extension/src/content/writebackOrchestrator.js +146 -141
  54. package/extension/src/content/writebackPlan.js +231 -0
  55. package/extension/src/content/writebackRecovery.js +122 -0
  56. package/extension/src/content/writingStyleSettings.js +576 -0
  57. package/extension/src/content/writtenChangesView.js +152 -0
  58. package/extension/src/page/binaryAssetUploader.js +97 -19
  59. package/extension/src/page/overleafProjectSnapshot.js +1 -0
  60. package/extension/src/page/referenceProjects.js +148 -0
  61. package/extension/src/page/saveState.js +58 -3
  62. package/extension/src/page/snapshotRouter.js +44 -13
  63. package/extension/src/page/textCoordinates.js +102 -0
  64. package/extension/src/page/textFileCreator.js +350 -59
  65. package/extension/src/page/trackedChangeCapture.js +42 -56
  66. package/extension/src/page/trackedChangeOwnership.js +198 -0
  67. package/extension/src/page/trackedChangeReplay.js +164 -0
  68. package/extension/src/page/trackedChangesLifecycle.js +139 -150
  69. package/extension/src/page/treeOperations.js +36 -29
  70. package/extension/src/page/writebackReceiptJournal.js +112 -0
  71. package/extension/src/page/writebackRouter.js +176 -136
  72. package/extension/src/pageBridge.js +59 -101
  73. package/extension/src/popup.js +203 -59
  74. package/extension/src/shared/agentTranscript.js +18 -4
  75. package/extension/src/shared/auditRecords.js +71 -0
  76. package/extension/src/shared/changeHistoryModel.js +94 -0
  77. package/extension/src/shared/compatibility.js +1 -1
  78. package/extension/src/shared/compileAdapter.js +42 -0
  79. package/extension/src/shared/failureReasons.js +20 -0
  80. package/extension/src/shared/globalPreferences.js +1 -1
  81. package/extension/src/shared/i18n.js +187 -65
  82. package/extension/src/shared/managedUpdateProjection.js +6 -3
  83. package/extension/src/shared/pageRpcContract.js +7 -1
  84. package/extension/src/shared/providerProfiles.js +109 -0
  85. package/extension/src/shared/runActivityModel.js +361 -0
  86. package/extension/src/shared/runExecutionSnapshot.js +8 -2
  87. package/extension/src/shared/runExecutionSnapshotCodec.js +16 -4
  88. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +6 -3
  89. package/extension/src/shared/selectionContext.js +28 -0
  90. package/extension/src/shared/sessionState.js +97 -6
  91. package/extension/src/shared/sharedSessionBridge.js +288 -0
  92. package/extension/src/shared/storageDb.js +23 -55
  93. package/extension/src/shared/storageMigration.js +3 -0
  94. package/extension/src/shared/storageRunActions.js +54 -4
  95. package/extension/src/shared/storageValuePruning.js +40 -0
  96. package/extension/src/shared/subagentActivityModel.js +174 -0
  97. package/extension/src/shared/undoOperations.js +87 -0
  98. package/extension/src/shared/updateConsent.js +37 -8
  99. package/extension/src/shared/writebackIntent.js +62 -0
  100. package/extension/src/shared/writebackSettlement.js +29 -10
  101. package/extension/src/shared/writingStyle.js +33 -0
  102. package/extension/styles/panel.css +2136 -744
  103. package/extension/styles/popup.css +110 -0
  104. package/native-host/src/anthropicBridgeRequest.js +2 -1
  105. package/native-host/src/codexModelRecovery.js +107 -0
  106. package/native-host/src/codexPromptAssembly.js +2 -0
  107. package/native-host/src/codexProviderLaunch.js +3 -3
  108. package/native-host/src/codexRuntimeIdentity.js +1 -0
  109. package/native-host/src/codexSessionRunner.js +146 -177
  110. package/native-host/src/codexSessionTiming.js +71 -0
  111. package/native-host/src/managedInstall.js +5 -4
  112. package/native-host/src/nativeEnvironment.js +70 -0
  113. package/native-host/src/nativeQuotas.js +15 -8
  114. package/native-host/src/overleafWorkspaceRules.js +16 -0
  115. package/native-host/src/processStopBarrier.js +80 -0
  116. package/native-host/src/providerModelCatalog.js +4 -3
  117. package/native-host/src/providerProfile.js +13 -1
  118. package/native-host/src/providerReasoning.js +9 -5
  119. package/native-host/src/providerStore.js +4 -0
  120. package/native-host/src/readProgressGuard.js +84 -17
  121. package/native-host/src/releaseTransport.js +14 -5
  122. package/native-host/src/selectionScope.js +58 -0
  123. package/native-host/src/skills/parallel-subagents/SKILL.md +31 -3
  124. package/native-host/src/skills/writing-style-builder/SKILL.md +146 -0
  125. package/native-host/src/subagentBroker.js +102 -98
  126. package/native-host/src/subagentTelemetry.js +316 -0
  127. package/native-host/src/subagentWorkspace.js +103 -0
  128. package/native-host/src/subagentWorkspacePath.js +31 -0
  129. package/native-host/src/taskRunnerRuntime.js +55 -5
  130. package/native-host/src/turnAttachments.js +113 -0
  131. package/native-host/src/updateManager.js +122 -12
  132. package/native-host/src/updateNetworkControl.js +75 -0
  133. package/native-host/src/updateTrust.js +21 -2
  134. package/native-host/src/vendor/pdfjs/LICENSE +177 -0
  135. package/native-host/src/vendor/pdfjs/SOURCE.txt +3 -0
  136. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  137. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  138. package/native-host/src/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
  139. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  140. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  141. package/native-host/src/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
  142. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  143. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  144. package/native-host/src/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  145. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  146. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  147. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  148. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  149. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  150. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  151. package/native-host/src/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
  152. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  153. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  154. package/native-host/src/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
  155. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  156. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  157. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  158. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  159. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  160. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  161. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  162. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  163. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  164. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  165. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  166. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  167. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  168. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  169. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  170. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  171. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  172. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  173. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  174. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  175. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  176. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  177. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  178. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  179. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  180. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  181. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  182. package/native-host/src/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
  183. package/native-host/src/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
  184. package/native-host/src/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  185. package/native-host/src/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  186. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  187. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  188. package/native-host/src/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  189. package/native-host/src/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  190. package/native-host/src/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  191. package/native-host/src/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  192. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  193. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  194. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  195. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  196. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  197. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  198. package/native-host/src/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
  199. package/native-host/src/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
  200. package/native-host/src/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
  201. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  202. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  203. package/native-host/src/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
  204. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  205. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  206. package/native-host/src/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
  207. package/native-host/src/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
  208. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  209. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  210. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  211. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  212. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  213. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  214. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  215. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  216. package/native-host/src/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
  217. package/native-host/src/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
  218. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  219. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  220. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  221. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  222. package/native-host/src/vendor/pdfjs/cmaps/H.bcmap +0 -0
  223. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  224. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  225. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  226. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  227. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  228. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  229. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  230. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  231. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  232. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  233. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  234. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  235. package/native-host/src/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
  236. package/native-host/src/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
  237. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  238. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  239. package/native-host/src/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
  240. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  241. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  242. package/native-host/src/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
  243. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  244. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  245. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  246. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  247. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  248. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  249. package/native-host/src/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
  250. package/native-host/src/vendor/pdfjs/cmaps/LICENSE +36 -0
  251. package/native-host/src/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
  252. package/native-host/src/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
  253. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  254. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  255. package/native-host/src/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
  256. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  257. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  258. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  259. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  260. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  261. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  262. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  263. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  264. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  265. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  266. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  267. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  268. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  269. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  270. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  271. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  272. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  273. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  274. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  275. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  276. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  277. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  278. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  279. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  280. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  281. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  282. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  283. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  284. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  285. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  286. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  287. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  288. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  289. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  290. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  291. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  292. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  293. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  294. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  295. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  296. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  297. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  298. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  299. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  300. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  301. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  302. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  303. package/native-host/src/vendor/pdfjs/cmaps/V.bcmap +0 -0
  304. package/native-host/src/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  305. package/native-host/src/vendor/pdfjs/pdf.mjs +26740 -0
  306. package/native-host/src/vendor/pdfjs/pdf.worker.mjs +62201 -0
  307. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  308. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  309. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  310. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  311. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  312. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  313. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  314. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  315. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  316. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  317. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  318. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +102 -0
  319. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  320. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  321. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  322. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  323. package/native-host/src/writingStyleEvidence.js +102 -0
  324. package/native-host/src/writingStylePdfWorker.js +35 -0
  325. package/native-host/src/writingStyleProfile.js +165 -0
  326. package/native-host/src/writingStyleRuntime.js +447 -0
  327. package/package.json +1 -1
@@ -99,6 +99,7 @@
99
99
  const {
100
100
  buildExpectedFilesAfterOperations,
101
101
  buildSnapshotRestoreUndo,
102
+ buildCreatedFileUndoCheckpoint,
102
103
  buildUndoCheckpoint
103
104
  } = Modules.UndoOperations;
104
105
  const { buildHumanCompletionReport, mapAgentEventToActivity, stripEmptyHtmlCommentPlaceholders,
@@ -215,7 +216,7 @@
215
216
  refreshStorageUsageSummary: () => refreshStorageUsageSummary(),
216
217
  renderAuditHistoryPanel: () => renderAuditHistoryPanel(),
217
218
  renderRecentProjectsVariant: (...args) => renderRecentProjectsVariant(...args),
218
- saveStateSoon: () => saveStateSoon(),
219
+ saveStateSoon: delay => saveStateSoon(delay),
219
220
  updateGlobalPreferences: patch => getGlobalPreferences().update(patch),
220
221
  setState: next => { state = next; },
221
222
  tr,
@@ -365,6 +366,9 @@
365
366
  // whose deps reference the recovery handlers.
366
367
  const panelMaintenance = Modules.PanelMaintenance.create({
367
368
  StorageDb: Modules.StorageDb,
369
+ ChangeHistoryView: Modules.ChangeHistoryView,
370
+ getAccountScopeId: () => cachedAccountScopeId || '',
371
+ sanitizeText: sanitizeAssistantVisibleText,
368
372
  tx,
369
373
  showPluginToast: (...args) => showPluginToast(...args),
370
374
  showPluginConfirm: (...args) => showPluginConfirm(...args),
@@ -384,7 +388,7 @@
384
388
  setState: next => { state = next; },
385
389
  getCurrentRunView: () => currentRunView,
386
390
  getSettingsPanelInstance: () => settingsPanelInstance,
387
- prepareRetryReplacement: run => prepareRetryReplacement(run)
391
+ prepareRetryReplacement: (run, task) => prepareRetryReplacement(run, task)
388
392
  });
389
393
  const {
390
394
  showUndoFileSelection,
@@ -415,6 +419,13 @@
415
419
  forkRunFromNode: sessionForkController.forkRunFromNode
416
420
  });
417
421
  const runTimelineView = Modules.RunTimelineView.create({
422
+ RunActivitySummary: Modules.RunActivitySummary,
423
+ RunPresence: Modules.RunPresence,
424
+ // recentProjects is composed below; the empty state only reads it after startup.
425
+ getProjectName: () => { try { return recentProjects.readCurrentProjectNameFromDom(); } catch { return ''; } },
426
+ getCurrentProjectId: () => getCurrentProjectId(),
427
+ RunActivityModel: Modules.RunActivityModel,
428
+ SubagentActivityView: Modules.SubagentActivityView,
418
429
  RunGuidanceView: Modules.RunGuidanceView,
419
430
  RunResultActions: runResultActions,
420
431
  RunScrollLayout: Modules.RunScrollLayout,
@@ -428,6 +439,7 @@
428
439
  formatEventDetail,
429
440
  formatEventTime,
430
441
  renderAttachmentPreviewList,
442
+ renderSentSelection: Modules.SelectionContextView.renderSent,
431
443
  formatModeLabel,
432
444
  undoRun,
433
445
  acceptRun,
@@ -443,6 +455,8 @@
443
455
  getState: () => state,
444
456
  getCurrentRunView: () => currentRunView,
445
457
  refillComposerForRetry,
458
+ canEditRun: run => runController.isRetryReplacementEligible(run, WritebackSettlement.projectRunSettlement(run)),
459
+ retryWriteback: retryRunWriteback,
446
460
  showNativeSetupGuidance: () => showNativeUpdateGuidanceModal({}),
447
461
  openProjectFileForFailure,
448
462
  openStorageSettings,
@@ -465,6 +479,8 @@
465
479
  upsertStreamEvent,
466
480
  renderCompletionReport,
467
481
  isTrackedChangeLifecycleRun,
482
+ configureEditButton,
483
+ configureWritebackButton,
468
484
  configureUndoButton,
469
485
  configureAcceptButton
470
486
  } = runTimelineView;
@@ -536,7 +552,80 @@
536
552
  // lives in writebackOrchestrator.js. Every dep below is either a hoisted
537
553
  // function declaration, an already-initialized const above, or a lazy
538
554
  // accessor thunk — safe in the top-of-init() wiring zone.
555
+ const selectionContextView = Modules.SelectionContextView.create({
556
+ getPanel: () => panel,
557
+ getState: () => state,
558
+ getProjectId: getCurrentProjectId,
559
+ openComposer: () => ensurePanelOpen(),
560
+ setSelection: (value, options = {}) => {
561
+ if (options.edit && panel?.querySelector('[data-task]')) readPanelInputs();
562
+ state = updateActiveSession(state, {
563
+ selectionContext: value,
564
+ ...(options.edit ? { mode: 'auto' } : {})
565
+ });
566
+ if (options.edit && panel) {
567
+ const modeInput = panel.querySelector('[data-mode]');
568
+ if (modeInput) modeInput.value = 'auto';
569
+ syncModeControls();
570
+ }
571
+ saveStateSoon();
572
+ },
573
+ callPageBridge, tx, toast: showPluginToast
574
+ });
575
+ selectionContextView.start();
576
+ const writingStyleSettings = Modules.WritingStyleSettings.create({
577
+ tx,
578
+ getScope: () => {
579
+ const projectId = getCurrentProjectId();
580
+ return cachedAccountScopeId && projectId ? { accountScopeId: cachedAccountScopeId, projectId } : null;
581
+ },
582
+ request: sendBackgroundNative,
583
+ getReferenceSnapshot: async (projectId, requestId) => {
584
+ const result = await callPageBridge('getReferenceProjectSnapshot', { projectId, requestId });
585
+ if (!result?.ok) throw Object.assign(new Error(result?.error || result?.reason || tx(
586
+ 'The reference project could not be read.', '参考项目未能读取。')), { code: result?.code });
587
+ return result;
588
+ },
589
+ cancelReferenceRead: async requestId => {
590
+ const result = await callPageBridge('cancelReferenceRead', { requestId });
591
+ if (!result?.ok) throw new Error(result?.error || tx('The reference read could not be cancelled.', '参考资料读取未能取消。'));
592
+ return result;
593
+ },
594
+ isRunning: () => Boolean(currentRunView),
595
+ listProjects: async () => {
596
+ const result = await callPageBridge('listReferenceProjects', {});
597
+ if (!result?.ok) throw new Error(result?.error || tx(
598
+ 'The Overleaf project directory could not be loaded.', 'Overleaf 项目目录未能加载。'));
599
+ return result.projects;
600
+ },
601
+ getModelSettings: async () => {
602
+ await providerSettingsCoordinator.ensureLoaded();
603
+ if (panel?.querySelector('[data-task]')) readPanelInputs();
604
+ return {
605
+ model: state.model,
606
+ reasoningEffort: state.reasoningEffort,
607
+ speedTier: state.speedTier,
608
+ providerSelection: providerSettingsCoordinator.getRunSelection(state.providerId || 'builtin'),
609
+ locale: getLocale()
610
+ };
611
+ },
612
+ startBuild: async params => {
613
+ const gate = await nativeCompatibilityController.ensureForMethod('codex.run');
614
+ if (!gate.ok) throw new Error(gate.response?.error?.message || 'Native host update required.');
615
+ const id = crypto.randomUUID();
616
+ const promise = nativeChannel.sendBackgroundNative(nativeCompatibilityController.attachEvidence({
617
+ id, method: 'codex.run', params
618
+ }, gate.compatibility));
619
+ return { id, promise };
620
+ }
621
+ });
539
622
  const assetTransferBroker = Modules.AssetTransferBroker.create({ callPageBridge, sendBackgroundNative });
623
+ const { partitionUnsafeProjectPathOperations, normalizeOperationProjectPaths, getInvalidOperationProjectPath,
624
+ evaluateGovernedOperations, buildGovernanceSkippedApplyResult, filterSyncChangesByOperations, mergeApplyResultSkipped,
625
+ confirmBinaryOperations, buildAuditSummaryFromApply, summarizeOperationForAudit } = Modules.WritebackPlan.create({
626
+ tx, tr, normalizeSafeProjectPath, GovernanceRules, getGovernanceRulesForCurrentProject,
627
+ showPluginConfirm, formatOperationFiles, getAppliedEntries, getSkippedEntries
628
+ });
540
629
  const writebackOrchestrator = Modules.WritebackOrchestrator.create({
541
630
  tr,
542
631
  tx,
@@ -577,6 +666,8 @@
577
666
  filterSyncChangesByOperations,
578
667
  writebackController,
579
668
  assetTransferBroker,
669
+ stageWritebackRecovery,
670
+ settleWritebackRecovery,
580
671
  writebackSettlement: WritebackSettlement,
581
672
  compileAdapter: Modules.CompileAdapter,
582
673
  RUN_SNAPSHOT_ZIP_TIMEOUT_MS,
@@ -774,6 +865,7 @@
774
865
  getRunEvents: () => currentRunView?.events,
775
866
  appendRunEvent,
776
867
  scrollLogToBottom,
868
+ renderWrittenChanges: changes => Modules.WrittenChangesView.render(changes, { document, tx }),
777
869
  onRejectedHunks: summaries => {
778
870
  if (currentRunView && Array.isArray(summaries) && summaries.length) {
779
871
  currentRunView.rejectedHunks = summaries;
@@ -922,6 +1014,15 @@
922
1014
  rolled_back: tx('The update failed its health check, so the previous version was restored.', '更新未通过健康检查,已自动恢复上一版本。')
923
1015
  }[state] || '')
924
1016
  });
1017
+ const diagnosticsExporter = Modules.DiagnosticsExport.create({
1018
+ root, window, document, Blob, URL, TextEncoder, AuditRecords, StorageDb: Modules.StorageDb,
1019
+ CodexOverleafCompatibility, getState: () => state, getCurrentRunView: () => currentRunView,
1020
+ getCurrentProjectId, findRunRecord, getMirrorFreshness, getExtensionCompatibilityMetadata,
1021
+ getModelDiscovery, getGovernanceRulesForCurrentProject, sendBackgroundNative,
1022
+ fallbackNativeCompatibility, isNativeCompatibilityCompatible, getNativeCompatibilityClassification,
1023
+ callPageBridge, showPluginToast, tr, RUN_SNAPSHOT_ZIP_TIMEOUT_MS
1024
+ });
1025
+
925
1026
  root[RUNTIME_INSTALLED_FLAG] = true;
926
1027
  root[RUNTIME_STATE_KEY] = { ok: true, alreadyInstalled: false };
927
1028
 
@@ -948,7 +1049,6 @@
948
1049
  if (message?.type === 'codex-overleaf/toggle-launcher') {
949
1050
  const visible = !PanelRenderer.isLauncherVisible(panelRendererInstance);
950
1051
  PanelRenderer.setLauncherVisible(panelRendererInstance, visible, { persist: true });
951
- if (!visible) closePanel();
952
1052
  sendResponse?.(getPanelStateResponse());
953
1053
  return;
954
1054
  }
@@ -969,7 +1069,7 @@
969
1069
  }
970
1070
  });
971
1071
 
972
- exposeSmokeHelper();
1072
+ diagnosticsExporter.exposeSmokeHelper();
973
1073
  init().catch(error => {
974
1074
  root[RUNTIME_STATE_KEY] = {
975
1075
  ok: false,
@@ -981,11 +1081,19 @@
981
1081
  throw error;
982
1082
  });
983
1083
 
1084
+ function normalizeLoadedPanelState(input) {
1085
+ // Shared history may belong to a live run in another tab. Loading it is
1086
+ // read-only; only an owner-lost journal may mark that run interrupted.
1087
+ return normalizePanelState(getGlobalPreferences().overlay(input), {
1088
+ restoreRunningRuns: Modules.StorageDb.sharedSessionsEnabled?.() !== true, recoverSettledRuns: true
1089
+ });
1090
+ }
1091
+
984
1092
  async function init() {
985
1093
  await getGlobalPreferences().initialize();
986
1094
  await refreshAccountScopeId();
987
1095
  storageKey = getProjectStorageKey(LEGACY_STORAGE_KEY, window.location.href);
988
- state = normalizePanelState(getGlobalPreferences().overlay(await loadStoredState()), { restoreRunningRuns: true });
1096
+ state = normalizeLoadedPanelState(await loadStoredState());
989
1097
  initializeRunQueueScheduler();
990
1098
  recoverInterruptedRunJournals()
991
1099
  .then(() => applyStateToPanel())
@@ -1067,129 +1175,9 @@
1067
1175
  : '';
1068
1176
  }
1069
1177
 
1070
- function exposeSmokeHelper() {
1071
- const helper = Object.freeze({
1072
- probeNative: smokeProbeNative,
1073
- probeProject: smokeProbeProject,
1074
- getProjectSnapshotMetrics: smokeProbeProject
1075
- });
1076
- try {
1077
- Object.defineProperty(globalThis, 'CodexOverleafSmoke', {
1078
- configurable: true,
1079
- enumerable: false,
1080
- value: helper
1081
- });
1082
- } catch (_error) {
1083
- globalThis.CodexOverleafSmoke = helper;
1084
- }
1085
- }
1086
1178
 
1087
- async function smokeProbeNative() {
1088
- try {
1089
- const params = CodexOverleafCompatibility?.buildBridgePingParams
1090
- ? CodexOverleafCompatibility.buildBridgePingParams(getExtensionCompatibilityMetadata())
1091
- : {};
1092
- const response = await sendBackgroundNative({ method: 'bridge.ping', params });
1093
- const compatibility = CodexOverleafCompatibility?.evaluateNativeCompatibility
1094
- ? CodexOverleafCompatibility.evaluateNativeCompatibility(response, getExtensionCompatibilityMetadata())
1095
- : fallbackNativeCompatibility(response);
1096
- return {
1097
- supported: true,
1098
- ok: response?.ok === true && isNativeCompatibilityCompatible(compatibility),
1099
- status: compatibility?.status || (response?.ok ? 'ok' : 'native_missing'),
1100
- classification: getNativeCompatibilityClassification(compatibility),
1101
- errorCode: response?.error?.code || compatibility?.status || '',
1102
- nativeCompatibility: summarizeSmokeNativeCompatibility(compatibility, response)
1103
- };
1104
- } catch (_error) {
1105
- return {
1106
- supported: true,
1107
- ok: false,
1108
- status: 'native_probe_failed',
1109
- errorCode: 'native_probe_failed'
1110
- };
1111
- }
1112
- }
1113
1179
 
1114
- async function smokeProbeProject(options = {}) {
1115
- try {
1116
- const project = await callPageBridge('getProjectSnapshot', {
1117
- force: Boolean(options.force),
1118
- preferLightweight: true,
1119
- allowZipFallback: true,
1120
- allowEditorNavigation: false,
1121
- requireFullProject: false,
1122
- includeBinaryFiles: true,
1123
- includeContent: false,
1124
- zipTimeoutMs: RUN_SNAPSHOT_ZIP_TIMEOUT_MS
1125
- });
1126
- const files = Array.isArray(project?.files) ? project.files : [];
1127
- const skipped = Array.isArray(project?.capabilities?.skipped) ? project.capabilities.skipped : [];
1128
- const bytes = summarizeSmokeProjectBytes(files);
1129
- const ok = project?.ok !== false && files.length > 0;
1130
- return {
1131
- supported: true,
1132
- ok,
1133
- status: ok ? 'ok' : 'project_snapshot_unavailable',
1134
- errorCode: ok ? '' : project?.code || 'project_snapshot_unavailable',
1135
- counts: {
1136
- files: files.length,
1137
- skipped: skipped.length
1138
- },
1139
- bytes,
1140
- method: project?.capabilities?.method || ''
1141
- };
1142
- } catch (_error) {
1143
- return {
1144
- supported: true,
1145
- ok: false,
1146
- status: 'project_probe_failed',
1147
- errorCode: 'project_probe_failed',
1148
- counts: {
1149
- files: 0,
1150
- skipped: 0
1151
- },
1152
- bytes: {
1153
- text: 0,
1154
- binary: 0
1155
- }
1156
- };
1157
- }
1158
- }
1159
1180
 
1160
- function summarizeSmokeNativeCompatibility(compatibility = {}, response = {}) {
1161
- const native = compatibility?.native || response?.result || {};
1162
- return {
1163
- status: compatibility?.status || (response?.ok ? 'ok' : 'native_missing'),
1164
- classification: getNativeCompatibilityClassification(compatibility),
1165
- nativeVersion: compatibility?.nativeVersion || native.version || '',
1166
- version: compatibility?.version || native.version || '',
1167
- minimumNativeVersion: compatibility?.minimumNativeVersion || compatibility?.minNativeVersion || '',
1168
- protocolVersion: compatibility?.protocolVersion || native.protocolVersion || '',
1169
- supportedProtocol: compatibility?.supportedProtocol || native.supportedProtocol || ''
1170
- };
1171
- }
1172
-
1173
- function summarizeSmokeProjectBytes(files = []) {
1174
- return files.reduce((bytes, file) => {
1175
- let size = Number(file?.size || file?.byteLength || 0);
1176
- if ((!Number.isFinite(size) || size <= 0) && typeof file?.content === 'string') {
1177
- size = new TextEncoder().encode(file.content).byteLength;
1178
- }
1179
- if (!Number.isFinite(size) || size <= 0) {
1180
- return bytes;
1181
- }
1182
- if (file?.kind === 'binary') {
1183
- bytes.binary += size;
1184
- } else {
1185
- bytes.text += size;
1186
- }
1187
- return bytes;
1188
- }, {
1189
- text: 0,
1190
- binary: 0
1191
- });
1192
- }
1193
1181
 
1194
1182
  function listSeparator() {
1195
1183
  return getLocale() === 'zh' ? '、' : ', ';
@@ -1209,7 +1197,6 @@
1209
1197
  callbacks: {
1210
1198
  onLauncherToggle: () => ensurePanelOpen(),
1211
1199
  onClosePanel: () => closePanel(),
1212
- onLauncherVisibilityChange: visible => { if (!visible) closePanel(); },
1213
1200
  onRefresh: () => refreshProbe({ userInitiated: true }),
1214
1201
  onNewSession: () => startNewSession(),
1215
1202
  onChangeHistory: () => openChangeHistory(),
@@ -1285,6 +1272,7 @@
1285
1272
  onOtToggleClick: handleExperimentalOtToggleClick
1286
1273
  }
1287
1274
  });
1275
+ writingStyleSettings.mount(panel);
1288
1276
  providerSettingsCoordinator.refreshSummary().catch(() => {});
1289
1277
 
1290
1278
  sessionPanelInstance = SessionPanel.create({
@@ -1476,7 +1464,7 @@
1476
1464
  setElementTitleAndAria('[data-context-refresh]', tr('refreshFileList'), tr('refreshFileList'));
1477
1465
  setElementTitleAndAria('[data-reasoning]', tr('reasoningLabel'), tr('reasoningLabel'));
1478
1466
  setElementTitleAndAria('[data-speed]', tr('speedLabel'), tr('speedLabel'));
1479
- setElementTitleAndAria('[data-run]', currentRunView ? tr('queueNextInput') : tr('send'), currentRunView ? tr('queueNextInput') : tr('send'));
1467
+ syncComposerSendAvailability();
1480
1468
  setElementTitleAndAria('[data-stop-run]', tr('cancelRun'), tr('cancelRun'));
1481
1469
 
1482
1470
  const actions = panel.querySelector('.codex-vscode-head-actions');
@@ -1525,10 +1513,8 @@
1525
1513
  if (contextStatus && !contextStatus.dataset.customStatus) {
1526
1514
  contextStatus.textContent = tr('contextStatus');
1527
1515
  }
1528
- const emptyRunLabel = panel.querySelector('.empty-runs div');
1529
- if (emptyRunLabel) {
1530
- emptyRunLabel.textContent = tr('emptyRunLabel');
1531
- }
1516
+ // Re-render rather than patch text: the first div is the animated mark.
1517
+ if (panel.querySelector('.empty-runs')) renderRunHistory();
1532
1518
 
1533
1519
  syncModeControls();
1534
1520
  updateOtStatusDisplay();
@@ -1537,14 +1523,7 @@
1537
1523
  updateModelDisplay();
1538
1524
  renderSessionList();
1539
1525
  renderContextSelection();
1540
- const probeStatus = panel.querySelector('[data-probe-status]');
1541
- if (probeStatus?.dataset.refreshing === 'true') probeStatus.textContent = tr('refreshProbeLoading');
1542
- else if (probeStatus && probeStatusSnapshot?.projectId === getCurrentProjectId()) {
1543
- const { failed, probe, userInitiated } = probeStatusSnapshot;
1544
- const text = failed ? tr('refreshProbeFailed') : formatProbeStatusBar(probe);
1545
- probeStatus.textContent = userInitiated && !failed ? tr('refreshProbeDone', { status: text }) : text;
1546
- if (!failed) updateExistingProbeNotice(probe);
1547
- }
1526
+ writingStyleSettings.sync();
1548
1527
  updateSkillsEntrySummary();
1549
1528
  providerSettingsCoordinator.renderSummary();
1550
1529
  if (settingsPanelInstance?.container?.querySelector('[data-storage-card]')?.open) refreshStorageUsageSummary();
@@ -1603,88 +1582,14 @@
1603
1582
  }
1604
1583
 
1605
1584
 
1606
- async function exportDiagnosticsBundle() {
1607
- if (!AuditRecords?.buildDiagnosticBundle) {
1608
- throw new Error('Audit diagnostics helper is unavailable');
1609
- }
1610
- const [auditLogs, mirror, nativeDiagnostics] = await Promise.all([
1611
- getRecentAuditLogsForCurrentProject(),
1612
- getMirrorFreshness().catch(error => ({ status: 'unavailable', errorCode: error.message })),
1613
- getNativeDiagnosticsSummaryForBundle()
1614
- ]);
1615
- const bundle = AuditRecords.buildDiagnosticBundle({
1616
- excludeContent: true,
1617
- compatibility: {
1618
- extension: getExtensionCompatibilityMetadata(),
1619
- modelDiscovery: getModelDiscovery()
1620
- },
1621
- platform: nativeDiagnostics.platform,
1622
- nativeEnvironment: nativeDiagnostics.nativeEnvironment,
1623
- mirror,
1624
- auditLogs,
1625
- run: currentRunView ? {
1626
- id: currentRunView.recordId || '',
1627
- status: 'running'
1628
- } : {},
1629
- governance: getGovernanceRulesForCurrentProject(),
1630
- projectId: getCurrentProjectId()
1631
- });
1632
- const blob = new Blob([JSON.stringify(bundle, null, 2)], { type: 'application/json' });
1633
- const url = URL.createObjectURL(blob);
1634
- const link = document.createElement('a');
1635
- link.href = url;
1636
- link.download = `codex-overleaf-diagnostics-${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
1637
- document.body.append(link);
1638
- link.click();
1639
- link.remove();
1640
- window.setTimeout(() => URL.revokeObjectURL(url), 1000);
1641
- showPluginToast(tr('diagnosticsExportDone'), { status: 'completed' });
1642
- }
1643
-
1644
- async function getNativeDiagnosticsSummaryForBundle() {
1645
- try {
1646
- const params = CodexOverleafCompatibility?.buildBridgePingParams
1647
- ? CodexOverleafCompatibility.buildBridgePingParams(getExtensionCompatibilityMetadata())
1648
- : {};
1649
- const response = await sendBackgroundNative({ method: 'bridge.ping', params });
1650
- if (!response?.ok) {
1651
- const errorCode = response?.error?.code || 'native_unavailable';
1652
- return {
1653
- platform: { status: 'unavailable', errorCode },
1654
- nativeEnvironment: { status: 'unavailable', errorCode }
1655
- };
1656
- }
1657
- return {
1658
- platform: {
1659
- host: response.result?.host || '',
1660
- platform: response.result?.platform || '',
1661
- version: response.result?.version || '',
1662
- protocolVersion: response.result?.protocolVersion || ''
1663
- },
1664
- nativeEnvironment: response.result?.environment || {}
1665
- };
1666
- } catch (error) {
1667
- const errorCode = error?.message || 'native_unavailable';
1668
- return {
1669
- platform: { status: 'unavailable', errorCode },
1670
- nativeEnvironment: { status: 'unavailable', errorCode }
1671
- };
1672
- }
1673
- }
1674
1585
 
1675
- async function getRecentAuditLogsForCurrentProject(limit = 12) {
1676
- const StorageDb = Modules.StorageDb;
1677
- if (!StorageDb?.getAllByIndex) {
1678
- return [];
1679
- }
1680
- const records = await StorageDb.getAllByIndex('auditLogs', 'projectId', getCurrentProjectId());
1681
- return (records || [])
1682
- .slice()
1683
- .sort((left, right) => String(right.createdAt || '').localeCompare(String(left.createdAt || '')))
1684
- .slice(0, limit);
1685
- }
1686
1586
 
1687
1587
 
1588
+
1589
+ function exportDiagnosticsBundle() {
1590
+ return diagnosticsExporter.exportDiagnosticsBundle();
1591
+ }
1592
+
1688
1593
  function isContextTrayClickTarget(target) {
1689
1594
  return contextTrayController.isContextTrayClickTarget(target);
1690
1595
  }
@@ -1796,6 +1701,7 @@
1796
1701
  appendLog(tx('Enter a task first.', '请先输入任务。'));
1797
1702
  return;
1798
1703
  }
1704
+ const submittedWritingStyleScope = { accountScopeId: cachedAccountScopeId, projectId: getCurrentProjectId() };
1799
1705
  const submittedPanelState = {
1800
1706
  mode: state.mode,
1801
1707
  providerId: state.providerId,
@@ -1804,13 +1710,21 @@
1804
1710
  speedTier: state.speedTier,
1805
1711
  autoRecompile: state.autoRecompile !== false,
1806
1712
  requireReviewing: state.requireReviewing === true,
1807
- focusFiles: getActiveFocusFiles()
1713
+ focusFiles: getActiveFocusFiles(),
1714
+ selectionContext: selectionContextView.capture()
1808
1715
  };
1809
1716
  const submittedCustomInstructions = getCustomInstructionsForCurrentProject();
1810
1717
  const submittedSkillLoadingSettings = getSkillLoadingSettings();
1811
1718
  const submittedAttachments = getComposerAttachmentsForRun();
1812
1719
  const submittedSkillInvocation = getComposerSkillInvocationForRun();
1813
1720
  await providerSettingsCoordinator.ensureLoaded();
1721
+ if (!queuedInput) await writingStyleSettings.ensureLoaded(true);
1722
+ if (submittedWritingStyleScope.accountScopeId !== cachedAccountScopeId
1723
+ || submittedWritingStyleScope.projectId !== getCurrentProjectId()) {
1724
+ throw new Error(tx('The active project changed before submission. Submit the task again.',
1725
+ '提交前活动项目已改变,请重新提交任务。'));
1726
+ }
1727
+ submittedPanelState.writingStyle = queuedInput ? null : writingStyleSettings.capture();
1814
1728
  const executionSnapshot = queuedInput
1815
1729
  ? RunExecutionSnapshot.resolveForExecution(
1816
1730
  RunExecutionSnapshot.fromQueuePayload(queuedInput.payload),
@@ -2156,9 +2070,10 @@
2156
2070
  submittedMode
2157
2071
  }));
2158
2072
  } else {
2159
- appendRunEvent({ title: tx('Cancelled: user chose not to create a new thread.', '已取消:用户选择不新建线程。'), status: 'rejected' });
2160
- await finalizeAuditRecord(runAuditDraft, { resultStatus: 'rejected' });
2161
- await finishRunView(tx('Cancelled', '已取消'), 'rejected');
2073
+ appendRunEvent({ title: tx('Cancelled: user chose not to create a new thread.', '已取消:用户选择不新建线程。'), status: 'info',
2074
+ activity: { v: 1, kind: 'lifecycle', state: 'cancelled', target: 'cancelled' } });
2075
+ await finalizeAuditRecord(runAuditDraft, { resultStatus: 'cancelled' });
2076
+ await finishRunView(tx('Cancelled', '已取消'), 'cancelled');
2162
2077
  return;
2163
2078
  }
2164
2079
  }
@@ -2166,7 +2081,7 @@
2166
2081
  if (!response.ok) {
2167
2082
  if (runCancellationRequested || isRunCancellationError(response.error)) {
2168
2083
  appendRunCancelledReport();
2169
- await finishRunView(tx('Cancelled', '已中断'), 'rejected');
2084
+ await finishRunView(tx('Cancelled', '已取消'), 'cancelled');
2170
2085
  void finalizeAuditRecord(runAuditDraft, { resultStatus: 'cancelled' });
2171
2086
  return;
2172
2087
  }
@@ -2420,7 +2335,7 @@
2420
2335
  } catch (error) {
2421
2336
  if (runCancellationRequested || isRunCancellationError(error)) {
2422
2337
  appendRunCancelledReport();
2423
- await finishRunView(tx('Cancelled', '已中断'), 'rejected');
2338
+ await finishRunView(tx('Cancelled', '已取消'), 'cancelled');
2424
2339
  void finalizeAuditRecord(runAuditDraft, { resultStatus: 'cancelled' });
2425
2340
  return;
2426
2341
  }
@@ -2544,7 +2459,7 @@
2544
2459
  if (runCancellationRequested || isRunCancellationError(response.error)) {
2545
2460
  appendRunCancelledReport();
2546
2461
  await finalizeAuditRecord(runAuditDraft, { resultStatus: 'cancelled' });
2547
- await finishRunView(tx('Cancelled', '已中断'), 'rejected');
2462
+ await finishRunView(tx('Cancelled', '已取消'), 'cancelled');
2548
2463
  return;
2549
2464
  }
2550
2465
  const translated = translateRawError(response.error.message, { mode: submittedMode, locale: getLocale() });
@@ -2771,6 +2686,7 @@
2771
2686
  }),
2772
2687
  clearTask: () => {
2773
2688
  panel.querySelector('[data-task]').value = '';
2689
+ selectionContextView.clear();
2774
2690
  updateSessionById(state.activeSessionId, { task: '' });
2775
2691
  autosizeTaskTextarea();
2776
2692
  syncComposerSendAvailability();
@@ -2801,6 +2717,9 @@
2801
2717
  const requestedProviderId = submitted.providerId || state?.providerId || 'builtin';
2802
2718
  const selection = providerSettingsCoordinator.getRunSelection(requestedProviderId);
2803
2719
  const providerId = selection?.providerId || requestedProviderId;
2720
+ const selectedContext = Object.prototype.hasOwnProperty.call(submitted, 'selectionContext')
2721
+ ? submitted.selectionContext : selectionContextView.capture();
2722
+ const selectedPaths = submitted.focusFiles ?? getActiveFocusFiles();
2804
2723
  return RunExecutionSnapshot.capture({
2805
2724
  mode: submitted.mode ?? state?.mode,
2806
2725
  providerId,
@@ -2810,7 +2729,11 @@
2810
2729
  speedTier: submitted.speedTier ?? state?.speedTier,
2811
2730
  autoRecompile: submitted.autoRecompile ?? state?.autoRecompile !== false,
2812
2731
  requireReviewing: submitted.requireReviewing ?? state?.requireReviewing === true,
2813
- focusFiles: submitted.focusFiles ?? getActiveFocusFiles()
2732
+ focusFiles: selectedContext?.mode === 'edit' && selectedPaths.length
2733
+ ? [...new Set([...selectedPaths, selectedContext.path])] : selectedPaths,
2734
+ selectionContext: selectedContext,
2735
+ writingStyle: Object.prototype.hasOwnProperty.call(submitted, 'writingStyle')
2736
+ ? submitted.writingStyle : writingStyleSettings.capture()
2814
2737
  }, {
2815
2738
  source: 'submitted',
2816
2739
  requireProviderRevision: providerId !== 'builtin'
@@ -3078,7 +3001,10 @@
3078
3001
  ...params,
3079
3002
  clientRunId: currentRunView?.recordId || '',
3080
3003
  clientSessionId: currentRunView?.sessionId || '',
3081
- providerSelection: RunExecutionSnapshot.toProviderSelection(executionSnapshot)
3004
+ providerSelection: RunExecutionSnapshot.toProviderSelection(executionSnapshot),
3005
+ selectionContext: executionSnapshot.selectionContext || null,
3006
+ accountScopeId: currentRunView?.runAccountScopeId || cachedAccountScopeId || '',
3007
+ writingStyle: executionSnapshot.writingStyle || null
3082
3008
  };
3083
3009
  }
3084
3010
 
@@ -3118,6 +3044,11 @@
3118
3044
  const projectKey = getCurrentProjectId();
3119
3045
  const recovery = await activeTurnControl.recoverJournals({
3120
3046
  projectKey,
3047
+ requireOwnerLost: Modules.StorageDb.sharedSessionsEnabled?.() === true,
3048
+ normalizeInterruptedRun: record => Object.assign(record, Modules.SessionState.normalizeRuns([record], {
3049
+ restoreRunningRuns: true,
3050
+ locale: getLocale()
3051
+ })[0]),
3121
3052
  findSession: findSessionById,
3122
3053
  getActiveSession: () => getActiveSession(state),
3123
3054
  createInterruptedRun: journal => ({
@@ -3364,11 +3295,12 @@
3364
3295
  .some(item => item?.status === 'queued');
3365
3296
  appendRunEvent({
3366
3297
  title: tx('Current Codex task was cancelled.', '已中断当前 Codex 任务。'),
3367
- status: 'failed'
3298
+ status: 'info',
3299
+ activity: { v: 1, kind: 'lifecycle', state: 'cancelled', target: 'cancelled' }
3368
3300
  });
3369
3301
  appendCompletionReport({
3370
3302
  conclusion: tx('This run was cancelled. It did not continue syncing or writing to Overleaf.', '这轮已中断,没有继续同步或写入 Overleaf。'),
3371
- status: 'rejected',
3303
+ status: 'cancelled',
3372
3304
  operations: [],
3373
3305
  applyResults: [],
3374
3306
  nextStep: hasQueuedFollowUp
@@ -3407,6 +3339,7 @@
3407
3339
  : !hasInput;
3408
3340
  runButton.title = label;
3409
3341
  runButton.setAttribute('aria-label', label);
3342
+ if (currentRunView?.writebackRetry && hasInput) runButton.disabled = true;
3410
3343
  }
3411
3344
 
3412
3345
  function handleTaskInputKeydown(event) {
@@ -3941,72 +3874,6 @@
3941
3874
  return writebackController.buildSyncApplyOperations(syncChanges, project);
3942
3875
  }
3943
3876
 
3944
- function partitionUnsafeProjectPathOperations(operations = []) {
3945
- const safe = [];
3946
- const skipped = [];
3947
- for (const operation of operations || []) {
3948
- const normalized = normalizeOperationProjectPaths(operation);
3949
- const invalid = getInvalidOperationProjectPath(normalized);
3950
- if (invalid) {
3951
- skipped.push({
3952
- operation: normalized,
3953
- result: {
3954
- ok: false,
3955
- code: 'invalid_project_path',
3956
- reason: tx(`Invalid ${invalid}. Codex did not write this file.`, `路径无效:${invalid}。Codex 没有写入这个文件。`)
3957
- }
3958
- });
3959
- continue;
3960
- }
3961
- safe.push(normalized);
3962
- }
3963
- return { safe, skipped };
3964
- }
3965
-
3966
- function normalizeOperationProjectPaths(operation = {}) {
3967
- if (!operation || typeof operation !== 'object') {
3968
- return operation;
3969
- }
3970
- const normalized = { ...operation };
3971
- if (typeof operation.path === 'string') {
3972
- normalized.path = normalizeSafeProjectPath(operation.path);
3973
- if (!normalized.path) {
3974
- normalized.invalidProjectPath = true;
3975
- }
3976
- }
3977
- if (typeof operation.to === 'string') {
3978
- normalized.to = normalizeSafeProjectPath(operation.to);
3979
- if (!normalized.to) {
3980
- normalized.invalidProjectDestinationPath = true;
3981
- }
3982
- }
3983
- if (typeof operation.destinationPath === 'string') {
3984
- normalized.destinationPath = normalizeSafeProjectPath(operation.destinationPath);
3985
- if (!normalized.destinationPath) {
3986
- normalized.invalidProjectDestinationPath = true;
3987
- }
3988
- }
3989
- return normalized;
3990
- }
3991
-
3992
- function getInvalidOperationProjectPath(operation = {}) {
3993
- if (operation.invalidProjectPath || (requiresOperationPath(operation) && !operation.path)) {
3994
- return 'operation path';
3995
- }
3996
- if (operation.invalidProjectDestinationPath || (requiresOperationDestinationPath(operation) && !(operation.to || operation.destinationPath))) {
3997
- return 'operation destination path';
3998
- }
3999
- return '';
4000
- }
4001
-
4002
- function requiresOperationPath(operation = {}) {
4003
- return ['edit', 'create', 'delete', 'rename', 'move', 'binary-create', 'overwrite-binary'].includes(operation.type);
4004
- }
4005
-
4006
- function requiresOperationDestinationPath(operation = {}) {
4007
- return operation.type === 'rename' || operation.type === 'move';
4008
- }
4009
-
4010
3877
  function normalizeSafeProjectPath(value) {
4011
3878
  if (Modules.ProjectFiles?.normalizeSafeProjectPath) {
4012
3879
  return Modules.ProjectFiles.normalizeSafeProjectPath(value);
@@ -4018,154 +3885,6 @@
4018
3885
  .replace(/^\/+/, '');
4019
3886
  }
4020
3887
 
4021
- function evaluateGovernedOperations(operations = []) {
4022
- if (!GovernanceRules?.evaluateGovernedOperations) {
4023
- return { allowed: operations || [], blocked: [], rules: getGovernanceRulesForCurrentProject() };
4024
- }
4025
- return GovernanceRules.evaluateGovernedOperations(operations, getGovernanceRulesForCurrentProject());
4026
- }
4027
-
4028
- function buildGovernanceSkippedApplyResult(blockedItems = []) {
4029
- return {
4030
- ok: blockedItems.length === 0,
4031
- applied: [],
4032
- skipped: (blockedItems || []).map(item => ({
4033
- operation: item.operation,
4034
- result: {
4035
- ok: false,
4036
- code: 'governance_blocked',
4037
- reason: formatGovernanceBlockedReason(item),
4038
- reasonKey: item.reason || 'governance_blocked'
4039
- }
4040
- }))
4041
- };
4042
- }
4043
-
4044
- function formatGovernanceBlockedReason(item = {}) {
4045
- if (item.reason === 'readonly') {
4046
- return tx(
4047
- 'Project governance marked this path read-only, so Codex did not write it.',
4048
- '项目治理规则将此路径标记为只读,因此 Codex 没有写入。'
4049
- );
4050
- }
4051
- if (item.reason === 'writable_allowlist') {
4052
- return tx(
4053
- 'Project governance allows writes only to configured writable patterns, and this path is outside that allowlist.',
4054
- '项目治理规则只允许写入配置的可写路径,此路径不在允许范围内。'
4055
- );
4056
- }
4057
- return tx('Project governance blocked this write.', '项目治理规则阻止了此写入。');
4058
- }
4059
-
4060
- function filterSyncChangesByOperations(syncChanges = [], operations = []) {
4061
- const allowedPaths = new Set((operations || []).map(operation => operation.path).filter(Boolean));
4062
- return (syncChanges || []).filter(change => allowedPaths.has(change?.path));
4063
- }
4064
-
4065
- function mergeApplyResultSkipped(result = {}, skipped = []) {
4066
- if (!skipped.length) {
4067
- return result;
4068
- }
4069
- return {
4070
- ...(result || {}),
4071
- ok: false,
4072
- applied: Array.isArray(result?.applied) ? result.applied : [],
4073
- skipped: [
4074
- ...getSkippedEntries(result),
4075
- ...skipped
4076
- ]
4077
- };
4078
- }
4079
-
4080
- async function confirmBinaryOperations(operations = []) {
4081
- const binaryOperations = (operations || []).filter(operation => operation.type === 'binary-create' || operation.type === 'overwrite-binary');
4082
- if (!binaryOperations.length) {
4083
- return { operations, skipped: [] };
4084
- }
4085
- const approved = await showPluginConfirm({
4086
- title: tr('binaryAssetConfirmTitle'),
4087
- message: tr('binaryAssetConfirmMessage', { files: formatOperationFiles(binaryOperations) }),
4088
- confirmLabel: tr('binaryAssetConfirm'),
4089
- cancelLabel: tr('binaryAssetCancel'),
4090
- destructive: true
4091
- });
4092
- if (approved) {
4093
- return { operations, skipped: [] };
4094
- }
4095
- return {
4096
- operations: operations.filter(operation => operation.type !== 'binary-create' && operation.type !== 'overwrite-binary'),
4097
- skipped: binaryOperations.map(operation => ({
4098
- operation,
4099
- result: {
4100
- ok: false,
4101
- code: 'binary_confirmation_rejected',
4102
- reason: tx('Binary asset writeback requires explicit confirmation and was skipped.', '二进制资源写回需要显式确认,已跳过。')
4103
- }
4104
- }))
4105
- };
4106
- }
4107
-
4108
- function buildAuditDiffSummary(operations = []) {
4109
- const changedFiles = new Set();
4110
- let binaryFilesChanged = 0;
4111
- for (const operation of operations || []) {
4112
- if (operation?.path) {
4113
- changedFiles.add(operation.path);
4114
- }
4115
- if (operation?.type === 'binary-create' || operation?.type === 'overwrite-binary') {
4116
- binaryFilesChanged++;
4117
- }
4118
- }
4119
- return {
4120
- filesChanged: changedFiles.size,
4121
- additions: 0,
4122
- deletions: 0,
4123
- binaryFilesChanged
4124
- };
4125
- }
4126
-
4127
- function buildAuditSummaryFromApply({ operations = [], applyResults = [], blockedFiles = [], resultStatus = 'completed', saveVerification = null } = {}) {
4128
- const appliedFiles = [];
4129
- const skippedFiles = [];
4130
- for (const result of applyResults || []) {
4131
- for (const item of getAppliedEntries(result)) {
4132
- appliedFiles.push(summarizeOperationForAudit(item.operation, item.result, 'applied'));
4133
- }
4134
- for (const item of getSkippedEntries(result)) {
4135
- skippedFiles.push(summarizeOperationForAudit(item.operation, item.result, 'skipped'));
4136
- }
4137
- }
4138
- return {
4139
- changedFiles: (operations || []).map(operation => summarizeOperationForAudit(operation, {}, 'changed')),
4140
- ['diffSummary']: buildAuditDiffSummary(operations),
4141
- blockedFiles,
4142
- appliedFiles,
4143
- skippedFiles,
4144
- resultStatus,
4145
- saveVerification
4146
- };
4147
- }
4148
-
4149
- // Tolerates `operation` and `result` being null in addition to undefined.
4150
- // Default-parameter values fire only for `undefined`, but the v1.3.8
4151
- // write-guard (pageBridge.runWriteGuard / writebackRouter.checkWritebackRunProjectId)
4152
- // emits batch-level skips with `operation: null` — there is no specific
4153
- // op to attribute the block to. Without this normalization the audit pass
4154
- // crashed with "Cannot read properties of null (reading 'path')", the
4155
- // outer-catch swallowed the partial-sync conclusion, and the user saw the
4156
- // misleading "local Codex returned no usable result" fallback.
4157
- function summarizeOperationForAudit(operation, result, status = '') {
4158
- const op = operation || {};
4159
- const res = result || {};
4160
- return {
4161
- path: op.path || op.from || op.to || '',
4162
- destinationPath: op.destinationPath || op.to || '',
4163
- type: op.type || '',
4164
- reason: res.reasonKey || res.code || res.reason || op.reasonKey || op.reason || '',
4165
- status,
4166
- size: op.size
4167
- };
4168
- }
4169
3888
 
4170
3889
  function getSyncChangePatches(change = {}) {
4171
3890
  return writebackController.getSyncChangePatches(change);
@@ -4276,6 +3995,25 @@
4276
3995
  // saveState. Returning null puts the record in degraded mode and excludes
4277
3996
  // it from cross-project queries (spec §5.2 storage rules).
4278
3997
  window.codexOverleafDeriveAccountScopeId = () => cachedAccountScopeId;
3998
+ var sharedSessionViewSync = Modules.SharedSessionViewSync.install({
3999
+ Bridge: Modules.SharedSessionBridge,
4000
+ StorageDb: Modules.StorageDb,
4001
+ getScope: () => ({
4002
+ accountScopeId: cachedAccountScopeId,
4003
+ projectId: getCurrentProjectId(),
4004
+ generation: spaRouteGeneration
4005
+ }),
4006
+ getState: () => state,
4007
+ isBusy: () => Boolean(readLiveRunViewForSaveStateGuard() || document.querySelector('dialog[open]')),
4008
+ apply: sessions => {
4009
+ state = normalizePanelState({ ...state, sessions }, { restoreRunningRuns: false });
4010
+ applyStateToPanel();
4011
+ },
4012
+ notice: error => appendStorageNoticeOnce('shared-history-refresh', tx(
4013
+ `Could not refresh shared history: ${error.message}`,
4014
+ `共享会话历史刷新失败:${error.message}`
4015
+ ))
4016
+ });
4279
4017
 
4280
4018
  // -----------------------------------------------------------------------
4281
4019
  // SPA route change lifecycle (spec §5.7).
@@ -4369,7 +4107,7 @@
4369
4107
  try {
4370
4108
  storageKey = getProjectStorageKey(LEGACY_STORAGE_KEY, window.location.href);
4371
4109
  const reloaded = await loadStoredState();
4372
- state = normalizePanelState(getGlobalPreferences().overlay(reloaded), { restoreRunningRuns: true });
4110
+ state = normalizeLoadedPanelState(reloaded);
4373
4111
  applyStateToPanel();
4374
4112
  } catch (_error) {
4375
4113
  // Reload failures fall back to the in-memory state; the next saveState
@@ -5018,18 +4756,33 @@
5018
4756
  button.setAttribute('aria-busy', loading ? 'true' : 'false');
5019
4757
  }
5020
4758
 
5021
- function formatProbeStatusBar(probe) {
4759
+ function syncProbeStatus(mode = state?.mode) {
4760
+ const status = panel?.querySelector('[data-probe-status]');
4761
+ if (!status) return;
4762
+ if (status.dataset.refreshing === 'true') {
4763
+ status.textContent = tr('refreshProbeLoading');
4764
+ return;
4765
+ }
4766
+ if (probeStatusSnapshot?.projectId !== getCurrentProjectId()) return;
4767
+ const { failed, probe, userInitiated } = probeStatusSnapshot;
4768
+ const text = failed ? tr('refreshProbeFailed') : formatProbeStatusBar(probe, mode);
4769
+ status.textContent = userInitiated && !failed ? tr('refreshProbeDone', { status: text }) : text;
4770
+ status.dataset.ok = !failed && isProbeReadyForCurrentMode(probe, mode) ? 'true' : 'false';
4771
+ if (!failed) updateExistingProbeNotice(probe, mode);
4772
+ }
4773
+
4774
+ function formatProbeStatusBar(probe, mode = state?.mode) {
5022
4775
  const readiness = getProbeRunReadiness(probe);
5023
4776
  const reviewingOk = readiness.reviewingOk;
5024
4777
  const editorWritable = readiness.editorWritable;
5025
4778
 
5026
- if (state?.mode === 'ask') {
4779
+ if (mode === 'ask') {
5027
4780
  return appendOtStatusToProbeStatus(`${formatModeLabel('ask')} · ${readiness.contextLabel}`);
5028
4781
  }
5029
4782
  if (reviewingOk) {
5030
4783
  const status = editorWritable
5031
4784
  ? `${tr('canRun')} · ${readiness.contextLabel}`
5032
- : `${formatModeLabel(state?.mode)} · ${tr('validatingEditor')}`;
4785
+ : `${formatModeLabel(mode)} · ${tr('validatingEditor')}`;
5033
4786
  return appendOtStatusToProbeStatus(status);
5034
4787
  }
5035
4788
  if (readiness.contextReady) {
@@ -5059,9 +4812,9 @@
5059
4812
  status.textContent = appendOtStatusToProbeStatus(base);
5060
4813
  }
5061
4814
 
5062
- function isProbeReadyForCurrentMode(probe) {
4815
+ function isProbeReadyForCurrentMode(probe, mode = state?.mode) {
5063
4816
  const readiness = getProbeRunReadiness(probe);
5064
- return state?.mode === 'ask' || readiness.reviewingOk;
4817
+ return mode === 'ask' || readiness.reviewingOk;
5065
4818
  }
5066
4819
 
5067
4820
  function getProbeRunReadiness(probe) {
@@ -5126,22 +4879,22 @@
5126
4879
  appendLog(message);
5127
4880
  }
5128
4881
 
5129
- function updateExistingProbeNotice(probe) {
4882
+ function updateExistingProbeNotice(probe, mode = state?.mode) {
5130
4883
  if (currentRunView || !panel?.querySelector('[data-probe-notice]')) {
5131
4884
  return;
5132
4885
  }
5133
- const { ready, message } = formatProbeUserNotice(probe);
4886
+ const { ready, message } = formatProbeUserNotice(probe, mode);
5134
4887
  updateProbeNotice(ready ? '' : message);
5135
4888
  }
5136
4889
 
5137
- function formatProbeUserNotice(probe) {
4890
+ function formatProbeUserNotice(probe, mode = state?.mode) {
5138
4891
  const readiness = getProbeRunReadiness(probe);
5139
4892
  const reviewingOk = readiness.reviewingOk;
5140
4893
  const editorOk = readiness.editorOk;
5141
4894
  const manualOverride = probe.reviewing?.status === 'manual-override';
5142
4895
  const editorWriteBlocked = probe.capabilities?.editor?.write === false;
5143
4896
 
5144
- if (state?.mode === 'ask') {
4897
+ if (mode === 'ask') {
5145
4898
  return {
5146
4899
  ready: true,
5147
4900
  message: tx(
@@ -5155,8 +4908,8 @@
5155
4908
  return {
5156
4909
  ready: true,
5157
4910
  message: tx(
5158
- `Ready: ${formatModeLabel(state?.mode)} is selected. This page has not exposed a writable editor yet; Codex will reopen and verify the target file when writing. If writeback fails, reload Overleaf and retry.`,
5159
- `可以运行:已选择“${formatModeLabel(state?.mode)}”。当前页面暂时没有暴露可写编辑器,写入时会重新打开目标文件并验证;如果写回失败,再刷新 Overleaf 页面后重试。`
4911
+ `Ready: ${formatModeLabel(mode)} is selected. This page has not exposed a writable editor yet; Codex will reopen and verify the target file when writing. If writeback fails, reload Overleaf and retry.`,
4912
+ `可以运行:已选择“${formatModeLabel(mode)}”。当前页面暂时没有暴露可写编辑器,写入时会重新打开目标文件并验证;如果写回失败,再刷新 Overleaf 页面后重试。`
5160
4913
  )
5161
4914
  };
5162
4915
  }
@@ -5489,6 +5242,9 @@
5489
5242
  }
5490
5243
 
5491
5244
  async function callPageBridge(method, params) {
5245
+ if (method === 'applyOperations' && params?.reviewingPolicy === 'no-trace-undo') {
5246
+ return Modules.AssetTransferBroker.create({ callPageBridge: (name, input) => pageBridgeClient.call(name, input), sendBackgroundNative }).applyOperations(params);
5247
+ }
5492
5248
  return pageBridgeClient.call(method, params);
5493
5249
  }
5494
5250
 
@@ -5569,7 +5325,7 @@
5569
5325
  accountScopeId: session.accountScopeId || accountScopeId,
5570
5326
  title: session.title || '',
5571
5327
  titleSource: session.titleSource || 'auto',
5572
- focusFiles: session.focusFiles || [],
5328
+ focusFiles: session.focusFiles || [], selectionContext: session.selectionContext?.projectId === projectId ? session.selectionContext : null,
5573
5329
  projectReferenceFiles: session.projectReferenceFiles || [],
5574
5330
  codexThreadId: session.codexThreadId || '',
5575
5331
  createdAt: session.createdAt,
@@ -5640,6 +5396,8 @@
5640
5396
  }
5641
5397
 
5642
5398
  async function saveState(options) {
5399
+ const endSharedSave = typeof sharedSessionViewSync !== 'undefined'
5400
+ ? sharedSessionViewSync?.beginSave() : null;
5643
5401
  try {
5644
5402
  const StorageDb = Modules.StorageDb;
5645
5403
  const Migration = Modules.StorageMigration;
@@ -5666,7 +5424,7 @@
5666
5424
  const urlProjectId = getCurrentProjectId();
5667
5425
  const projectId = projectIdOverride || urlProjectId;
5668
5426
  if (!projectId) {
5669
- return;
5427
+ if (options?.reviewRunIds?.length) throw new Error('Review persistence lost its project.'); return;
5670
5428
  }
5671
5429
  // Read currentRunView through a defensive accessor so historical test
5672
5430
  // harnesses that inline `saveState` without re-declaring the module-
@@ -5678,7 +5436,7 @@
5678
5436
  'Skipped saveState: run is in flight on a different project than the current URL; settlement owns persistence.',
5679
5437
  '已跳过 saveState:本次运行所属项目与当前 URL 不一致,写回由 settlement 负责。'
5680
5438
  ));
5681
- return;
5439
+ if (options?.reviewRunIds?.length) throw new Error('Review persistence project changed.'); return;
5682
5440
  }
5683
5441
  const compactState = prepareStateForStorage(state, { onAggressive: notifyAggressiveCompactionOnce });
5684
5442
  compactState.autoRecompile = state.autoRecompile;
@@ -5699,7 +5457,7 @@
5699
5457
  state: stateSnapshot,
5700
5458
  compactState: compactStateSnapshot,
5701
5459
  projectId,
5702
- deletedSessionIds: options?.deletedSessionIds,
5460
+ deletedSessionIds: options?.deletedSessionIds, reviewRunIds: options?.reviewRunIds,
5703
5461
  Migration,
5704
5462
  StorageDb,
5705
5463
  SessionPersistence: Modules.SessionPersistence,
@@ -5727,7 +5485,7 @@
5727
5485
  persistScopedState
5728
5486
  );
5729
5487
  if (!committed.ok && (
5730
- options?.queueMutation
5488
+ options?.reviewRunIds?.length || options?.queueMutation
5731
5489
  || options?.queueMutations?.length
5732
5490
  || committed.reason !== 'stale_view'
5733
5491
  )) {
@@ -5739,12 +5497,12 @@
5739
5497
  } catch (error) {
5740
5498
  if (
5741
5499
  error?.code === 'account_scope_unavailable' &&
5742
- !options?.queueMutation &&
5500
+ !options?.reviewRunIds?.length && !options?.queueMutation &&
5743
5501
  !(Array.isArray(options?.queueMutations) && options.queueMutations.length)
5744
5502
  ) {
5745
5503
  return;
5746
5504
  }
5747
- if (options?.queueMutation || options?.queueMutations?.length) {
5505
+ if (options?.reviewRunIds?.length || options?.queueMutation || options?.queueMutations?.length) {
5748
5506
  throw error;
5749
5507
  }
5750
5508
  let fallbackError = null;
@@ -5768,6 +5526,8 @@
5768
5526
  } else {
5769
5527
  throw error;
5770
5528
  }
5529
+ } finally {
5530
+ endSharedSave?.();
5771
5531
  }
5772
5532
  }
5773
5533
 
@@ -5869,6 +5629,7 @@
5869
5629
  // here (e.g. fallback chrome.storage.local.set rethrew).
5870
5630
  emitStorageQuotaFailure(error, null);
5871
5631
  }
5632
+ if (persistenceOptions?.reviewRunIds?.length) throw error;
5872
5633
  appendPlainLog(tx(`Failed to save session state: ${formatStateSaveError(error)}`, `保存会话状态失败:${formatStateSaveError(error)}`));
5873
5634
  })
5874
5635
  .finally(() => {
@@ -5923,6 +5684,7 @@
5923
5684
  ...(current || {}),
5924
5685
  ...incoming,
5925
5686
  queueMutation: undefined,
5687
+ reviewRunIds: Array.from(new Set([...(current?.reviewRunIds || []), ...(incoming?.reviewRunIds || [])])),
5926
5688
  queueMutations,
5927
5689
  deletedSessionIds: Array.from(new Set([
5928
5690
  ...(current?.deletedSessionIds || []),
@@ -6002,6 +5764,7 @@
6002
5764
  button.dataset.active = active ? 'true' : 'false';
6003
5765
  button.setAttribute('aria-pressed', active ? 'true' : 'false');
6004
5766
  });
5767
+ syncProbeStatus(currentMode);
6005
5768
  }
6006
5769
 
6007
5770
  function readPanelInputs() {
@@ -6075,11 +5838,14 @@
6075
5838
  }
6076
5839
  composerSkillInvocation = null;
6077
5840
  renderComposerSkillInvocation();
5841
+ selectionContextView.clear();
6078
5842
  state = updateActiveSession(state, { task: '' });
6079
5843
  saveStateSoon();
6080
5844
  }
6081
5845
 
6082
5846
  function applyStateToPanel(options = {}) {
5847
+ selectionContextView.render();
5848
+ writingStyleSettings.sync();
6083
5849
  applyPanelTheme(getThemePreference());
6084
5850
  if ((state.providerId || 'builtin') === 'builtin' && !modelSelectHasOption(state.model)) {
6085
5851
  renderModelOptions(getModelCatalog().FALLBACK_MODELS, state.model);
@@ -6157,17 +5923,38 @@
6157
5923
  }
6158
5924
  panel.dataset.running = running ? 'true' : 'false';
6159
5925
  panel.dataset.cancelling = running && runCancellationRequested ? 'true' : 'false';
5926
+ panel.querySelectorAll('[data-run-id]').forEach(root => {
5927
+ const record = findRunRecord(root.dataset.runId);
5928
+ if (record) configureEditButton(root, record, { running });
5929
+ if (record) configureWritebackButton(root, record, { running });
5930
+ });
6160
5931
  renderPendingInputs();
6161
5932
  }
6162
5933
 
6163
- function prepareRetryReplacement(run) {
5934
+ function prepareRetryReplacement(run, task) {
6164
5935
  const sessionId = state?.activeSessionId || '';
6165
5936
  const source = findRunRecord(run?.id, sessionId);
6166
- const eligible = source && runController.isRetryReplacementEligible(
5937
+ const eligible = source && !source.forkSnapshot
5938
+ && (!source.runProjectId || source.runProjectId === getCurrentProjectId())
5939
+ && runController.isRetryReplacementEligible(
6167
5940
  source,
6168
5941
  WritebackSettlement.projectRunSettlement(source)
6169
5942
  );
6170
- pendingRetryReplacement = eligible ? { sessionId, runId: source.id } : null;
5943
+ const returnToDraft = eligible && !currentRunView
5944
+ && ['cancelled', 'rejected'].includes(source.status);
5945
+ const draft = { task: typeof task === 'string' ? task : String(run?.task || '') };
5946
+ if (returnToDraft) {
5947
+ const snapshot = source.executionSnapshot || {};
5948
+ const mode = snapshot.mode || source.mode;
5949
+ draft.runs = (state.runs || []).filter(item => item.id !== source.id);
5950
+ draft.mode = ['ask', 'auto'].includes(mode) ? mode : state.mode;
5951
+ draft.focusFiles = Array.isArray(snapshot.focusFiles) ? snapshot.focusFiles : state.focusFiles;
5952
+ draft.selectionContext = snapshot.selectionContext || null;
5953
+ }
5954
+ // Persist the composer through its session owner, not only the top-level alias.
5955
+ state = updateActiveSession(state, draft);
5956
+ pendingRetryReplacement = eligible && !returnToDraft ? { sessionId, runId: source.id } : null;
5957
+ if (returnToDraft) applyStateToPanel({ preserveScroll: true });
6171
5958
  return Boolean(eligible);
6172
5959
  }
6173
5960
 
@@ -6344,6 +6131,17 @@
6344
6131
  return;
6345
6132
  }
6346
6133
 
6134
+ const owner = currentRunView && findRunRecord(currentRunView.recordId, currentRunView.sessionId);
6135
+ if (owner && Modules.SubagentActivityModel.ingest(owner, event, {
6136
+ locale: getLocale(), sanitize: sanitizeAssistantVisibleText, nativeEventSeq: journalSeq
6137
+ })) {
6138
+ owner.nativeEventSeq = Math.max(Number(owner.nativeEventSeq || 0), Number(journalSeq || 0));
6139
+ scheduleRunStateSave('stream');
6140
+ const visible = getCurrentRunViewForRender();
6141
+ if (visible) runTimelineView.refreshActivitySummary?.(visible);
6142
+ return;
6143
+ }
6144
+
6347
6145
  const activity = mapAgentEventToActivity(event, { locale: getLocale() });
6348
6146
  if (!activity?.visible || activity.kind === 'technical') {
6349
6147
  return;
@@ -6413,9 +6211,11 @@
6413
6211
  kind: input.kind || 'activity',
6414
6212
  subagent: input.subagent === true ? true : undefined,
6415
6213
  technicalDetail: sanitizeAssistantVisibleValue(input.technicalDetail),
6214
+ activity: Modules.RunActivityModel.capture(input, sanitizeAssistantVisibleText),
6416
6215
  guidanceId: sanitizeAssistantVisibleText(input.guidanceId),
6417
6216
  streamKey: sanitizeAssistantVisibleText(input.streamKey),
6418
6217
  streamRole: sanitizeAssistantVisibleText(input.streamRole),
6218
+ streamPhase: sanitizeAssistantVisibleText(input.streamPhase),
6419
6219
  appendText: typeof input.appendText === 'string' ? sanitizeAssistantVisibleText(input.appendText) : input.appendText,
6420
6220
  replaceText: input.replaceText,
6421
6221
  // Structured FailureReason (§7) attached to the event when the caller
@@ -6529,6 +6329,7 @@
6529
6329
  }
6530
6330
 
6531
6331
  function appendRunEventToView(view, event) {
6332
+ const projected = event.kind !== 'stream' && runTimelineView.refreshActivitySummary?.(view, event);
6532
6333
  if (event.kind === 'report') {
6533
6334
  view.report.hidden = false;
6534
6335
  const record = view?.recordId ? findRunRecord(view.recordId, view.sessionId) : null;
@@ -6539,6 +6340,7 @@
6539
6340
  if (event.kind === 'technical') {
6540
6341
  return;
6541
6342
  }
6343
+ if (projected) { bumpUnreadIfDetached(); return; }
6542
6344
  if (runGuidanceController.appendToView(event, view)) return;
6543
6345
  if (event.kind === 'stream') {
6544
6346
  // Stream deltas update an existing line in place — they must not inflate
@@ -6546,7 +6348,7 @@
6546
6348
  upsertStreamEvent(view, event);
6547
6349
  return;
6548
6350
  }
6549
- view.events.append(renderRunEvent(event));
6351
+ runTimelineView.appendActivityDetail(view, renderRunEvent(event), event);
6550
6352
  bumpUnreadIfDetached();
6551
6353
  }
6552
6354
 
@@ -6557,6 +6359,7 @@
6557
6359
  if (existing) {
6558
6360
  const nextTitle = sanitizeAssistantVisibleText(event.title);
6559
6361
  const nextRole = sanitizeAssistantVisibleText(event.streamRole) || existing.streamRole;
6362
+ if (event.streamPhase) existing.streamPhase = event.streamPhase;
6560
6363
  const combinedTitle = event.replaceText
6561
6364
  ? nextTitle
6562
6365
  : sanitizeAssistantVisibleText(appendStreamText(existing.title, nextTitle));
@@ -6616,10 +6419,6 @@
6616
6419
  }
6617
6420
 
6618
6421
 
6619
-
6620
-
6621
-
6622
-
6623
6422
  function applyPanelWidth(width, options = {}) {
6624
6423
  const nextWidth = PanelRenderer.setWidth(panelRendererInstance, width, { notify: false });
6625
6424
  if (state) {
@@ -6631,10 +6430,6 @@
6631
6430
  return nextWidth;
6632
6431
  }
6633
6432
 
6634
-
6635
-
6636
-
6637
-
6638
6433
  function getCurrentProjectMathSources() {
6639
6434
  const sources = [];
6640
6435
  const seen = new Set();
@@ -6810,7 +6605,8 @@
6810
6605
  await pendingMirror.catch(() => {});
6811
6606
  }
6812
6607
 
6813
- if ((Array.isArray(run.undoTrackedChanges) && run.undoTrackedChanges.length) || hasTrackedEditorUndo(run)) {
6608
+ if ((Array.isArray(run.undoTrackedChanges) && run.undoTrackedChanges.length) || hasTrackedEditorUndo(run)
6609
+ || (run.trackedChangeStatus === 'needs_review' && run.undoOperations?.some(operation => operation?.undoCreatedFile?.v === 1))) {
6814
6610
  await undoRunTrackedChanges(runId, run);
6815
6611
  return;
6816
6612
  }
@@ -6868,6 +6664,7 @@
6868
6664
  }
6869
6665
  }
6870
6666
 
6667
+ const deadlineAt = Date.now() + getPageBridgeTimeoutMs('rejectTrackedChanges');
6871
6668
  setRunUndoStatus(runId, 'running');
6872
6669
  appendRunRecordEvent(runId, {
6873
6670
  title: tr('undoNoTraceStarted'),
@@ -6876,12 +6673,10 @@
6876
6673
  });
6877
6674
 
6878
6675
  const result = await callPageBridge('applyOperations', {
6879
- operations: selectedOperations,
6676
+ operations: selectedOperations, deadlineAt,
6880
6677
  baseFiles: run.undoBaseFiles || [],
6881
6678
  reviewingPolicy: 'no-trace-undo',
6882
- // Welcome-panel + write-guard:
6883
- // route the undo through the same project-ID guard. The undo is
6884
- // bound to the run's original project, not the editor's active one.
6679
+ // Undo remains bound to its original project, including after navigation.
6885
6680
  runProjectId: getRunProjectIdForWriteback(run)
6886
6681
  });
6887
6682
  const undoApplied = isUndoResultEffectivelyApplied(run, result);
@@ -6913,15 +6708,18 @@
6913
6708
  }
6914
6709
 
6915
6710
  async function undoRunTrackedChanges(runId, run) {
6711
+ const createdUndoOperations = (run.undoOperations || []).filter(operation => operation?.undoCreatedFile?.v === 1);
6916
6712
  const trackedUndo = Array.isArray(run.undoTrackedChanges) && run.undoTrackedChanges.length > 0;
6713
+ const hasTextUndo = trackedUndo || buildTrackedUndoPostFiles(run).length > 0;
6917
6714
  const approved = await showPluginConfirm({
6918
- title: trackedUndo ? tr('undoTrackedTitle') : tr('undoNativeTitle'),
6715
+ title: !hasTextUndo ? tr('undoNoTraceTitle') : trackedUndo ? tr('undoTrackedTitle') : tr('undoNativeTitle'),
6919
6716
  message: [
6920
6717
  truncateRunTitle(run.task),
6921
6718
  '',
6922
- trackedUndo
6719
+ ...(hasTextUndo ? [trackedUndo
6923
6720
  ? tr('undoTrackedMessage', { files: formatTrackedChangeFiles(run.undoTrackedChanges) })
6924
- : tr('undoNativeMessage', { files: formatTrackedUndoFiles(run) })
6721
+ : tr('undoNativeMessage', { files: formatTrackedUndoFiles(run) })] : []),
6722
+ ...(createdUndoOperations.length ? [tr('undoNoTraceMessage', { files: formatOperationFiles(createdUndoOperations) })] : [])
6925
6723
  ].join('\n'),
6926
6724
  confirmLabel: trackedUndo ? tr('undoTrackedConfirm') : tr('undoConfirm'),
6927
6725
  cancelLabel: tr('confirmDefaultCancel'),
@@ -6931,10 +6729,10 @@
6931
6729
  return;
6932
6730
  }
6933
6731
 
6934
- // Tracked-change-lifecycle runs (those holding tracked-change refs) reach a
6935
- // decisive terminal `rejected` once the reject returns. The UI-local
6936
- // in-flight lock disables the button; it is never persisted.
6937
- const lifecycleReject = trackedUndo && isTrackedChangeLifecycleRun(run);
6732
+ const deadlineAt = Date.now() + getPageBridgeTimeoutMs('rejectTrackedChanges');
6733
+ // Keep the review lifecycle until every pending part has settled.
6734
+ // Its local lock stays active while verified recovery progress is persisted.
6735
+ const lifecycleReject = (trackedUndo || createdUndoOperations.length > 0) && isTrackedChangeLifecycleRun(run);
6938
6736
  if (lifecycleReject) {
6939
6737
  trackedChangeInFlight.set(runId, 'reject');
6940
6738
  refreshRunCardControls(runId);
@@ -6942,60 +6740,90 @@
6942
6740
  setRunUndoStatus(runId, 'running');
6943
6741
  }
6944
6742
  appendRunRecordEvent(runId, {
6945
- title: trackedUndo ? tr('undoTrackedStarted') : tr('undoNativeStarted'),
6743
+ title: !hasTextUndo ? tr('undoNoTraceStarted') : trackedUndo ? tr('undoTrackedStarted') : tr('undoNativeStarted'),
6946
6744
  status: 'running',
6947
- detail: { [tr('detailWillUndo')]: trackedUndo ? formatTrackedChangeFiles(run.undoTrackedChanges) : formatTrackedUndoFiles(run) }
6745
+ detail: { [tr('detailWillUndo')]: !hasTextUndo ? formatOperationFiles(createdUndoOperations)
6746
+ : trackedUndo ? formatTrackedChangeFiles(run.undoTrackedChanges) : formatTrackedUndoFiles(run) }
6948
6747
  });
6949
6748
 
6950
- let result;
6951
- let trackedUndoPostFilesAtDispatch;
6952
6749
  try {
6953
- trackedUndoPostFilesAtDispatch = buildTrackedUndoPostFiles(run);
6954
- result = await callPageBridge('rejectTrackedChanges', {
6955
- trackedChanges: run.undoTrackedChanges || [],
6750
+ const trackedUndoPostFilesAtDispatch = buildTrackedUndoPostFiles(run);
6751
+ let result = hasTextUndo ? await callPageBridge('rejectTrackedChanges', {
6752
+ trackedChanges: run.undoTrackedChanges || [], deadlineAt,
6956
6753
  expectedFiles: run.undoExpectedFiles || [],
6957
6754
  postFiles: trackedUndoPostFilesAtDispatch,
6958
6755
  untrackedUndo: run.executionSnapshot
6959
6756
  ? run.executionSnapshot.requireReviewing === false && ['submitted', 'legacy-captured'].includes(run.executionSnapshot.source)
6960
6757
  : getTrackedChangeCaptureController().hasLegacyUntrackedCheckpoint(run),
6961
- // Welcome-panel + write-guard:
6962
- // bind the reject to the run's original project. If the user has
6963
- // navigated away the page-side guard refuses with
6964
- // `aborted_project_changed` and the document is left untouched.
6758
+ // The page-side project guard still refuses writes after navigation.
6965
6759
  runProjectId: getRunProjectIdForWriteback(run)
6760
+ }) : { ok: true, applied: [], skipped: [] };
6761
+ if (createdUndoOperations.length && lifecycleReject && hasTextUndo) {
6762
+ WritebackSettlement.attachUndoNotVerifiedFailure(run, result, { buildFailure: buildContentFailure, postFiles: trackedUndoPostFilesAtDispatch });
6763
+ await applyTrackedChangeSettlement(runId, 'reject', { ...result, ok: false });
6764
+ }
6765
+ if (createdUndoOperations.length) {
6766
+ let createdResult;
6767
+ try {
6768
+ if (result.ok === false || result.skipped?.length) {
6769
+ throw new Error('Text changes remain unresolved; created files were kept for the next undo attempt.');
6770
+ }
6771
+ createdResult = await callPageBridge('applyOperations', {
6772
+ operations: createdUndoOperations, deadlineAt,
6773
+ baseFiles: run.undoBaseFiles || [],
6774
+ reviewingPolicy: 'no-trace-undo',
6775
+ runProjectId: getRunProjectIdForWriteback(run)
6776
+ });
6777
+ } catch (error) {
6778
+ createdResult = { ok: false, applied: [], skipped: createdUndoOperations.map(operation => ({
6779
+ operation, result: { ok: false, code: 'undo_operation_failed', reason: error.message }
6780
+ })) };
6781
+ }
6782
+ const applied = [...(result.applied || []), ...(createdResult.applied || [])];
6783
+ const skipped = [...(result.skipped || []), ...(createdResult.skipped || [])];
6784
+ for (const operation of createdUndoOperations) {
6785
+ if (!(createdResult.applied || []).some(entry => entry.operation?.path === operation.path
6786
+ && entry.result?.ok === true && entry.result?.verified === true)
6787
+ && !(createdResult.skipped || []).some(entry => entry.operation?.path === operation.path)) {
6788
+ skipped.push({ operation, result: { ok: false, code: 'undo_operation_failed',
6789
+ reason: 'The created file has no verified deletion receipt.' } });
6790
+ }
6791
+ }
6792
+ result = { ...result, ok: result.ok !== false && createdResult.ok !== false && !skipped.length, applied, skipped };
6793
+ }
6794
+ writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
6795
+ appendRunRecordEvent(runId, {
6796
+ title: createdUndoOperations.length ? tr('undoResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }) : trackedUndo
6797
+ ? tr('undoTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 })
6798
+ : tr('undoNativeResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
6799
+ status: result.skipped?.length ? 'failed' : 'completed',
6800
+ detail: {
6801
+ [trackedUndo && !createdUndoOperations.length ? tr('detailRejected') : tr('detailUndone')]: (result.applied || []).map(item => ({
6802
+ [tr('detailFile')]: item.trackedChange?.path || item.operation?.path || tr('unknownFile'),
6803
+ [tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || item.operation?.type
6804
+ })),
6805
+ [tr('detailSkipped')]: (result.skipped || []).map(item => ({
6806
+ [tr('detailFile')]: item.trackedChange?.path || item.operation?.path || tr('unknownFile'),
6807
+ [tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || item.operation?.type || '',
6808
+ [tr('detailReason')]: formatBridgeResultReason(item.result, item.trackedChange?.path || item.operation?.path)
6809
+ }))
6810
+ }
6966
6811
  });
6812
+ if (lifecycleReject) {
6813
+ WritebackSettlement.attachUndoNotVerifiedFailure(run, result, {
6814
+ buildFailure: buildContentFailure,
6815
+ postFiles: trackedUndoPostFilesAtDispatch
6816
+ });
6817
+ await applyTrackedChangeSettlement(runId, 'reject', result);
6818
+ return;
6819
+ }
6820
+ applyLegacyUndoSettlement(runId, result.skipped?.length ? 'partial' : 'applied', result);
6967
6821
  } finally {
6968
6822
  if (lifecycleReject) {
6969
6823
  trackedChangeInFlight.delete(runId);
6824
+ refreshRunCardControls(runId);
6970
6825
  }
6971
6826
  }
6972
- writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
6973
- appendRunRecordEvent(runId, {
6974
- title: trackedUndo
6975
- ? tr('undoTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 })
6976
- : tr('undoNativeResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
6977
- status: result.skipped?.length ? 'failed' : 'completed',
6978
- detail: {
6979
- [trackedUndo ? tr('detailRejected') : tr('detailUndone')]: (result.applied || []).map(item => ({
6980
- [tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
6981
- [tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
6982
- })),
6983
- [tr('detailSkipped')]: (result.skipped || []).map(item => ({
6984
- [tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
6985
- [tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || '',
6986
- [tr('detailReason')]: formatBridgeResultReason(item.result, item.trackedChange?.path)
6987
- }))
6988
- }
6989
- });
6990
- if (lifecycleReject) {
6991
- WritebackSettlement.attachUndoNotVerifiedFailure(run, result, {
6992
- buildFailure: buildContentFailure,
6993
- postFiles: trackedUndoPostFilesAtDispatch
6994
- });
6995
- applyTrackedChangeSettlement(runId, 'reject', result);
6996
- return;
6997
- }
6998
- applyLegacyUndoSettlement(runId, result.skipped?.length ? 'partial' : 'applied', result);
6999
6827
  }
7000
6828
 
7001
6829
  async function acceptRun(runId) {
@@ -7025,50 +6853,50 @@
7025
6853
  detail: { [tr('detailAccepted')]: formatTrackedChangeFiles(run.undoTrackedChanges) }
7026
6854
  });
7027
6855
 
7028
- let result;
7029
6856
  try {
7030
- result = await callPageBridge('acceptTrackedChanges', {
6857
+ const result = await callPageBridge('acceptTrackedChanges', {
7031
6858
  trackedChanges: run.undoTrackedChanges || [],
7032
6859
  expectedFiles: run.undoExpectedFiles || [],
7033
6860
  postFiles: buildTrackedUndoPostFiles(run),
7034
6861
  appliedOperations: Array.isArray(run.appliedOperations) ? run.appliedOperations : [],
7035
6862
  runProjectId: getRunProjectIdForWriteback(run)
7036
6863
  });
7037
- } finally {
7038
- trackedChangeInFlight.delete(runId);
7039
- }
7040
- appendAcceptDiagnosticEvents(runId, Array.isArray(result.diagnostics) ? result.diagnostics : []);
7041
- appendRunRecordEvent(runId, {
7042
- title: tr('runAcceptTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
7043
- status: result.skipped?.length ? 'failed' : 'completed',
7044
- detail: {
7045
- [tr('detailAccepted')]: (result.applied || []).map(item => ({
7046
- [tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
7047
- [tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
7048
- })),
7049
- [tr('detailSkipped')]: (result.skipped || []).map(item => ({
7050
- [tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
7051
- [tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || '',
7052
- [tr('detailReason')]: formatBridgeResultReason(item.result, item.trackedChange?.path)
7053
- }))
7054
- }
7055
- });
7056
- if (result.ok === false) {
6864
+ appendAcceptDiagnosticEvents(runId, Array.isArray(result.diagnostics) ? result.diagnostics : []);
7057
6865
  appendRunRecordEvent(runId, {
7058
- title: tr('runAcceptTrackedFailed'),
7059
- status: 'failed',
6866
+ title: tr('runAcceptTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
6867
+ status: result.skipped?.length ? 'failed' : 'completed',
7060
6868
  detail: {
7061
- [tr('detailReason')]: tr('runAcceptTrackedFailedReason')
6869
+ [tr('detailAccepted')]: (result.applied || []).map(item => ({
6870
+ [tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
6871
+ [tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
6872
+ })),
6873
+ [tr('detailSkipped')]: (result.skipped || []).map(item => ({
6874
+ [tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
6875
+ [tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || '',
6876
+ [tr('detailReason')]: formatBridgeResultReason(item.result, item.trackedChange?.path)
6877
+ }))
7062
6878
  }
7063
6879
  });
7064
- if (!WritebackSettlement.isSuccessfulTrackedChangeSettlement(result)) {
7065
- refreshRunCardControls(runId);
6880
+ if (result.ok === false) {
6881
+ appendRunRecordEvent(runId, {
6882
+ title: tr('runAcceptTrackedFailed'),
6883
+ status: 'failed',
6884
+ detail: {
6885
+ [tr('detailReason')]: tr('runAcceptTrackedFailedReason')
6886
+ }
6887
+ });
6888
+ if (!WritebackSettlement.isSuccessfulTrackedChangeSettlement(result)) {
6889
+ refreshRunCardControls(runId);
6890
+ }
7066
6891
  }
6892
+ WritebackSettlement.attachAcceptNotVerifiedFailure(run, result, {
6893
+ buildFailure: buildContentFailure
6894
+ });
6895
+ await applyTrackedChangeSettlement(runId, 'accept', result);
6896
+ } finally {
6897
+ trackedChangeInFlight.delete(runId);
6898
+ refreshRunCardControls(runId);
7067
6899
  }
7068
- WritebackSettlement.attachAcceptNotVerifiedFailure(run, result, {
7069
- buildFailure: buildContentFailure
7070
- });
7071
- applyTrackedChangeSettlement(runId, 'accept', result);
7072
6900
  }
7073
6901
 
7074
6902
  function getRunProjectIdForWriteback(run) {
@@ -7112,35 +6940,179 @@
7112
6940
  );
7113
6941
  }
7114
6942
 
7115
- function applyTrackedChangeSettlement(runId, kind, result) {
7116
- const run = findRunRecord(runId);
6943
+ async function applyTrackedChangeSettlement(runId, kind, result) {
6944
+ const record = findRunRecord(runId);
6945
+ const createdUndo = record?.undoOperations?.some(operation => operation?.undoCreatedFile?.v === 1);
6946
+ const run = kind === 'reject' && createdUndo ? advanceRunUndoProgress(record, result) : record;
7117
6947
  const settlement = WritebackSettlement.settleTrackedChangeLifecycle({
7118
6948
  kind,
7119
6949
  run,
7120
6950
  result,
7121
6951
  failureReasons: FailureReasons
7122
6952
  });
7123
- if (settlement.decision === 'blocked') {
7124
- refreshRunCardControls(runId);
6953
+ if (settlement.decision === 'blocked' && !(kind === 'reject' && createdUndo)) {
7125
6954
  return;
7126
6955
  }
7127
- Object.assign(run, WritebackSettlement.applySettlementTransition(run, settlement));
7128
- saveStateSoon();
7129
- refreshRunCardControls(runId);
6956
+ await Modules.ScopedPersistenceCoordinator.applyReviewTransition({
6957
+ getState: () => state, runId, transition: WritebackSettlement.applySettlementTransition(run, settlement),
6958
+ persist: flushQueuedSaveState
6959
+ });
7130
6960
  }
7131
6961
 
6962
+
6963
+ function advanceRunUndoProgress(run, result) {
6964
+ const postFiles = buildTrackedUndoPostFiles(run);
6965
+ return Modules.UndoOperations.advanceUndoCheckpoint(run, result, file =>
6966
+ WritebackSettlement.isUndoVerifiedContentMatching({ undoExpectedFiles: [file] }, result, { postFiles })
6967
+ );
6968
+ }
7132
6969
  function getRunUndoCount(run) {
7133
6970
  if (!run) {
7134
6971
  return 0;
7135
6972
  }
7136
6973
  const trackedEditorUndoCount = hasTrackedEditorUndo(run) ? 1 : 0;
7137
6974
  if (trackedEditorUndoCount) {
7138
- return trackedEditorUndoCount;
6975
+ return trackedEditorUndoCount + (run.undoOperations || []).filter(operation => operation?.undoCreatedFile?.v === 1).length;
7139
6976
  }
7140
6977
  return (run.undoOperations?.length || 0)
7141
6978
  + (run.undoTrackedChanges?.length || 0);
7142
6979
  }
7143
6980
 
6981
+ function recoveryOwner(target) {
6982
+ if (!target.runProjectId || target.runProjectId !== getCurrentProjectId() || !cachedAccountScopeId) {
6983
+ throw new Error('Writeback recovery lost its project or account identity.');
6984
+ }
6985
+ return { projectId: target.runProjectId, accountScopeId: cachedAccountScopeId,
6986
+ sessionId: target.sessionId, runId: target.recordId, createdAt: new Date().toISOString() };
6987
+ }
6988
+
6989
+ async function stageWritebackRecovery(operations, target, requireReviewing) {
6990
+ const record = findRunRecord(target.recordId, target.sessionId);
6991
+ if (!record) throw new Error('The writeback run is no longer available.');
6992
+ const creates = operations.filter(op => op.type === 'create' && typeof op.content === 'string');
6993
+ record.retryRequireReviewing = requireReviewing === true; // In-memory; seeds an edit-only retry intent.
6994
+ if (!creates.length) return;
6995
+ const candidate = Modules.WritebackIntent.normalize({ ...recoveryOwner(target), id: crypto.randomUUID(),
6996
+ operations: creates, baseFiles: [], requireReviewing });
6997
+ const safe = Modules.SessionState.pickWritebackRecovery({ ...record, retryWriteback: candidate }).retryWriteback;
6998
+ if (safe) record.retryWriteback = safe; else delete record.retryWriteback;
6999
+ await flushQueuedSaveState();
7000
+ }
7001
+
7002
+ async function settleWritebackRecovery(applied, project, verification, target) {
7003
+ const record = findRunRecord(target.recordId, target.sessionId);
7004
+ if (!record) return;
7005
+ const owner = recoveryOwner(target);
7006
+ const skippedPaths = new Set((applied.skipped || []).map(entry => entry.operation?.path));
7007
+ const creates = (record.retryWriteback?.operations || []).filter(op => skippedPaths.has(op.path));
7008
+ // Untouched edits (never opened / rejected) and deletes whose server check failed are safe to replay.
7009
+ const edits = (applied.skipped || []).filter(({ operation: op, result }) => result?.failure?.changedDocument === false && ((op?.type === 'edit'
7010
+ && (result.failure.stage === 'navigation' || result.failure.code === 'write_operation_failed')) || (op?.type === 'delete' && result.failure.code === 'source_zip_unavailable'))).map(entry => entry.operation);
7011
+ const base = { ...(record.retryWriteback || { ...owner, id: crypto.randomUUID(), requireReviewing: record.retryRequireReviewing === true }),
7012
+ baseFiles: project?.files || [], requestId: applied.receiptUnconfirmed ? applied.requestId || '' : '' };
7013
+ // An oversized edit pre-image must not cost the create retries (the intent has a 300 KiB ceiling).
7014
+ const safeRetry = [[...creates, ...edits], creates].filter(ops => ops.length).map(ops => Modules.SessionState
7015
+ .pickWritebackRecovery({ ...record, retryWriteback: Modules.WritebackIntent.normalize({ ...base, operations: ops }) }).retryWriteback).find(Boolean);
7016
+ if (safeRetry) record.retryWriteback = safeRetry; else delete record.retryWriteback;
7017
+ delete record.retryRequireReviewing;
7018
+ const next = await writebackController.buildSaveCheck(applied, project, owner);
7019
+ const files = new Map((record.saveCheck?.files || []).map(file => [file.path, file]));
7020
+ for (const file of next?.files || []) {
7021
+ if (verification.state === 'verified_saved') files.delete(file.path);
7022
+ else files.set(file.path, file);
7023
+ }
7024
+ if (files.size) record.saveCheck = Modules.WritebackIntent.normalizeSaveCheck({ ...owner, files: [...files.values()] });
7025
+ else {
7026
+ delete record.saveCheck;
7027
+ if (verification.state === 'verified_saved') record.saveConfirmedAt = new Date().toISOString();
7028
+ }
7029
+ await flushQueuedSaveState();
7030
+ }
7031
+
7032
+ async function retryRunWriteback(runId) {
7033
+ if (currentRunView || trackedChangeInFlight.size) return;
7034
+ const session = getActiveSession(state);
7035
+ const record = (session?.runs || []).find(run => run.id === runId);
7036
+ const intent = Modules.WritebackIntent.normalize(record?.retryWriteback);
7037
+ const check = Modules.WritebackIntent.normalizeSaveCheck(record?.saveCheck);
7038
+ const owner = intent || check;
7039
+ if (!record || record.forkSnapshot || !owner || owner.projectId !== getCurrentProjectId()
7040
+ || owner.accountScopeId !== cachedAccountScopeId || owner.sessionId !== session.id
7041
+ || owner.runId !== record.id || record.undoStatus === 'applied' || record.trackedChangeStatus === 'rejected') return;
7042
+ const originalStatus = record.status;
7043
+ // State is renormalized during the awaits below (probes, session sync), which
7044
+ // replaces run objects; always write through the current copy.
7045
+ const live = () => findRunRecord(runId, session.id) || record;
7046
+ let finished = false;
7047
+ runCancellationRequested = false;
7048
+ runCancellationController = new AbortController();
7049
+ record.status = 'running';
7050
+ record.retryingWriteback = true;
7051
+ renderRunHistory({ preserveScroll: true });
7052
+ const root = panel.querySelector(`[data-run-id="${cssEscape(runId)}"]`);
7053
+ currentRunView = { root, recordId: runId, sessionId: session.id, runProjectId: owner.projectId,
7054
+ runAccountScopeId: owner.accountScopeId, executionSnapshot: record.executionSnapshot,
7055
+ startedAt: Date.now(), writebackRetry: true, events: root?.querySelector('[data-run-events]'),
7056
+ report: root?.querySelector('[data-run-report]'), status: root?.querySelector('[data-run-status]'),
7057
+ runProcess: root?.querySelector('[data-run-process]'), processLabel: root?.querySelector('[data-run-process-summary]') };
7058
+ setRunning(true);
7059
+ const assertOwner = () => {
7060
+ if (runCancellationRequested || currentRunView?.recordId !== runId) {
7061
+ throw Object.assign(new Error('Save checking was cancelled.'), { code: 'codex_cancelled' });
7062
+ }
7063
+ if (owner.projectId !== getCurrentProjectId() || owner.accountScopeId !== cachedAccountScopeId) {
7064
+ throw Object.assign(new Error('The project or account changed.'), { code: 'aborted_project_changed' });
7065
+ }
7066
+ };
7067
+ try {
7068
+ assertOwner();
7069
+ if (intent?.requestId) {
7070
+ const receipt = await callPageBridge('getWritebackReceipt', { requestId: intent.requestId, runProjectId: owner.projectId });
7071
+ if (receipt?.state === 'running') throw new Error(tx('The previous upload is still finishing. Try again shortly.', '上一轮上传仍在收尾,请稍后再试。'));
7072
+ }
7073
+ if (intent?.operations.length) {
7074
+ // Edits replay against the run's original base files, so the stale guard still refuses changed documents.
7075
+ const result = await applySyncChangesToOverleaf(intent.operations.map(op => op.type === 'edit'
7076
+ ? { type: 'write', path: op.path, patches: op.patches, content: op.replaceAll } : op.type === 'delete' ? { type: 'delete', path: op.path } : { ...op, type: 'create' }),
7077
+ { id: owner.projectId, files: intent.baseFiles }, { mode: 'auto', requireReviewing: intent.requireReviewing,
7078
+ retryCreates: true, assistantMessage: tx('Retried the remaining file transfers for this task.', '已重试本轮未完成的文件同步。') });
7079
+ assertOwner();
7080
+ if (result.settlement?.facts) live().settlement = WritebackSettlement.mergeRetrySettlement(live().settlement, result.settlement.facts); // Keep files outside the retry.
7081
+ await finishRunView(result.hasSkippedOperations ? tx('Sync incomplete', '同步未完成') : tx('Synced', '已同步'),
7082
+ result.hasSkippedOperations ? 'failed' : 'completed');
7083
+ finished = true;
7084
+ } else if (check) {
7085
+ const result = await writebackController.confirmSaveCheck(check, { assertCurrent: assertOwner,
7086
+ readSnapshot: params => callPageBridge('getProjectSnapshot', { ...params, runProjectId: owner.projectId }) });
7087
+ assertOwner();
7088
+ const current = live();
7089
+ if (result.ok) {
7090
+ delete current.saveCheck;
7091
+ current.saveConfirmedAt = new Date().toISOString();
7092
+ if (current.settlement) current.settlement.evidence = { ...current.settlement.evidence, saved: 'verified' };
7093
+ }
7094
+ showPluginToast(result.ok ? tx('Saved content confirmed.', '已确认文件保存。')
7095
+ : tx('Saved content is still unconfirmed. No file was uploaded again.', '保存状态仍待确认,本次没有重复上传文件。'));
7096
+ }
7097
+ await flushQueuedSaveState();
7098
+ } catch (error) {
7099
+ showPluginToast(error.message || String(error));
7100
+ } finally {
7101
+ // Only finishRunView settles a new terminal status; every other exit
7102
+ // returns the card to the status it had before the retry.
7103
+ for (const copy of new Set([record, live()])) {
7104
+ delete copy.retryingWriteback;
7105
+ if (!finished && copy.status === 'running') copy.status = originalStatus;
7106
+ }
7107
+ if (currentRunView?.recordId === runId) currentRunView = null;
7108
+ runCancellationRequested = false;
7109
+ runCancellationController = null;
7110
+ setRunning(false);
7111
+ saveStateSoon();
7112
+ renderRunHistory({ preserveScroll: true });
7113
+ }
7114
+ }
7115
+
7144
7116
  function appendUndoReviewingPolicyEvent(runId, reviewingPolicy) {
7145
7117
  if (!reviewingPolicy || reviewingPolicy.policy !== 'no-trace-undo') {
7146
7118
  return;
@@ -7224,7 +7196,7 @@
7224
7196
  }
7225
7197
 
7226
7198
  function recordUndoFromApply(project, applyResult) {
7227
- const appliedEntries = getAppliedEntries(applyResult);
7199
+ const appliedEntries = getAppliedEntries(applyResult).filter(entry => entry.result?.idempotent !== true);
7228
7200
  if (!currentRunView?.recordId || !appliedEntries.length) {
7229
7201
  return;
7230
7202
  }
@@ -7243,14 +7215,15 @@
7243
7215
  ...appliedOperations
7244
7216
  ];
7245
7217
  record.appliedOperations = combinedAppliedOperations;
7218
+ const createdCheckpoint = buildCreatedFileUndoCheckpoint(project, combinedAppliedOperations, record);
7246
7219
 
7247
7220
  if ((currentRunView?.executionSnapshot?.requireReviewing ?? state.requireReviewing) === true) {
7248
7221
  const combinedTrackedChanges = normalizeApplyTrackedChanges([
7249
7222
  ...(Array.isArray(record.undoTrackedChanges) ? record.undoTrackedChanges : []),
7250
7223
  ...trackedChanges
7251
7224
  ]);
7252
- record.undoOperations = [];
7253
- record.undoBaseFiles = [];
7225
+ record.undoOperations = createdCheckpoint.undoOperations;
7226
+ record.undoBaseFiles = createdCheckpoint.undoBaseFiles;
7254
7227
  record.undoTrackedChanges = combinedTrackedChanges;
7255
7228
  record.undoExpectedFiles = selectExpectedFilesForTrackedUndo(
7256
7229
  project,
@@ -7285,6 +7258,11 @@
7285
7258
  [tr('detailMethod')]: tr('undoCheckpointNativeMethod')
7286
7259
  }
7287
7260
  });
7261
+ } else if (record.undoOperations.length) {
7262
+ appendRunEvent({
7263
+ title: tr('undoCheckpointPlain', { count: record.undoOperations.length }),
7264
+ status: 'completed'
7265
+ });
7288
7266
  } else {
7289
7267
  appendRunEvent({
7290
7268
  title: tr('undoCheckpointMissing'),
@@ -7299,6 +7277,15 @@
7299
7277
  }
7300
7278
 
7301
7279
  const checkpoint = buildUndoCheckpoint(project, combinedAppliedOperations);
7280
+ const createdPaths = new Set(createdCheckpoint.undoOperations.map(operation => operation.path));
7281
+ checkpoint.undoOperations = [
7282
+ ...checkpoint.undoOperations.filter(operation => !createdPaths.has(operation.path)),
7283
+ ...createdCheckpoint.undoOperations
7284
+ ];
7285
+ checkpoint.undoBaseFiles = [
7286
+ ...checkpoint.undoBaseFiles.filter(file => !createdPaths.has(file.path)),
7287
+ ...createdCheckpoint.undoBaseFiles
7288
+ ];
7302
7289
  if (!checkpoint.undoOperations.length) {
7303
7290
  return;
7304
7291
  }
@@ -7373,6 +7360,7 @@
7373
7360
  timestamp: event.timestamp || new Date().toISOString(),
7374
7361
  kind: event.kind || 'activity',
7375
7362
  technicalDetail: event.technicalDetail,
7363
+ activity: Modules.RunActivityModel.capture(event, sanitizeAssistantVisibleText),
7376
7364
  guidanceId: sanitizeAssistantVisibleText(event.guidanceId)
7377
7365
  };
7378
7366
  record.events = [...(record.events || []), normalized].slice(-MAX_RUN_EVENTS);
@@ -7402,10 +7390,12 @@
7402
7390
  function applyLegacyUndoSettlement(runId, undoStatus, result) {
7403
7391
  const run = findRunRecord(runId);
7404
7392
  if (!run) return;
7393
+ const checkpoint = run.undoOperations?.some(operation => operation?.undoCreatedFile?.v === 1)
7394
+ ? advanceRunUndoProgress(run, result) : run;
7405
7395
  Object.assign(run, WritebackSettlement.applySettlementTransition(
7406
- run,
7396
+ checkpoint,
7407
7397
  WritebackSettlement.settleLegacyUndo({
7408
- run,
7398
+ run: checkpoint,
7409
7399
  status: undoStatus,
7410
7400
  result
7411
7401
  })
@@ -7415,13 +7405,19 @@
7415
7405
  }
7416
7406
 
7417
7407
  function refreshRunCardControls(runId) {
7418
- const run = findRunRecord(runId);
7419
- const root = panel?.querySelector(`[data-run-id="${cssEscape(runId)}"]`);
7408
+ const run = findRunRecord(runId), root = panel?.querySelector(`[data-run-id="${cssEscape(runId)}"]`);
7420
7409
  if (!run || !root) {
7421
7410
  return;
7422
7411
  }
7412
+ if (runTimelineView.refreshResolvedUndo?.(runId)) return;
7423
7413
  configureAcceptButton(root, run);
7424
7414
  configureUndoButton(root, run);
7415
+ const reportEvent = (run.events || []).filter(event => event.kind === 'report').pop();
7416
+ const report = root.querySelector('[data-run-report]');
7417
+ if (reportEvent && report) {
7418
+ report.replaceChildren(renderCompletionReport(reportEvent, run));
7419
+ report.hidden = false;
7420
+ }
7425
7421
  }
7426
7422
 
7427
7423
  function findRunRecord(runId, sessionId = '') {
@@ -7542,6 +7538,11 @@
7542
7538
  // v1.8.0 C1: a change-history row jumps to the run it describes —
7543
7539
  // switch to its session if needed, then scroll + flash the run card.
7544
7540
  async function jumpToHistoryRun(record = {}) {
7541
+ if (!cachedAccountScopeId || record.accountScopeId !== cachedAccountScopeId
7542
+ || record.projectId !== getCurrentProjectId()) {
7543
+ showPluginToast(tx('The project or account changed. Reopen file history and try again.', '项目或账号已变化,请重新打开文件修改记录。'));
7544
+ return;
7545
+ }
7545
7546
  closeCustomInstructionsSettings();
7546
7547
  if (record.sessionId && record.sessionId !== state.activeSessionId) {
7547
7548
  const target = (state.sessions || []).find(session => session.id === record.sessionId);
@@ -7620,7 +7621,7 @@
7620
7621
  if (primaryNext) return primaryNext;
7621
7622
  return tx('Expand the write result, review the skipped reasons, then retry after fixing them.', '请展开写入结果查看跳过原因,处理后可以重试。');
7622
7623
  }
7623
- if (input.status === 'rejected') {
7624
+ if (input.status === 'cancelled' || input.status === 'rejected') {
7624
7625
  return tx('Adjust the task description and run again.', '可以调整任务描述后重新运行。');
7625
7626
  }
7626
7627
  if (input.status === 'blocked' || input.status === 'failed') {