codex-overleaf-link 2.4.1 → 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 (324) hide show
  1. package/README.md +272 -314
  2. package/README.zh-CN.md +287 -329
  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 +507 -507
  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 +8330 -1349
  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 +48 -121
  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 +10 -1
  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 +186 -154
  42. package/extension/src/content/selectionContextView.js +300 -0
  43. package/extension/src/content/sessionPersistence.js +6 -1
  44. package/extension/src/content/settingsPanel.js +77 -52
  45. package/extension/src/content/settingsWorkbench.js +374 -0
  46. package/extension/src/content/sharedSessionViewSync.js +72 -0
  47. package/extension/src/content/subagentActivityView.js +195 -0
  48. package/extension/src/content/trackedChangeCaptureController.js +7 -3
  49. package/extension/src/content/updateNotice.js +121 -50
  50. package/extension/src/content/writebackController.js +257 -0
  51. package/extension/src/content/writebackOrchestrator.js +146 -141
  52. package/extension/src/content/writebackPlan.js +231 -0
  53. package/extension/src/content/writebackRecovery.js +122 -0
  54. package/extension/src/content/writingStyleSettings.js +576 -0
  55. package/extension/src/content/writtenChangesView.js +152 -0
  56. package/extension/src/page/binaryAssetUploader.js +97 -19
  57. package/extension/src/page/overleafProjectSnapshot.js +1 -0
  58. package/extension/src/page/referenceProjects.js +148 -0
  59. package/extension/src/page/saveState.js +14 -2
  60. package/extension/src/page/snapshotRouter.js +44 -13
  61. package/extension/src/page/textCoordinates.js +102 -0
  62. package/extension/src/page/textFileCreator.js +350 -59
  63. package/extension/src/page/trackedChangeCapture.js +42 -56
  64. package/extension/src/page/trackedChangeOwnership.js +198 -0
  65. package/extension/src/page/trackedChangeReplay.js +164 -0
  66. package/extension/src/page/trackedChangesLifecycle.js +135 -149
  67. package/extension/src/page/treeOperations.js +26 -2
  68. package/extension/src/page/writebackReceiptJournal.js +112 -0
  69. package/extension/src/page/writebackRouter.js +175 -135
  70. package/extension/src/pageBridge.js +56 -101
  71. package/extension/src/popup.js +203 -59
  72. package/extension/src/shared/agentTranscript.js +18 -4
  73. package/extension/src/shared/auditRecords.js +71 -0
  74. package/extension/src/shared/changeHistoryModel.js +94 -0
  75. package/extension/src/shared/compatibility.js +1 -1
  76. package/extension/src/shared/compileAdapter.js +42 -0
  77. package/extension/src/shared/failureReasons.js +20 -0
  78. package/extension/src/shared/globalPreferences.js +1 -1
  79. package/extension/src/shared/i18n.js +93 -25
  80. package/extension/src/shared/managedUpdateProjection.js +6 -3
  81. package/extension/src/shared/pageRpcContract.js +7 -1
  82. package/extension/src/shared/providerProfiles.js +109 -0
  83. package/extension/src/shared/runActivityModel.js +361 -0
  84. package/extension/src/shared/runExecutionSnapshot.js +8 -2
  85. package/extension/src/shared/runExecutionSnapshotCodec.js +16 -4
  86. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +6 -3
  87. package/extension/src/shared/selectionContext.js +28 -0
  88. package/extension/src/shared/sessionState.js +97 -6
  89. package/extension/src/shared/sharedSessionBridge.js +288 -0
  90. package/extension/src/shared/storageDb.js +22 -55
  91. package/extension/src/shared/storageMigration.js +3 -0
  92. package/extension/src/shared/storageRunActions.js +10 -4
  93. package/extension/src/shared/storageValuePruning.js +40 -0
  94. package/extension/src/shared/subagentActivityModel.js +174 -0
  95. package/extension/src/shared/undoOperations.js +87 -0
  96. package/extension/src/shared/updateConsent.js +37 -8
  97. package/extension/src/shared/writebackIntent.js +62 -0
  98. package/extension/src/shared/writebackSettlement.js +29 -10
  99. package/extension/src/shared/writingStyle.js +33 -0
  100. package/extension/styles/panel.css +1972 -384
  101. package/extension/styles/popup.css +110 -0
  102. package/native-host/src/anthropicBridgeRequest.js +2 -1
  103. package/native-host/src/codexModelRecovery.js +107 -0
  104. package/native-host/src/codexPromptAssembly.js +2 -0
  105. package/native-host/src/codexProviderLaunch.js +3 -3
  106. package/native-host/src/codexRuntimeIdentity.js +1 -0
  107. package/native-host/src/codexSessionRunner.js +106 -140
  108. package/native-host/src/codexSessionTiming.js +3 -0
  109. package/native-host/src/managedInstall.js +5 -4
  110. package/native-host/src/nativeEnvironment.js +70 -0
  111. package/native-host/src/nativeQuotas.js +15 -8
  112. package/native-host/src/overleafWorkspaceRules.js +16 -0
  113. package/native-host/src/processStopBarrier.js +80 -0
  114. package/native-host/src/providerModelCatalog.js +4 -3
  115. package/native-host/src/providerProfile.js +13 -1
  116. package/native-host/src/providerReasoning.js +9 -5
  117. package/native-host/src/providerStore.js +4 -0
  118. package/native-host/src/releaseTransport.js +14 -5
  119. package/native-host/src/selectionScope.js +58 -0
  120. package/native-host/src/skills/parallel-subagents/SKILL.md +31 -3
  121. package/native-host/src/skills/writing-style-builder/SKILL.md +146 -0
  122. package/native-host/src/subagentBroker.js +102 -98
  123. package/native-host/src/subagentTelemetry.js +316 -0
  124. package/native-host/src/subagentWorkspace.js +103 -0
  125. package/native-host/src/subagentWorkspacePath.js +31 -0
  126. package/native-host/src/taskRunnerRuntime.js +55 -5
  127. package/native-host/src/turnAttachments.js +113 -0
  128. package/native-host/src/updateManager.js +122 -12
  129. package/native-host/src/updateNetworkControl.js +75 -0
  130. package/native-host/src/updateTrust.js +21 -2
  131. package/native-host/src/vendor/pdfjs/LICENSE +177 -0
  132. package/native-host/src/vendor/pdfjs/SOURCE.txt +3 -0
  133. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  134. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  135. package/native-host/src/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
  136. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  137. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  138. package/native-host/src/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
  139. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  140. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  141. package/native-host/src/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  142. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  143. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  144. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  145. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  146. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  147. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  148. package/native-host/src/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
  149. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  150. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  151. package/native-host/src/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
  152. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  153. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  154. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  155. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  156. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  157. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  158. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  159. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  160. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  161. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  162. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  163. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  164. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  165. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  166. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  167. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  168. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  169. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  170. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  171. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  172. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  173. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  174. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  175. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  176. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  177. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  178. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  179. package/native-host/src/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
  180. package/native-host/src/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
  181. package/native-host/src/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  182. package/native-host/src/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  183. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  184. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  185. package/native-host/src/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  186. package/native-host/src/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  187. package/native-host/src/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  188. package/native-host/src/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  189. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  190. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  191. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  192. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  193. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  194. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  195. package/native-host/src/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
  196. package/native-host/src/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
  197. package/native-host/src/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
  198. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  199. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  200. package/native-host/src/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
  201. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  202. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  203. package/native-host/src/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
  204. package/native-host/src/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
  205. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  206. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  207. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  208. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  209. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  210. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  211. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  212. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  213. package/native-host/src/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
  214. package/native-host/src/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
  215. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  216. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  217. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  218. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  219. package/native-host/src/vendor/pdfjs/cmaps/H.bcmap +0 -0
  220. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  221. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  222. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  223. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  224. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  225. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  226. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  227. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  228. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  229. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  230. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  231. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  232. package/native-host/src/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
  233. package/native-host/src/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
  234. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  235. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  236. package/native-host/src/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
  237. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  238. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  239. package/native-host/src/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
  240. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  241. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  242. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  243. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  244. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  245. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  246. package/native-host/src/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
  247. package/native-host/src/vendor/pdfjs/cmaps/LICENSE +36 -0
  248. package/native-host/src/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
  249. package/native-host/src/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
  250. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  251. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  252. package/native-host/src/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
  253. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  254. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  255. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  256. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  257. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  258. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  259. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  260. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  261. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  262. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  263. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  264. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  265. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  266. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  267. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  268. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  269. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  270. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  271. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  272. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  273. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  274. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  275. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  276. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  277. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  278. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  279. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  280. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  281. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  282. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  283. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  284. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  285. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  286. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  287. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  288. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  289. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  290. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  291. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  292. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  293. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  294. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  295. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  296. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  297. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  298. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  299. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  300. package/native-host/src/vendor/pdfjs/cmaps/V.bcmap +0 -0
  301. package/native-host/src/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  302. package/native-host/src/vendor/pdfjs/pdf.mjs +26740 -0
  303. package/native-host/src/vendor/pdfjs/pdf.worker.mjs +62201 -0
  304. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  305. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  306. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  307. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  308. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  309. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  310. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  311. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  312. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  313. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  314. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  315. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +102 -0
  316. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  317. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  318. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  319. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  320. package/native-host/src/writingStyleEvidence.js +102 -0
  321. package/native-host/src/writingStylePdfWorker.js +35 -0
  322. package/native-host/src/writingStyleProfile.js +165 -0
  323. package/native-host/src/writingStyleRuntime.js +447 -0
  324. 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
