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
@@ -10,8 +10,10 @@
10
10
  const cancelTimer = deps.clearTimeout || clearTimeout;
11
11
  let timer = null, busy = false, disposed = false;
12
12
  const runs = () => deps.getRuns() || [];
13
+ const mergedRecovery = capture => capture?.source === 'native'
14
+ && capture.state === 'needs_review' && ['capture_timeout', 'capture_expired'].includes(capture.reason);
13
15
  const pending = run => Array.isArray(run.trackedChangeCaptures)
14
- && run.trackedChangeCaptures.some(capture => capture.state === 'pending');
16
+ && run.trackedChangeCaptures.some(capture => capture.state === 'pending' || mergedRecovery(capture));
15
17
  const terminal = run => ['accepted', 'rejected'].includes(run.trackedChangeStatus)
16
18
  || run.undoStatus === 'applied' || run.forkSnapshot === true;
17
19
  function buildPostFiles(run) {
@@ -78,9 +80,10 @@
78
80
  const postFiles = buildPostFiles(run);
79
81
  for (let i = 0; i < captures.length; i++) {
80
82
  const capture = captures[i];
81
- if (capture.state !== 'pending') continue;
83
+ const recoverMerged = mergedRecovery(capture);
84
+ if (capture.state !== 'pending' && !recoverMerged) continue;
82
85
  const expected = postFiles.find(file => file.path === capture.path);
83
- if (newer || !expected || now() >= capture.expiresAt || run.status === 'cancelled') {
86
+ if (newer || !expected || (!recoverMerged && now() >= capture.expiresAt) || run.status === 'cancelled') {
84
87
  capture.state = 'needs_review';
85
88
  capture.reason = newer ? 'capture_superseded' : !expected ? 'capture_baseline_missing'
86
89
  : run.status === 'cancelled' ? 'capture_cancelled' : 'capture_expired';
@@ -129,6 +132,7 @@
129
132
  if (changed) {
130
133
  run.trackedChangeCaptures = captures;
131
134
  await deps.persist();
135
+ deps.refresh?.(run);
132
136
  if (!pending(run)) deps.notify?.(run, 'needs_review', {
133
137
  reasons: captures.filter(capture => capture.state !== 'observed').map(capture => capture.reason)
134
138
  });
@@ -11,6 +11,8 @@
11
11
  let currentView = null;
12
12
  let mountedPanel = null;
13
13
  let actionInFlight = false;
14
+ let cancellationInFlight = false;
15
+ let actionSequence = 0;
14
16
  let listenerInstalled = false;
15
17
  let getLocale = () => '';
16
18
  let settingsRoot = null;
@@ -68,42 +70,62 @@
68
70
 
69
71
  async function handleClick(event) {
70
72
  const action = event.target?.closest?.('[data-update-notice-action]')?.dataset?.updateNoticeAction;
71
- if (!action || actionInFlight) return;
73
+ if (!action) return;
74
+ if (action === 'release-notes') {
75
+ const version = currentView?.state?.latestVersion || '';
76
+ window.open('https://github.com/Ghqqqq/codex-overleaf-link/releases/tag/v' + version, '_blank', 'noopener');
77
+ return;
78
+ }
72
79
  if (action === 'copy-manual') {
73
80
  try {
74
81
  await navigator.clipboard.writeText(manualUpdateCommand(currentView?.state));
75
82
  manualCommandCopied = true;
76
83
  render();
77
- setTimeout(() => {
78
- manualCommandCopied = false;
79
- render();
80
- }, 1800);
81
- } catch (_error) {
82
- manualCommandCopied = false;
83
- }
84
+ setTimeout(() => { manualCommandCopied = false; render(); }, 1800);
85
+ } catch (_) { manualCommandCopied = false; }
84
86
  return;
85
87
  }
88
+ return runUpdateAction(action);
89
+ }
90
+
91
+ async function runUpdateAction(action) {
92
+ const cancelling = action === 'cancel' || action === 'later';
93
+ if (cancellationInFlight || ((actionInFlight || settingsActionInFlight) && !cancelling)) return;
94
+ const sequence = ++actionSequence;
86
95
  actionInFlight = true;
96
+ settingsActionInFlight = true;
97
+ cancellationInFlight = cancelling;
87
98
  render();
88
99
  try {
89
- currentView = await request({
100
+ const view = await request({
101
+ check: 'codex-overleaf/consent-update-check',
90
102
  install: 'codex-overleaf/consent-update-install',
91
103
  retry: 'codex-overleaf/consent-update-check',
92
104
  later: 'codex-overleaf/consent-update-later',
105
+ cancel: 'codex-overleaf/consent-update-cancel',
106
+ recover: 'codex-overleaf/consent-update-recover',
93
107
  dismiss: 'codex-overleaf/consent-update-dismiss',
94
108
  reload: 'codex-overleaf/consent-update-reload'
95
109
  }[action]);
110
+ if (sequence === actionSequence) currentView = view;
96
111
  } catch (error) {
97
- currentView = await reconcileAfterActionError(error, {
98
- action,
99
- fallbackMessage: tx('Update failed.', '更新失败。')
100
- });
112
+ if (sequence === actionSequence) {
113
+ const view = await reconcileAfterActionError(error, {
114
+ action, fallbackMessage: tx('Update action failed.', '更新操作失败。')
115
+ });
116
+ if (sequence === actionSequence) currentView = view;
117
+ }
101
118
  } finally {
102
- actionInFlight = false;
103
- render();
119
+ if (sequence === actionSequence) {
120
+ actionInFlight = false;
121
+ settingsActionInFlight = false;
122
+ cancellationInFlight = false;
123
+ render();
124
+ }
104
125
  }
105
126
  }
106
127
 
128
+
107
129
  async function request(type) {
108
130
  if (!type) throw new Error('Unknown update action.');
109
131
  const response = await chrome.runtime.sendMessage({ type });
@@ -164,8 +186,9 @@
164
186
  restartWatchdog = setTimeout(async () => {
165
187
  try {
166
188
  const view = await request('codex-overleaf/consent-update-get-state');
167
- if (view?.state && !ACTIVE_UPDATE_STATES.has(view.state.state)) {
189
+ if (view?.state) {
168
190
  currentView = view;
191
+ if (ACTIVE_UPDATE_STATES.has(view.state.state)) scheduleRestartWatchdog();
169
192
  } else {
170
193
  currentView = buildFailedView('update_recovery_timeout', tx(
171
194
  'The update did not finish restarting. Retry or use the manual recovery command.',
@@ -206,37 +229,18 @@
206
229
  renderSettings();
207
230
  }
208
231
 
209
- async function handleSettingsAction() {
210
- if (settingsActionInFlight) return;
211
- settingsActionInFlight = true;
212
- renderSettings();
213
- const stateName = currentView?.state?.state || 'idle';
214
- let requestedAction = 'check';
215
- try {
216
- if (['reload_required', 'reload_tabs_required'].includes(stateName)) {
217
- requestedAction = 'reload';
218
- currentView = await request('codex-overleaf/consent-update-reload');
219
- } else if (stateName === 'update_available') {
220
- requestedAction = 'install';
221
- currentView = await request('codex-overleaf/consent-update-install');
222
- } else if (['failed', 'rolled_back'].includes(stateName)) {
223
- currentView = await request('codex-overleaf/consent-update-check');
224
- } else if (!ACTIVE_UPDATE_STATES.has(stateName)) {
225
- currentView = await request('codex-overleaf/consent-update-check');
226
- } else {
227
- return;
228
- }
229
- } catch (error) {
230
- currentView = await reconcileAfterActionError(error, {
231
- action: requestedAction,
232
- fallbackMessage: tx('Update check failed.', '更新检查失败。')
233
- });
234
- } finally {
235
- settingsActionInFlight = false;
236
- render();
232
+ function handleSettingsAction() {
233
+ const state = currentView?.state || {};
234
+ if (state.recoveryPending) return runUpdateAction('recover');
235
+ if (state.cancelRequested || ['checking', 'downloading', 'staged', 'waiting_for_idle'].includes(state.state)) {
236
+ return runUpdateAction('cancel');
237
237
  }
238
+ if (['reload_required', 'reload_tabs_required'].includes(state.state)) return runUpdateAction('reload');
239
+ if (state.state === 'update_available') return runUpdateAction('install');
240
+ if (!ACTIVE_UPDATE_STATES.has(state.state)) return runUpdateAction('check');
238
241
  }
239
242
 
243
+
240
244
  function render() {
241
245
  if (!currentView) return;
242
246
  renderSettings();
@@ -270,7 +274,24 @@
270
274
  detail.textContent = copy.detail;
271
275
  body.append(eyebrow, title, detail);
272
276
 
273
- if (state.state === 'failed') {
277
+ if (state.state === 'manual_install_required') {
278
+ const steps = document.createElement('ol');
279
+ steps.className = 'codex-update-notice-steps';
280
+ for (const text of [
281
+ tx('Run this once in a terminal:', '在终端运行一次:'),
282
+ tx('Reload the extension in chrome://extensions, then refresh Overleaf.', '在 chrome://extensions 里重新加载扩展,再刷新 Overleaf。')
283
+ ]) steps.append(Object.assign(document.createElement('li'), { textContent: text }));
284
+ const command = document.createElement('code');
285
+ command.className = 'codex-update-notice-command';
286
+ command.textContent = manualUpdateCommand(state);
287
+ steps.firstElementChild.append(command);
288
+ const keep = document.createElement('span');
289
+ keep.className = 'codex-update-notice-recovery';
290
+ keep.textContent = tx('Sessions, settings, provider keys and project mirrors are kept.', '会话、设置、模型服务的密钥和项目镜像都会保留。');
291
+ body.append(steps, keep);
292
+ }
293
+
294
+ if (state.state === 'failed' && !state.cancelRequested && !state.recoveryPending) {
274
295
  const recovery = document.createElement('span');
275
296
  recovery.className = 'codex-update-notice-recovery';
276
297
  recovery.textContent = tx('Manual recovery:', '手动恢复:');
@@ -280,7 +301,7 @@
280
301
  body.append(recovery, command);
281
302
  }
282
303
 
283
- if (!['update_available', 'failed', 'rolled_back', 'reload_required', 'reload_tabs_required'].includes(state.state)) {
304
+ if (!['update_available', 'manual_install_required', 'failed', 'rolled_back', 'reload_required', 'reload_tabs_required'].includes(state.state)) {
284
305
  const bar = document.createElement('div');
285
306
  bar.className = 'codex-update-notice-progress' + (progress.determinate ? '' : ' is-indeterminate');
286
307
  bar.setAttribute('role', 'progressbar');
@@ -302,7 +323,9 @@
302
323
  button.dataset.updateNoticeAction = action.id;
303
324
  button.className = action.primary ? 'is-primary' : '';
304
325
  button.textContent = action.label;
305
- button.disabled = actionInFlight;
326
+ button.disabled = action.id === 'cancel'
327
+ ? cancellationInFlight || (state.cancelRequested && !state.code)
328
+ : actionInFlight || settingsActionInFlight;
306
329
  actions.append(button);
307
330
  }
308
331
  notice.replaceChildren(body, actions);
@@ -343,6 +366,9 @@
343
366
  summaryText = tx(`Stable v${latestVersion} is available.`, `稳定版本 v${latestVersion} 已可用。`);
344
367
  statusText = tx('Ready to download and verify.', '已可下载并验证。');
345
368
  buttonText = tx('Update now', '立即更新');
369
+ } else if (stateName === 'manual_install_required') {
370
+ summaryText = tx(`v${latestVersion} is available but needs a one-time reinstall.`, `v${latestVersion} 已发布,但需要重新安装一次。`);
371
+ statusText = manualReasonText(state) || getCopy(state).detail;
346
372
  } else if (ACTIVE_UPDATE_STATES.has(stateName)) {
347
373
  statusText = getCopy(state).detail;
348
374
  buttonText = tx('Update in progress', '更新进行中');
@@ -358,11 +384,29 @@
358
384
  status.textContent = statusText;
359
385
  status.hidden = !statusText;
360
386
  button.textContent = buttonText;
361
- button.disabled = settingsActionInFlight || stateName === 'checking' || ACTIVE_UPDATE_STATES.has(stateName);
387
+ const cancellable = state.cancelRequested || ['checking', 'downloading', 'staged', 'waiting_for_idle'].includes(stateName);
388
+ if (state.recoveryPending) {
389
+ button.textContent = tx('Check recovery', '检查恢复状态');
390
+ button.disabled = actionInFlight || settingsActionInFlight;
391
+ } else if (cancellable) {
392
+ button.textContent = state.cancelRequested && !state.code
393
+ ? tx('Cancelling…', '正在取消…')
394
+ : state.cancelRequested ? tx('Retry cancellation', '重试取消') : tx('Cancel update', '取消更新');
395
+ button.disabled = cancellationInFlight || (state.cancelRequested && !state.code);
396
+ } else {
397
+ button.disabled = actionInFlight || settingsActionInFlight || ACTIVE_UPDATE_STATES.has(stateName);
398
+ }
362
399
  }
363
400
 
364
401
  function visibleActions(state) {
365
- if (actionInFlight) return [];
402
+ const value = currentView?.state || {};
403
+ if (value.recoveryPending) return [{ id: 'recover', label: tx('Check recovery', '检查恢复状态'), primary: true }];
404
+ if (value.cancelRequested || ['checking', 'downloading'].includes(state)) {
405
+ return [{ id: 'cancel', label: value.cancelRequested
406
+ ? value.code ? tx('Retry cancellation', '重试取消') : tx('Cancelling…', '正在取消…')
407
+ : tx('Cancel update', '取消更新'), primary: false }];
408
+ }
409
+ if (actionInFlight && !['staged', 'waiting_for_idle'].includes(state)) return [];
366
410
  if (['reload_required', 'reload_tabs_required'].includes(state)) {
367
411
  return [{ id: 'reload', label: state === 'reload_required'
368
412
  ? tx('Reload extension', '重新加载扩展') : tx('Refresh Overleaf tabs', '刷新 Overleaf 标签页'), primary: true }];
@@ -374,7 +418,14 @@
374
418
  ];
375
419
  }
376
420
  if (['staged', 'waiting_for_idle'].includes(state)) {
377
- return [{ id: 'later', label: tx('Later', '稍后'), primary: false }];
421
+ return [{ id: 'cancel', label: tx('Cancel update', '取消更新'), primary: false }];
422
+ }
423
+ if (state === 'manual_install_required') {
424
+ return [
425
+ { id: 'later', label: tx('Later', '稍后'), primary: false },
426
+ { id: 'release-notes', label: tx('Release notes', '发布说明'), primary: false },
427
+ { id: 'copy-manual', label: manualCommandCopied ? tx('Copied', '已复制') : tx('Copy command', '复制命令'), primary: true }
428
+ ];
378
429
  }
379
430
  if (['failed', 'rolled_back'].includes(state)) {
380
431
  return [
@@ -390,8 +441,18 @@
390
441
 
391
442
  function getCopy(state) {
392
443
  const target = state.latestVersion ? 'v' + state.latestVersion : '';
444
+ if (state.cancelRequested) return {
445
+ eyebrow: tx('Cancelling update', '正在取消更新'), title: target,
446
+ detail: state.message || tx('Stopping this update and clearing its authorization.', '正在停止本次更新并清理更新授权。')
447
+ };
448
+ if (state.recoveryPending) return {
449
+ eyebrow: tx('Checking update recovery', '正在确认更新恢复状态'), title: target,
450
+ detail: state.message || tx('Waiting for the installed version to be confirmed.', '正在确认实际安装的版本。')
451
+ };
393
452
  const blocker = blockersCopy(state.blockers?.length ? state.blockers : [state.blocker]);
394
453
  return {
454
+ checking: { eyebrow: tx('Checking for updates', '正在检查更新'), title: target,
455
+ detail: tx('Reading the latest signed release.', '正在读取最新签名版本。') },
395
456
  reload_required: {
396
457
  eyebrow: tx('Installed, awaiting reload', '已安装,等待加载'), title: target,
397
458
  detail: state.message || tx('The new files are installed. Reload the extension when Overleaf is saved and idle.', '新版文件已安装,请在 Overleaf 保存并空闲后重新加载扩展。')
@@ -400,6 +461,11 @@
400
461
  eyebrow: tx('Updated runtime is ready', '新版运行组件已就绪'), title: target,
401
462
  detail: state.message || tx('Refresh the saved, idle Overleaf tabs to load the updated panel.', '刷新已保存且空闲的 Overleaf 标签页以加载新版面板。')
402
463
  },
464
+ manual_install_required: {
465
+ eyebrow: tx('Reinstall needed for this update', '这次更新需要重新安装'),
466
+ title: target,
467
+ detail: manualReasonText(state) || tx('This release changes what Chrome lets the extension do, so Update now cannot apply it.', '这一版改动了 Chrome 授予扩展的权限,无法通过“立即更新”完成。')
468
+ },
403
469
  update_available: {
404
470
  eyebrow: tx('Update available', '发现新版本'),
405
471
  title: target,
@@ -488,6 +554,11 @@
488
554
  );
489
555
  }
490
556
 
557
+ function manualReasonText(state = {}) {
558
+ const reason = state.manualReason || {};
559
+ return tx(reason.en || reason.zh || '', reason.zh || reason.en || '');
560
+ }
561
+
491
562
  function tx(english, chinese) {
492
563
  const locale = String(getLocale() || document.documentElement?.lang || 'en');
493
564
  return /^zh\b/i.test(locale) ? chinese : english;
@@ -227,6 +227,11 @@
227
227
  ? changeOrReason
228
228
  : { reason: changeOrReason };
229
229
  const reason = change.reason;
230
+ if (reason === 'selection_scope_violation') {
231
+ return isEnglish
232
+ ? 'The local changes exceeded the captured selection. No changes from this task were written back.'
233
+ : '本地改动超出了已附加的选区,本轮改动未写回。';
234
+ }
230
235
  if (reason === 'generated_artifact') {
231
236
  return isEnglish
232
237
  ? 'LaTeX build artifact; not written back by default.'
@@ -240,6 +245,11 @@
240
245
  if (reason === 'binary_payload_exceeds_native_message_limit') {
241
246
  return formatBinaryPayloadLimitReason(change, locale);
242
247
  }
248
+ if (reason === 'subagent_unfinished_output') {
249
+ return isEnglish
250
+ ? 'The subagent did not finish; the main agent must review and adopt this file before it can sync.'
251
+ : '子代理未正常完成,主代理需核对并接管该文件后才能同步。';
252
+ }
243
253
  if (reason === 'subagent_unauthorized_edit') {
244
254
  return isEnglish
245
255
  ? "A subagent's change to this file was withheld from Overleaf — it either fell outside the subagent's assigned files or the subagent did not finish cleanly."
@@ -290,10 +300,257 @@
290
300
  return (syncChanges || []).filter(change => appliedPaths.has(change.path));
291
301
  }
292
302
 
303
+
304
+ // Verify the server revision independently of editor overlays. No writes or retries
305
+ // of writes are performed here; a missing acknowledgement never becomes success.
306
+ function buildSavedWritebackExpectations(applied = {}, project = {}) {
307
+ const expected = new Map();
308
+ const original = new Map((project.files || []).map(file => [file.path, file]));
309
+ const unsupported = [];
310
+ for (const entry of getAppliedEntries(applied)) {
311
+ const operation = entry?.operation || {};
312
+ const result = entry?.result || {};
313
+ const path = operation.path;
314
+ if (!path || result.ok === false) { unsupported.push(path || 'unknown'); continue; }
315
+ if (operation.type === 'delete') { expected.set(path, { absent: true }); continue; }
316
+ if (operation.type === 'rename' || operation.type === 'move') {
317
+ const content = typeof result.verifiedContent === 'string'
318
+ ? result.verifiedContent : original.get(path)?.content;
319
+ if (!operation.to || typeof content !== 'string') { unsupported.push(path); continue; }
320
+ if (operation.to !== path) expected.set(path, { absent: true });
321
+ expected.set(operation.to, { content });
322
+ continue;
323
+ }
324
+ if (operation.type === 'binary-create' || operation.type === 'overwrite-binary') {
325
+ const hash = operation.sha256 || operation.assetRef?.sha256;
326
+ if (/^[a-f0-9]{64}$/i.test(hash || '')) expected.set(path, { sha256: hash.toLowerCase() });
327
+ else if (typeof operation.contentBase64 === 'string'
328
+ && (operation.contentBase64 || !operation.assetRef || operation.size === 0)) {
329
+ expected.set(path, { contentBase64: operation.contentBase64.replace(/\s/g, '') });
330
+ } else unsupported.push(path);
331
+ continue;
332
+ }
333
+ const content = typeof result.verifiedContent === 'string' ? result.verifiedContent
334
+ : operation.type === 'create' && typeof operation.content === 'string' ? operation.content
335
+ : operation.type === 'edit' && typeof operation.replaceAll === 'string' ? operation.replaceAll : undefined;
336
+ if (typeof content === 'string') expected.set(path, { content });
337
+ else unsupported.push(path);
338
+ }
339
+ return { expected, unsupported };
340
+ }
341
+
342
+ async function matchSavedWritebackSnapshot(snapshot, expectations, projectId) {
343
+ if (snapshot?.id !== projectId || snapshot?.ok === false
344
+ || snapshot?.capabilities?.method !== 'overleaf-zip'
345
+ || snapshot.capabilities.fullProjectSnapshot !== true || !Array.isArray(snapshot.files)
346
+ || snapshot.files.some(file => file?.source !== 'overleaf-zip')) {
347
+ return { matched: false, reason: 'authoritative_snapshot_unavailable' };
348
+ }
349
+ if (!expectations.expected.size || expectations.unsupported.length) {
350
+ return { matched: false, reason: 'expected_content_unavailable' };
351
+ }
352
+ const byPath = new Map(snapshot.files.map(file => [file.path, file]));
353
+ for (const [path, expected] of expectations.expected) {
354
+ const file = byPath.get(path);
355
+ if (expected.absent) {
356
+ if (file || snapshot.capabilities.skipped?.length) return { matched: false, reason: 'server_content_mismatch', path };
357
+ continue;
358
+ }
359
+ if (!file) return { matched: false, reason: 'server_content_mismatch', path };
360
+ if (typeof expected.content === 'string') {
361
+ if (file.content !== expected.content) return { matched: false, reason: 'server_content_mismatch', path };
362
+ } else {
363
+ if (typeof file.contentBase64 !== 'string') return { matched: false, reason: 'server_binary_unavailable', path };
364
+ if (expected.contentBase64 !== undefined) {
365
+ if (file.contentBase64.replace(/\s/g, '') !== expected.contentBase64) return { matched: false, reason: 'server_content_mismatch', path };
366
+ } else {
367
+ const hash = await binaryDigest(file.contentBase64);
368
+ if (!hash) return { matched: false, reason: 'binary_digest_unavailable', path };
369
+ if (hash !== expected.sha256) return { matched: false, reason: 'server_content_mismatch', path };
370
+ }
371
+ }
372
+ }
373
+ return { matched: true };
374
+ }
375
+
376
+ async function confirmPostWriteSave(options = {}) {
377
+ const { applied, project, runProjectId, assertCurrent, readSaveState, readSnapshot } = options;
378
+ if (!runProjectId || typeof assertCurrent !== 'function') {
379
+ return { verification: { ok: false, state: 'unavailable', signal: 'context_missing' } };
380
+ }
381
+ const now = options.now || Date.now;
382
+ const deadline = now() + 90000;
383
+ const attempts = [];
384
+ const isInterruption = error => ['codex_cancelled', 'aborted_project_changed', 'editor_project_id_unavailable'].includes(error?.code)
385
+ || error?.name === 'AbortError';
386
+ const probe = async duration => {
387
+ assertCurrent();
388
+ try {
389
+ const result = await readSaveState({ deadlineMs: Math.max(0, Math.min(duration, deadline - now())), requirePositiveSignal: true });
390
+ assertCurrent();
391
+ return result || { ok: false, state: 'unavailable', signal: 'unavailable' };
392
+ } catch (error) {
393
+ if (isInterruption(error)) throw error;
394
+ assertCurrent();
395
+ return { ok: false, state: 'unavailable', signal: 'unavailable', reason: String(error?.message || error).slice(0, 800) };
396
+ }
397
+ };
398
+ const receiptExpectations = buildSavedWritebackExpectations(applied, project);
399
+ assertCurrent();
400
+ if (await receiptsConfirmExpectedFiles(applied, receiptExpectations, runProjectId)) {
401
+ assertCurrent();
402
+ return { verification: { ok: true, state: 'verified_saved', source: 'server-receipts' } };
403
+ }
404
+ const primary = await probe(1500);
405
+ if (primary.ok === true && primary.state === 'verified_saved') {
406
+ return { verification: { ...primary, source: 'save-indicator' } };
407
+ }
408
+ const expectations = buildSavedWritebackExpectations(applied, project);
409
+ let lastReason = '';
410
+ if (primary.signal !== 'offline' && expectations.expected.size && !expectations.unsupported.length) {
411
+ for (let attempt = 0; attempt < 3 && deadline - now() > 1000; attempt++) {
412
+ assertCurrent();
413
+ try {
414
+ const snapshot = await readSnapshot({
415
+ force: true, maxAgeMs: 0, zipOnly: true, serverOnly: true,
416
+ includeBinaryFiles: true, allowEditorNavigation: false, requireFullProject: true,
417
+ zipTimeoutMs: Math.max(1, Math.min(30000, deadline - now())),
418
+ saveCheckId: String(now()) + '-' + attempt + '-' + Math.random().toString(36).slice(2, 8)
419
+ });
420
+ assertCurrent();
421
+ const match = await matchSavedWritebackSnapshot(snapshot, expectations, runProjectId);
422
+ assertCurrent();
423
+ attempts.push({ attempt: attempt + 1, result: match.matched ? 'matched' : match.reason, path: match.path || '' });
424
+ if (match.matched) return {
425
+ verification: { ok: true, state: 'verified_saved', source: 'overleaf-zip', recovered: true,
426
+ primary: { state: primary.state, signal: primary.signal, reason: primary.reason }, attempts },
427
+ snapshot
428
+ };
429
+ lastReason = match.reason;
430
+ } catch (error) {
431
+ if (isInterruption(error)) throw error;
432
+ assertCurrent();
433
+ lastReason = String(error?.message || error).slice(0, 800);
434
+ attempts.push({ attempt: attempt + 1, result: 'snapshot_unavailable', reason: lastReason });
435
+ }
436
+ }
437
+ }
438
+ const final = primary.signal === 'offline' ? primary : await probe(2000);
439
+ if (final.ok === true && final.state === 'verified_saved') {
440
+ return { verification: { ...final, source: 'save-indicator', recovered: true, attempts } };
441
+ }
442
+ return { verification: {
443
+ ok: false, state: final.state === 'unavailable' ? 'unavailable' : 'unknown_timeout',
444
+ signal: final.signal || primary.signal || 'unavailable',
445
+ reason: final.reason || primary.reason || 'No positive save evidence was available.',
446
+ recoveryReason: lastReason || (expectations.unsupported.length ? 'expected_content_unavailable' : ''),
447
+ attempts
448
+ } };
449
+ }
450
+
451
+ async function bytesDigest(bytes) {
452
+ if (!globalThis.crypto?.subtle) return '';
453
+ const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes);
454
+ return Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('');
455
+ }
456
+
457
+ async function textDigest(content) {
458
+ return typeof TextEncoder === 'function' ? bytesDigest(new TextEncoder().encode(content)) : '';
459
+ }
460
+
461
+ async function binaryDigest(contentBase64) {
462
+ if (typeof contentBase64 !== 'string' || typeof atob !== 'function') return '';
463
+ let bytes;
464
+ try { bytes = Uint8Array.from(atob(contentBase64.replace(/\s/g, '')), value => value.charCodeAt(0)); }
465
+ catch (_error) { return ''; }
466
+ return bytesDigest(bytes);
467
+ }
468
+
469
+ async function receiptsConfirmExpectedFiles(applied, expectations, projectId) {
470
+ if (!expectations.expected.size || expectations.unsupported.length) return false;
471
+ const entries = getAppliedEntries(applied);
472
+ for (const [path, expected] of expectations.expected) {
473
+ if (typeof expected.content !== 'string') return false;
474
+ const receipt = [...entries].reverse().find(entry => entry.operation?.path === path)?.result?.saveReceipt;
475
+ if (receipt?.v !== 1 || receipt.source !== 'overleaf-zip' || receipt.projectId !== projectId
476
+ || receipt.path !== path || !Number.isFinite(Date.parse(receipt.verifiedAt || ''))
477
+ || !/^[a-f0-9]{64}$/.test(receipt.sha256 || '') || receipt.sha256 !== await textDigest(expected.content)) return false;
478
+ }
479
+ return true;
480
+ }
481
+
482
+ async function buildSaveCheck(applied, project, owner) {
483
+ const expectations = buildSavedWritebackExpectations(applied, project);
484
+ if (expectations.unsupported.length) return null;
485
+ const files = [];
486
+ for (const [path, expected] of expectations.expected) {
487
+ if (expected.absent) files.push({ path, absent: true });
488
+ else if (typeof expected.content === 'string') {
489
+ const sha256 = await textDigest(expected.content);
490
+ if (!sha256) return null;
491
+ files.push({ path, sha256, kind: 'text' });
492
+ } else {
493
+ const sha256 = expected.sha256 || await binaryDigest(expected.contentBase64);
494
+ if (!sha256) return null;
495
+ files.push({ path, sha256, kind: 'binary' });
496
+ }
497
+ }
498
+ return files.length ? { ...owner, v: 1, files } : null;
499
+ }
500
+
501
+ async function confirmSaveCheck(check, options) {
502
+ if (!check?.projectId || !Array.isArray(check.files) || !check.files.length
503
+ || check.files.some(file => !file?.path || (file.absent !== true && !/^[a-f0-9]{64}$/i.test(file.sha256 || '')))) {
504
+ return { ok: false, state: 'unavailable', reason: 'expected_content_unavailable' };
505
+ }
506
+ const now = options.now || Date.now, deadline = now() + 90000;
507
+ let reason = 'server_snapshot_unavailable';
508
+ for (let attempt = 0; attempt < 3 && now() < deadline; attempt++) {
509
+ options.assertCurrent();
510
+ let snapshot;
511
+ try {
512
+ snapshot = await options.readSnapshot({ force: true, maxAgeMs: 0, zipOnly: true, serverOnly: true,
513
+ includeBinaryFiles: true, allowEditorNavigation: false, requireFullProject: true,
514
+ zipTimeoutMs: Math.min(30000, Math.max(1, deadline - now())), saveCheckId: String(now()) + '-' + attempt });
515
+ } catch (error) {
516
+ if (['codex_cancelled', 'aborted_project_changed', 'editor_project_id_unavailable'].includes(error?.code)
517
+ || error?.name === 'AbortError') throw error;
518
+ options.assertCurrent();
519
+ continue;
520
+ }
521
+ options.assertCurrent();
522
+ if (snapshot?.ok === false || snapshot?.id !== check.projectId
523
+ || snapshot.capabilities?.fullProjectSnapshot !== true
524
+ || snapshot.capabilities?.method !== 'overleaf-zip' || !Array.isArray(snapshot.files)) continue;
525
+ const files = new Map(snapshot.files.map(file => [file.path, file]));
526
+ let matched = true;
527
+ for (const expected of check.files) {
528
+ const file = files.get(expected.path);
529
+ if (expected.absent) { if (file || snapshot.capabilities.skipped?.length) matched = false; continue; }
530
+ if (!file || file.source !== 'overleaf-zip') { matched = false; continue; }
531
+ const kind = typeof file.content === 'string' ? 'text'
532
+ : typeof file.contentBase64 === 'string' ? 'binary' : '';
533
+ if (!kind || (expected.kind && expected.kind !== kind)) { matched = false; continue; }
534
+ const hash = kind === 'binary' ? await binaryDigest(file.contentBase64) : await textDigest(file.content);
535
+ if (!hash || hash !== expected.sha256.toLowerCase()) matched = false;
536
+ }
537
+ options.assertCurrent();
538
+ if (matched) return { ok: true, state: 'verified_saved', source: 'overleaf-zip' };
539
+ reason = 'server_content_mismatch';
540
+ }
541
+ return { ok: false, state: 'unknown_timeout', reason };
542
+ }
543
+
293
544
  return {
545
+ confirmPostWriteSave,
546
+ buildSaveCheck,
547
+ confirmSaveCheck,
548
+ buildSavedWritebackExpectations,
549
+ matchSavedWritebackSnapshot,
294
550
  buildSyncApplyOperations,
295
551
  computeSingleTextPatch,
296
552
  formatUnsupportedLocalChangeSummary,
553
+ formatUnsupportedLocalChangeReason,
297
554
  getAppliedOperationPaths,
298
555
  getAppliedSyncChanges,
299
556
  getSyncChangePatches,