-
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
1178
 
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
1179
 
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
1180
 
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;
@@ -5768,6 +5526,8 @@
5768
5526
  } else {
5769
5527
  throw error;
5770
5528
  }
5529
+ } finally {
5530
+ endSharedSave?.();
5771
5531
  }
5772
5532
  }
5773
5533
 
@@ -6004,6 +5764,7 @@
6004
5764
  button.dataset.active = active ? 'true' : 'false';
6005
5765
  button.setAttribute('aria-pressed', active ? 'true' : 'false');
6006
5766
  });
5767
+ syncProbeStatus(currentMode);
6007
5768
  }
6008
5769
 
6009
5770
  function readPanelInputs() {
@@ -6077,11 +5838,14 @@
6077
5838
  }
6078
5839
  composerSkillInvocation = null;
6079
5840
  renderComposerSkillInvocation();
5841
+ selectionContextView.clear();
6080
5842
  state = updateActiveSession(state, { task: '' });
6081
5843
  saveStateSoon();
6082
5844
  }
6083
5845
 
6084
5846
  function applyStateToPanel(options = {}) {
5847
+ selectionContextView.render();
5848
+ writingStyleSettings.sync();
6085
5849
  applyPanelTheme(getThemePreference());
6086
5850
  if ((state.providerId || 'builtin') === 'builtin' && !modelSelectHasOption(state.model)) {
6087
5851
  renderModelOptions(getModelCatalog().FALLBACK_MODELS, state.model);
@@ -6159,17 +5923,38 @@
6159
5923
  }
6160
5924
  panel.dataset.running = running ? 'true' : 'false';
6161
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
+ });
6162
5931
  renderPendingInputs();
6163
5932
  }
6164
5933
 
6165
- function prepareRetryReplacement(run) {
5934
+ function prepareRetryReplacement(run, task) {
6166
5935
  const sessionId = state?.activeSessionId || '';
6167
5936
  const source = findRunRecord(run?.id, sessionId);
6168
- const eligible = source && runController.isRetryReplacementEligible(
5937
+ const eligible = source && !source.forkSnapshot
5938
+ && (!source.runProjectId || source.runProjectId === getCurrentProjectId())
5939
+ && runController.isRetryReplacementEligible(
6169
5940
  source,
6170
5941
  WritebackSettlement.projectRunSettlement(source)
6171
5942
  );
6172
- 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 });
6173
5958
  return Boolean(eligible);
6174
5959
  }
6175
5960
 
@@ -6346,6 +6131,17 @@
6346
6131
  return;
6347
6132
  }
6348
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
+
6349
6145
  const activity = mapAgentEventToActivity(event, { locale: getLocale() });
6350
6146
  if (!activity?.visible || activity.kind === 'technical') {
6351
6147
  return;
@@ -6415,9 +6211,11 @@
6415
6211
  kind: input.kind || 'activity',
6416
6212
  subagent: input.subagent === true ? true : undefined,
6417
6213
  technicalDetail: sanitizeAssistantVisibleValue(input.technicalDetail),
6214
+ activity: Modules.RunActivityModel.capture(input, sanitizeAssistantVisibleText),
6418
6215
  guidanceId: sanitizeAssistantVisibleText(input.guidanceId),
6419
6216
  streamKey: sanitizeAssistantVisibleText(input.streamKey),
6420
6217
  streamRole: sanitizeAssistantVisibleText(input.streamRole),
6218
+ streamPhase: sanitizeAssistantVisibleText(input.streamPhase),
6421
6219
  appendText: typeof input.appendText === 'string' ? sanitizeAssistantVisibleText(input.appendText) : input.appendText,
6422
6220
  replaceText: input.replaceText,
6423
6221
  // Structured FailureReason (§7) attached to the event when the caller
@@ -6531,6 +6329,7 @@
6531
6329
  }
6532
6330
 
6533
6331
  function appendRunEventToView(view, event) {
6332
+ const projected = event.kind !== 'stream' && runTimelineView.refreshActivitySummary?.(view, event);
6534
6333
  if (event.kind === 'report') {
6535
6334
  view.report.hidden = false;
6536
6335
  const record = view?.recordId ? findRunRecord(view.recordId, view.sessionId) : null;
@@ -6541,6 +6340,7 @@
6541
6340
  if (event.kind === 'technical') {
6542
6341
  return;
6543
6342
  }
6343
+ if (projected) { bumpUnreadIfDetached(); return; }
6544
6344
  if (runGuidanceController.appendToView(event, view)) return;
6545
6345
  if (event.kind === 'stream') {
6546
6346
  // Stream deltas update an existing line in place — they must not inflate
@@ -6548,7 +6348,7 @@
6548
6348
  upsertStreamEvent(view, event);
6549
6349
  return;
6550
6350
  }
6551
- view.events.append(renderRunEvent(event));
6351
+ runTimelineView.appendActivityDetail(view, renderRunEvent(event), event);
6552
6352
  bumpUnreadIfDetached();
6553
6353
  }
6554
6354
 
@@ -6559,6 +6359,7 @@
6559
6359
  if (existing) {
6560
6360
  const nextTitle = sanitizeAssistantVisibleText(event.title);
6561
6361
  const nextRole = sanitizeAssistantVisibleText(event.streamRole) || existing.streamRole;
6362
+ if (event.streamPhase) existing.streamPhase = event.streamPhase;
6562
6363
  const combinedTitle = event.replaceText
6563
6364
  ? nextTitle
6564
6365
  : sanitizeAssistantVisibleText(appendStreamText(existing.title, nextTitle));
@@ -6618,10 +6419,6 @@
6618
6419
  }
6619
6420
 
6620
6421
 
6621
-
6622
-
6623
-
6624
-
6625
6422
  function applyPanelWidth(width, options = {}) {
6626
6423
  const nextWidth = PanelRenderer.setWidth(panelRendererInstance, width, { notify: false });
6627
6424
  if (state) {
@@ -6633,10 +6430,6 @@
6633
6430
  return nextWidth;
6634
6431
  }
6635
6432
 
6636
-
6637
-
6638
-
6639
-
6640
6433
  function getCurrentProjectMathSources() {
6641
6434
  const sources = [];
6642
6435
  const seen = new Set();
@@ -6812,7 +6605,8 @@
6812
6605
  await pendingMirror.catch(() => {});
6813
6606
  }
6814
6607
 
6815
- 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))) {
6816
6610
  await undoRunTrackedChanges(runId, run);
6817
6611
  return;
6818
6612
  }
@@ -6870,6 +6664,7 @@
6870
6664
  }
6871
6665
  }
6872
6666
 
6667
+ const deadlineAt = Date.now() + getPageBridgeTimeoutMs('rejectTrackedChanges');
6873
6668
  setRunUndoStatus(runId, 'running');
6874
6669
  appendRunRecordEvent(runId, {
6875
6670
  title: tr('undoNoTraceStarted'),
@@ -6878,12 +6673,10 @@
6878
6673
  });
6879
6674
 
6880
6675
  const result = await callPageBridge('applyOperations', {
6881
- operations: selectedOperations,
6676
+ operations: selectedOperations, deadlineAt,
6882
6677
  baseFiles: run.undoBaseFiles || [],
6883
6678
  reviewingPolicy: 'no-trace-undo',
6884
- // Welcome-panel + write-guard:
6885
- // route the undo through the same project-ID guard. The undo is
6886
- // bound to the run's original project, not the editor's active one.
6679
+ // Undo remains bound to its original project, including after navigation.
6887
6680
  runProjectId: getRunProjectIdForWriteback(run)
6888
6681
  });
6889
6682
  const undoApplied = isUndoResultEffectivelyApplied(run, result);
@@ -6915,15 +6708,18 @@
6915
6708
  }
6916
6709
 
6917
6710
  async function undoRunTrackedChanges(runId, run) {
6711
+ const createdUndoOperations = (run.undoOperations || []).filter(operation => operation?.undoCreatedFile?.v === 1);
6918
6712
  const trackedUndo = Array.isArray(run.undoTrackedChanges) && run.undoTrackedChanges.length > 0;
6713
+ const hasTextUndo = trackedUndo || buildTrackedUndoPostFiles(run).length > 0;
6919
6714
  const approved = await showPluginConfirm({
6920
- title: trackedUndo ? tr('undoTrackedTitle') : tr('undoNativeTitle'),
6715
+ title: !hasTextUndo ? tr('undoNoTraceTitle') : trackedUndo ? tr('undoTrackedTitle') : tr('undoNativeTitle'),
6921
6716
  message: [
6922
6717
  truncateRunTitle(run.task),
6923
6718
  '',
6924
- trackedUndo
6719
+ ...(hasTextUndo ? [trackedUndo
6925
6720
  ? tr('undoTrackedMessage', { files: formatTrackedChangeFiles(run.undoTrackedChanges) })
6926
- : tr('undoNativeMessage', { files: formatTrackedUndoFiles(run) })
6721
+ : tr('undoNativeMessage', { files: formatTrackedUndoFiles(run) })] : []),
6722
+ ...(createdUndoOperations.length ? [tr('undoNoTraceMessage', { files: formatOperationFiles(createdUndoOperations) })] : [])
6927
6723
  ].join('\n'),
6928
6724
  confirmLabel: trackedUndo ? tr('undoTrackedConfirm') : tr('undoConfirm'),
6929
6725
  cancelLabel: tr('confirmDefaultCancel'),
@@ -6933,10 +6729,10 @@
6933
6729
  return;
6934
6730
  }
6935
6731
 
6936
- // Tracked-change-lifecycle runs (those holding tracked-change refs) reach a
6937
- // decisive terminal `rejected` once the reject returns. The UI-local
6938
- // in-flight lock disables the button; it is never persisted.
6939
- 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);
6940
6736
  if (lifecycleReject) {
6941
6737
  trackedChangeInFlight.set(runId, 'reject');
6942
6738
  refreshRunCardControls(runId);
@@ -6944,41 +6740,72 @@
6944
6740
  setRunUndoStatus(runId, 'running');
6945
6741
  }
6946
6742
  appendRunRecordEvent(runId, {
6947
- title: trackedUndo ? tr('undoTrackedStarted') : tr('undoNativeStarted'),
6743
+ title: !hasTextUndo ? tr('undoNoTraceStarted') : trackedUndo ? tr('undoTrackedStarted') : tr('undoNativeStarted'),
6948
6744
  status: 'running',
6949
- detail: { [tr('detailWillUndo')]: trackedUndo ? formatTrackedChangeFiles(run.undoTrackedChanges) : formatTrackedUndoFiles(run) }
6745
+ detail: { [tr('detailWillUndo')]: !hasTextUndo ? formatOperationFiles(createdUndoOperations)
6746
+ : trackedUndo ? formatTrackedChangeFiles(run.undoTrackedChanges) : formatTrackedUndoFiles(run) }
6950
6747
  });
6951
6748
 
6952
6749
  try {
6953
6750
  const trackedUndoPostFilesAtDispatch = buildTrackedUndoPostFiles(run);
6954
- const result = await callPageBridge('rejectTrackedChanges', {
6955
- trackedChanges: run.undoTrackedChanges || [],
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)
6966
- });
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
+ }
6967
6794
  writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
6968
6795
  appendRunRecordEvent(runId, {
6969
- title: trackedUndo
6796
+ title: createdUndoOperations.length ? tr('undoResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }) : trackedUndo
6970
6797
  ? tr('undoTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 })
6971
6798
  : tr('undoNativeResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
6972
6799
  status: result.skipped?.length ? 'failed' : 'completed',
6973
6800
  detail: {
6974
- [trackedUndo ? tr('detailRejected') : tr('detailUndone')]: (result.applied || []).map(item => ({
6975
- [tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
6976
- [tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
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
6977
6804
  })),
6978
6805
  [tr('detailSkipped')]: (result.skipped || []).map(item => ({
6979
- [tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
6980
- [tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || '',
6981
- [tr('detailReason')]: formatBridgeResultReason(item.result, item.trackedChange?.path)
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)
6982
6809
  }))
6983
6810
  }
6984
6811
  });
@@ -7114,14 +6941,16 @@
7114
6941
  }
7115
6942
 
7116
6943
  async function applyTrackedChangeSettlement(runId, kind, result) {
7117
- const run = findRunRecord(runId);
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;
7118
6947
  const settlement = WritebackSettlement.settleTrackedChangeLifecycle({
7119
6948
  kind,
7120
6949
  run,
7121
6950
  result,
7122
6951
  failureReasons: FailureReasons
7123
6952
  });
7124
- if (settlement.decision === 'blocked') {
6953
+ if (settlement.decision === 'blocked' && !(kind === 'reject' && createdUndo)) {
7125
6954
  return;
7126
6955
  }
7127
6956
  await Modules.ScopedPersistenceCoordinator.applyReviewTransition({
@@ -7130,18 +6959,160 @@
7130
6959
  });
7131
6960
  }
7132
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
+ }
7133
6969
  function getRunUndoCount(run) {
7134
6970
  if (!run) {
7135
6971
  return 0;
7136
6972
  }
7137
6973
  const trackedEditorUndoCount = hasTrackedEditorUndo(run) ? 1 : 0;
7138
6974
  if (trackedEditorUndoCount) {
7139
- return trackedEditorUndoCount;
6975
+ return trackedEditorUndoCount + (run.undoOperations || []).filter(operation => operation?.undoCreatedFile?.v === 1).length;
7140
6976
  }
7141
6977
  return (run.undoOperations?.length || 0)
7142
6978
  + (run.undoTrackedChanges?.length || 0);
7143
6979
  }
7144
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
+
7145
7116
  function appendUndoReviewingPolicyEvent(runId, reviewingPolicy) {
7146
7117
  if (!reviewingPolicy || reviewingPolicy.policy !== 'no-trace-undo') {
7147
7118
  return;
@@ -7225,7 +7196,7 @@
7225
7196
  }
7226
7197
 
7227
7198
  function recordUndoFromApply(project, applyResult) {
7228
- const appliedEntries = getAppliedEntries(applyResult);
7199
+ const appliedEntries = getAppliedEntries(applyResult).filter(entry => entry.result?.idempotent !== true);
7229
7200
  if (!currentRunView?.recordId || !appliedEntries.length) {
7230
7201
  return;
7231
7202
  }
@@ -7244,14 +7215,15 @@
7244
7215
  ...appliedOperations
7245
7216
  ];
7246
7217
  record.appliedOperations = combinedAppliedOperations;
7218
+ const createdCheckpoint = buildCreatedFileUndoCheckpoint(project, combinedAppliedOperations, record);
7247
7219
 
7248
7220
  if ((currentRunView?.executionSnapshot?.requireReviewing ?? state.requireReviewing) === true) {
7249
7221
  const combinedTrackedChanges = normalizeApplyTrackedChanges([
7250
7222
  ...(Array.isArray(record.undoTrackedChanges) ? record.undoTrackedChanges : []),
7251
7223
  ...trackedChanges
7252
7224
  ]);
7253
- record.undoOperations = [];
7254
- record.undoBaseFiles = [];
7225
+ record.undoOperations = createdCheckpoint.undoOperations;
7226
+ record.undoBaseFiles = createdCheckpoint.undoBaseFiles;
7255
7227
  record.undoTrackedChanges = combinedTrackedChanges;
7256
7228
  record.undoExpectedFiles = selectExpectedFilesForTrackedUndo(
7257
7229
  project,
@@ -7286,6 +7258,11 @@
7286
7258
  [tr('detailMethod')]: tr('undoCheckpointNativeMethod')
7287
7259
  }
7288
7260
  });
7261
+ } else if (record.undoOperations.length) {
7262
+ appendRunEvent({
7263
+ title: tr('undoCheckpointPlain', { count: record.undoOperations.length }),
7264
+ status: 'completed'
7265
+ });
7289
7266
  } else {
7290
7267
  appendRunEvent({
7291
7268
  title: tr('undoCheckpointMissing'),
@@ -7300,6 +7277,15 @@
7300
7277
  }
7301
7278
 
7302
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
+ ];
7303
7289
  if (!checkpoint.undoOperations.length) {
7304
7290
  return;
7305
7291
  }
@@ -7374,6 +7360,7 @@
7374
7360
  timestamp: event.timestamp || new Date().toISOString(),
7375
7361
  kind: event.kind || 'activity',
7376
7362
  technicalDetail: event.technicalDetail,
7363
+ activity: Modules.RunActivityModel.capture(event, sanitizeAssistantVisibleText),
7377
7364
  guidanceId: sanitizeAssistantVisibleText(event.guidanceId)
7378
7365
  };
7379
7366
  record.events = [...(record.events || []), normalized].slice(-MAX_RUN_EVENTS);
@@ -7403,10 +7390,12 @@
7403
7390
  function applyLegacyUndoSettlement(runId, undoStatus, result) {
7404
7391
  const run = findRunRecord(runId);
7405
7392
  if (!run) return;
7393
+ const checkpoint = run.undoOperations?.some(operation => operation?.undoCreatedFile?.v === 1)
7394
+ ? advanceRunUndoProgress(run, result) : run;
7406
7395
  Object.assign(run, WritebackSettlement.applySettlementTransition(
7407
- run,
7396
+ checkpoint,
7408
7397
  WritebackSettlement.settleLegacyUndo({
7409
- run,
7398
+ run: checkpoint,
7410
7399
  status: undoStatus,
7411
7400
  result
7412
7401
  })
@@ -7416,13 +7405,19 @@
7416
7405
  }
7417
7406
 
7418
7407
  function refreshRunCardControls(runId) {
7419
- const run = findRunRecord(runId);
7420
- const root = panel?.querySelector(`[data-run-id="${cssEscape(runId)}"]`);
7408
+ const run = findRunRecord(runId), root = panel?.querySelector(`[data-run-id="${cssEscape(runId)}"]`);
7421
7409
  if (!run || !root) {
7422
7410
  return;
7423
7411
  }
7412
+ if (runTimelineView.refreshResolvedUndo?.(runId)) return;
7424
7413
  configureAcceptButton(root, run);
7425
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
+ }
7426
7421
  }
7427
7422
 
7428
7423
  function findRunRecord(runId, sessionId = '') {
@@ -7543,6 +7538,11 @@
7543
7538
  // v1.8.0 C1: a change-history row jumps to the run it describes —
7544
7539
  // switch to its session if needed, then scroll + flash the run card.
7545
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
+ }
7546
7546
  closeCustomInstructionsSettings();
7547
7547
  if (record.sessionId && record.sessionId !== state.activeSessionId) {
7548
7548
  const target = (state.sessions || []).find(session => session.id === record.sessionId);
@@ -7621,7 +7621,7 @@
7621
7621
  if (primaryNext) return primaryNext;
7622
7622
  return tx('Expand the write result, review the skipped reasons, then retry after fixing them.', '请展开写入结果查看跳过原因,处理后可以重试。');
7623
7623
  }
7624
- if (input.status === 'rejected') {
7624
+ if (input.status === 'cancelled' || input.status === 'rejected') {
7625
7625
  return tx('Adjust the task description and run again.', '可以调整任务描述后重新运行。');
7626
7626
  }
7627
7627
  if (input.status === 'blocked' || input.status === 'failed') {