codex-overleaf-link 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/README.md +272 -311
  2. package/README.zh-CN.md +287 -326
  3. package/extension/bootstrap/background.js +47 -10
  4. package/extension/bootstrap/manifest.template.json +9 -4
  5. package/extension/bootstrap/popup.html +10 -16
  6. package/extension/bootstrap/update.js +25 -2
  7. package/extension/runtime-manifest.json +3 -1
  8. package/extension/src/background.js +28 -3
  9. package/extension/src/backgroundSharedSessions.js +380 -0
  10. package/extension/src/backgroundUpdateCoordinator.js +442 -379
  11. package/extension/src/content/activeTurnControl.js +8 -0
  12. package/extension/src/content/assetTransferBroker.js +186 -12
  13. package/extension/src/content/changeHistoryView.js +175 -0
  14. package/extension/src/content/composerAttachments.js +24 -22
  15. package/extension/src/content/composerPanel.js +23 -8
  16. package/extension/src/content/contentRuntime.js +572 -571
  17. package/extension/src/content/diagnosticsController.js +1 -2
  18. package/extension/src/content/diagnosticsExport.js +222 -0
  19. package/extension/src/content/diagnosticsPanel.js +48 -19
  20. package/extension/src/content/diffReviewPanel.js +8 -5
  21. package/extension/src/content/generated/content.bundle.js +8974 -1781
  22. package/extension/src/content/generated/content.bundle.meta.json +22 -3
  23. package/extension/src/content/legacyGlobalRegistry.js +19 -1
  24. package/extension/src/content/localSkillsPanel.js +42 -7
  25. package/extension/src/content/markdownDomRenderer.js +9 -4
  26. package/extension/src/content/modelPicker.js +1 -1
  27. package/extension/src/content/pageBridgeClient.js +18 -3
  28. package/extension/src/content/panelMaintenance.js +51 -120
  29. package/extension/src/content/panelRenderer.js +4 -0
  30. package/extension/src/content/pendingInputView.js +109 -76
  31. package/extension/src/content/providerSettingsCoordinator.js +17 -6
  32. package/extension/src/content/providerSettingsDialog.js +661 -108
  33. package/extension/src/content/recentProjects.js +364 -356
  34. package/extension/src/content/runActivitySummary.js +631 -0
  35. package/extension/src/content/runController.js +3 -1
  36. package/extension/src/content/runFailureNotice.js +14 -1
  37. package/extension/src/content/runGuidanceController.js +4 -3
  38. package/extension/src/content/runPresence.js +306 -0
  39. package/extension/src/content/runQueueScheduler.js +2 -1
  40. package/extension/src/content/runResultActions.js +123 -3
  41. package/extension/src/content/runTimelineView.js +194 -164
  42. package/extension/src/content/scopedPersistenceCoordinator.js +2 -1
  43. package/extension/src/content/scopedPersistencePanelState.js +45 -2
  44. package/extension/src/content/selectionContextView.js +300 -0
  45. package/extension/src/content/sessionPersistence.js +10 -2
  46. package/extension/src/content/settingsPanel.js +93 -54
  47. package/extension/src/content/settingsWorkbench.js +374 -0
  48. package/extension/src/content/sharedSessionViewSync.js +72 -0
  49. package/extension/src/content/subagentActivityView.js +195 -0
  50. package/extension/src/content/trackedChangeCaptureController.js +7 -3
  51. package/extension/src/content/updateNotice.js +121 -50
  52. package/extension/src/content/writebackController.js +257 -0
  53. package/extension/src/content/writebackOrchestrator.js +146 -141
  54. package/extension/src/content/writebackPlan.js +231 -0
  55. package/extension/src/content/writebackRecovery.js +122 -0
  56. package/extension/src/content/writingStyleSettings.js +576 -0
  57. package/extension/src/content/writtenChangesView.js +152 -0
  58. package/extension/src/page/binaryAssetUploader.js +97 -19
  59. package/extension/src/page/overleafProjectSnapshot.js +1 -0
  60. package/extension/src/page/referenceProjects.js +148 -0
  61. package/extension/src/page/saveState.js +58 -3
  62. package/extension/src/page/snapshotRouter.js +44 -13
  63. package/extension/src/page/textCoordinates.js +102 -0
  64. package/extension/src/page/textFileCreator.js +350 -59
  65. package/extension/src/page/trackedChangeCapture.js +42 -56
  66. package/extension/src/page/trackedChangeOwnership.js +198 -0
  67. package/extension/src/page/trackedChangeReplay.js +164 -0
  68. package/extension/src/page/trackedChangesLifecycle.js +139 -150
  69. package/extension/src/page/treeOperations.js +36 -29
  70. package/extension/src/page/writebackReceiptJournal.js +112 -0
  71. package/extension/src/page/writebackRouter.js +176 -136
  72. package/extension/src/pageBridge.js +59 -101
  73. package/extension/src/popup.js +203 -59
  74. package/extension/src/shared/agentTranscript.js +18 -4
  75. package/extension/src/shared/auditRecords.js +71 -0
  76. package/extension/src/shared/changeHistoryModel.js +94 -0
  77. package/extension/src/shared/compatibility.js +1 -1
  78. package/extension/src/shared/compileAdapter.js +42 -0
  79. package/extension/src/shared/failureReasons.js +20 -0
  80. package/extension/src/shared/globalPreferences.js +1 -1
  81. package/extension/src/shared/i18n.js +187 -65
  82. package/extension/src/shared/managedUpdateProjection.js +6 -3
  83. package/extension/src/shared/pageRpcContract.js +7 -1
  84. package/extension/src/shared/providerProfiles.js +109 -0
  85. package/extension/src/shared/runActivityModel.js +361 -0
  86. package/extension/src/shared/runExecutionSnapshot.js +8 -2
  87. package/extension/src/shared/runExecutionSnapshotCodec.js +16 -4
  88. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +6 -3
  89. package/extension/src/shared/selectionContext.js +28 -0
  90. package/extension/src/shared/sessionState.js +97 -6
  91. package/extension/src/shared/sharedSessionBridge.js +288 -0
  92. package/extension/src/shared/storageDb.js +23 -55
  93. package/extension/src/shared/storageMigration.js +3 -0
  94. package/extension/src/shared/storageRunActions.js +54 -4
  95. package/extension/src/shared/storageValuePruning.js +40 -0
  96. package/extension/src/shared/subagentActivityModel.js +174 -0
  97. package/extension/src/shared/undoOperations.js +87 -0
  98. package/extension/src/shared/updateConsent.js +37 -8
  99. package/extension/src/shared/writebackIntent.js +62 -0
  100. package/extension/src/shared/writebackSettlement.js +29 -10
  101. package/extension/src/shared/writingStyle.js +33 -0
  102. package/extension/styles/panel.css +2136 -744
  103. package/extension/styles/popup.css +110 -0
  104. package/native-host/src/anthropicBridgeRequest.js +2 -1
  105. package/native-host/src/codexModelRecovery.js +107 -0
  106. package/native-host/src/codexPromptAssembly.js +2 -0
  107. package/native-host/src/codexProviderLaunch.js +3 -3
  108. package/native-host/src/codexRuntimeIdentity.js +1 -0
  109. package/native-host/src/codexSessionRunner.js +146 -177
  110. package/native-host/src/codexSessionTiming.js +71 -0
  111. package/native-host/src/managedInstall.js +5 -4
  112. package/native-host/src/nativeEnvironment.js +70 -0
  113. package/native-host/src/nativeQuotas.js +15 -8
  114. package/native-host/src/overleafWorkspaceRules.js +16 -0
  115. package/native-host/src/processStopBarrier.js +80 -0
  116. package/native-host/src/providerModelCatalog.js +4 -3
  117. package/native-host/src/providerProfile.js +13 -1
  118. package/native-host/src/providerReasoning.js +9 -5
  119. package/native-host/src/providerStore.js +4 -0
  120. package/native-host/src/readProgressGuard.js +84 -17
  121. package/native-host/src/releaseTransport.js +14 -5
  122. package/native-host/src/selectionScope.js +58 -0
  123. package/native-host/src/skills/parallel-subagents/SKILL.md +31 -3
  124. package/native-host/src/skills/writing-style-builder/SKILL.md +146 -0
  125. package/native-host/src/subagentBroker.js +102 -98
  126. package/native-host/src/subagentTelemetry.js +316 -0
  127. package/native-host/src/subagentWorkspace.js +103 -0
  128. package/native-host/src/subagentWorkspacePath.js +31 -0
  129. package/native-host/src/taskRunnerRuntime.js +55 -5
  130. package/native-host/src/turnAttachments.js +113 -0
  131. package/native-host/src/updateManager.js +122 -12
  132. package/native-host/src/updateNetworkControl.js +75 -0
  133. package/native-host/src/updateTrust.js +21 -2
  134. package/native-host/src/vendor/pdfjs/LICENSE +177 -0
  135. package/native-host/src/vendor/pdfjs/SOURCE.txt +3 -0
  136. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  137. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  138. package/native-host/src/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
  139. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  140. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  141. package/native-host/src/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
  142. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  143. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  144. package/native-host/src/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  145. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  146. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  147. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  148. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  149. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  150. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  151. package/native-host/src/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
  152. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  153. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  154. package/native-host/src/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
  155. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  156. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  157. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  158. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  159. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  160. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  161. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  162. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  163. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  164. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  165. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  166. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  167. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  168. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  169. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  170. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  171. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  172. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  173. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  174. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  175. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  176. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  177. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  178. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  179. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  180. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  181. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  182. package/native-host/src/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
  183. package/native-host/src/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
  184. package/native-host/src/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  185. package/native-host/src/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  186. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  187. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  188. package/native-host/src/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  189. package/native-host/src/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  190. package/native-host/src/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  191. package/native-host/src/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  192. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  193. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  194. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  195. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  196. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  197. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  198. package/native-host/src/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
  199. package/native-host/src/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
  200. package/native-host/src/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
  201. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  202. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  203. package/native-host/src/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
  204. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  205. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  206. package/native-host/src/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
  207. package/native-host/src/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
  208. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  209. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  210. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  211. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  212. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  213. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  214. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  215. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  216. package/native-host/src/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
  217. package/native-host/src/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
  218. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  219. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  220. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  221. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  222. package/native-host/src/vendor/pdfjs/cmaps/H.bcmap +0 -0
  223. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  224. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  225. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  226. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  227. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  228. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  229. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  230. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  231. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  232. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  233. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  234. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  235. package/native-host/src/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
  236. package/native-host/src/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
  237. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  238. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  239. package/native-host/src/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
  240. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  241. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  242. package/native-host/src/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
  243. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  244. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  245. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  246. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  247. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  248. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  249. package/native-host/src/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
  250. package/native-host/src/vendor/pdfjs/cmaps/LICENSE +36 -0
  251. package/native-host/src/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
  252. package/native-host/src/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
  253. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  254. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  255. package/native-host/src/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
  256. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  257. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  258. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  259. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  260. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  261. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  262. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  263. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  264. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  265. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  266. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  267. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  268. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  269. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  270. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  271. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  272. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  273. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  274. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  275. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  276. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  277. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  278. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  279. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  280. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  281. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  282. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  283. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  284. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  285. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  286. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  287. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  288. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  289. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  290. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  291. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  292. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  293. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  294. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  295. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  296. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  297. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  298. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  299. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  300. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  301. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  302. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  303. package/native-host/src/vendor/pdfjs/cmaps/V.bcmap +0 -0
  304. package/native-host/src/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  305. package/native-host/src/vendor/pdfjs/pdf.mjs +26740 -0
  306. package/native-host/src/vendor/pdfjs/pdf.worker.mjs +62201 -0
  307. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  308. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  309. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  310. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  311. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  312. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  313. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  314. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  315. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  316. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  317. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  318. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +102 -0
  319. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  320. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  321. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  322. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  323. package/native-host/src/writingStyleEvidence.js +102 -0
  324. package/native-host/src/writingStylePdfWorker.js +35 -0
  325. package/native-host/src/writingStyleProfile.js +165 -0
  326. package/native-host/src/writingStyleRuntime.js +447 -0
  327. package/package.json +1 -1
@@ -4,6 +4,8 @@
4
4
  })(typeof window !== 'undefined' ? window : globalThis, function () {
5
5
  'use strict';
6
6
 
7
+ const MAX_CONFIRMATION_OBSERVATIONS = 8;
8
+
7
9
  function create(deps = {}) {
8
10
  const { window, document, treeOperations, uploadHelpers, snapshotRouter } = deps;
9
11
  const { getProjectId, findFileTreeNode, projectPathExists, collectProjectTextPaths,
@@ -14,6 +16,148 @@
14
16
  };
15
17
  const normalizeDomText = value => String(value || '').replace(/\s+/g, ' ').trim();
16
18
  const delay = ms => new Promise(resolve => window.setTimeout(resolve, ms));
19
+ const now = typeof deps.now === 'function' ? deps.now : Date.now;
20
+ const verificationReads = new Set();
21
+ const verificationBudget = () => ({ remainingMs: 90000 });
22
+ async function pauseVerification(budget, durationMs) {
23
+ const started = now();
24
+ await delay(Math.max(0, Math.min(durationMs, budget.remainingMs)));
25
+ budget.remainingMs = Math.max(0, budget.remainingMs - Math.max(0, now() - started));
26
+ }
27
+ function assertVerificationOwner(projectId, isCurrent) {
28
+ if (!projectId || getProjectId() !== projectId) {
29
+ throw Object.assign(new Error('The project changed during writeback.'), { code: 'aborted_project_changed' });
30
+ }
31
+ if (isCurrent?.() === false) {
32
+ throw Object.assign(new Error('Writeback was cancelled.'), { code: 'codex_cancelled' });
33
+ }
34
+ }
35
+ async function boundedSnapshotRead(params, controller, timeoutMs) {
36
+ const deadline = now() + timeoutMs;
37
+ const expired = diagnostics => Object.assign(new Error('The server snapshot request timed out.'), {
38
+ code: 'source_zip_unavailable', diagnostics,
39
+ technicalMessage: 'The server snapshot request reached its deletion verification deadline.'
40
+ });
41
+ let timer, onAbort;
42
+ try {
43
+ const interruption = new Promise((_, reject) => {
44
+ onAbort = () => reject(Object.assign(new Error('Writeback was cancelled.'), { code: 'codex_cancelled' }));
45
+ controller?.signal.addEventListener('abort', onAbort, { once: true });
46
+ timer = window.setTimeout(() => { reject(expired(null)); controller?.abort(); }, timeoutMs);
47
+ });
48
+ const result = await Promise.race([snapshotRouter.fetchProjectZipSnapshot(params), interruption]);
49
+ if (now() >= deadline) throw expired(result?.diagnostics);
50
+ return result;
51
+ } finally {
52
+ (window.clearTimeout || globalThis.clearTimeout)(timer);
53
+ controller?.signal.removeEventListener('abort', onAbort);
54
+ }
55
+ }
56
+ async function readServerSnapshot({ projectId, isCurrent, budget = verificationBudget(), includeBinaryFiles = false,
57
+ singleAttempt = false, deadlineAt = Infinity }) {
58
+ const started = now(), deadline = Math.min(started + Math.max(0, budget.remainingMs), deadlineAt);
59
+ const attemptLimit = singleAttempt ? 1 : 3;
60
+ let last;
61
+ try {
62
+ for (let attempt = 0; attempt < attemptLimit && now() < deadline; attempt++) {
63
+ assertVerificationOwner(projectId, isCurrent);
64
+ const Controller = window.AbortController || globalThis.AbortController;
65
+ const controller = typeof Controller === 'function' ? new Controller() : null;
66
+ if (controller) verificationReads.add(controller);
67
+ let result;
68
+ try {
69
+ const params = { force: true, maxAgeMs: 0,
70
+ serverOnly: true, writebackVerification: true, includeBinaryFiles, includeContent: true,
71
+ signal: controller?.signal, saveCheckId: String(now()) + '-' + attempt,
72
+ zipTimeoutMs: Math.min(30000, Math.max(1, deadline - now())) };
73
+ result = singleAttempt ? await boundedSnapshotRead(params, controller, params.zipTimeoutMs)
74
+ : await snapshotRouter.fetchProjectZipSnapshot(params);
75
+ } catch (error) {
76
+ if (singleAttempt) assertVerificationOwner(projectId, isCurrent);
77
+ if (['codex_cancelled', 'aborted_project_changed'].includes(error?.code)) throw error;
78
+ result = { ok: false, reason: error?.message || String(error), ...(singleAttempt ? {
79
+ reason: error?.technicalMessage || error?.message || String(error), diagnostics: error?.diagnostics,
80
+ status: error?.status || error?.statusCode || error?.response?.status, code: error?.code, retryable: error?.retryable
81
+ } : {}) };
82
+ } finally { if (controller) verificationReads.delete(controller); }
83
+ assertVerificationOwner(projectId, isCurrent);
84
+ if (result?.ok === true && Array.isArray(result.files)) return result;
85
+ last = Object.assign(new Error('The server project snapshot could not be read.'), {
86
+ code: 'source_zip_unavailable', diagnostics: result?.diagnostics || null,
87
+ technicalMessage: result?.reason || 'No complete server snapshot was returned.',
88
+ ...(singleAttempt ? { status: result?.status || result?.statusCode, originalCode: result?.code, retryable: result?.retryable } : {})
89
+ });
90
+ const attempts = result?.diagnostics?.attempts || [];
91
+ if (attempts.length && attempts.every(item => [401, 403].includes(item.status))) break;
92
+ if (attempt < attemptLimit - 1 && now() < deadline) await delay(Math.min(1000 * (2 ** attempt), deadline - now()));
93
+ }
94
+ throw last || Object.assign(new Error('The project verification budget was exhausted.'), { code: 'source_zip_unavailable' });
95
+ } finally { budget.remainingMs = Math.max(0, budget.remainingMs - Math.max(0, now() - started)); }
96
+ }
97
+ function verificationFailure(error, changedDocument, phase) {
98
+ const interrupted = ['codex_cancelled', 'aborted_project_changed'].includes(error?.code);
99
+ const code = interrupted ? error.code : changedDocument ? 'file_upload_unconfirmed' : 'source_zip_unavailable';
100
+ const reason = interrupted ? error.message : changedDocument
101
+ ? 'The file was submitted, but its saved content could not be confirmed.'
102
+ : 'The project could not be checked before uploading this file.';
103
+ return { ok: false, code, reason, changedDocument, diagnostics: {
104
+ verificationPhase: phase, ...(error?.diagnostics || {}), reason: error?.technicalMessage || error?.message || ''
105
+ }, failure: { code, stage: changedDocument ? 'verify' : 'preflight', severity: changedDocument ? 'warning' : 'blocked',
106
+ retryable: !interrupted, terminalState: interrupted ? 'cancelled' : changedDocument ? 'needs_review' : 'blocked',
107
+ changedDocument, userMessage: reason, nextAction: interrupted ? '' : 'Retry sync to check the file and upload only if it is missing.',
108
+ technicalMessage: error?.technicalMessage || error?.message || '' } };
109
+ }
110
+ async function makeSaveReceipt(projectId, path, content) {
111
+ const cryptoApi = window.crypto || globalThis.crypto;
112
+ if (!cryptoApi?.subtle || typeof TextEncoder !== 'function') return undefined;
113
+ const digest = await cryptoApi.subtle.digest('SHA-256', new TextEncoder().encode(content));
114
+ return { v: 1, source: 'overleaf-zip', projectId, path,
115
+ sha256: Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join(''),
116
+ verifiedAt: new Date().toISOString() };
117
+ }
118
+ async function beginCreateBatch(operations, options = {}) {
119
+ const projectId = getProjectId(), budget = verificationBudget();
120
+ const batch = { projectId, budget, targets: new Set(operations.map(op => op.path)),
121
+ before: new Map(), pending: new Map(), blocked: new Set(), isCurrent: options.isCurrent, error: null };
122
+ try {
123
+ const snapshot = await readServerSnapshot({ projectId, budget, isCurrent: options.isCurrent });
124
+ batch.before = new Map(snapshot.files.map(file => [file.path, file]));
125
+ } catch (error) { batch.error = error; }
126
+ return batch;
127
+ }
128
+ async function finishCreateBatch(batch, entries, appliedEntries = []) {
129
+ let observed = new Map(), lastError = batch.error;
130
+ try {
131
+ for (let attempt = 0; entries.length && attempt < 3 && batch.budget.remainingMs > 0; attempt++) {
132
+ const snapshot = await readServerSnapshot(batch);
133
+ observed = new Map(snapshot.files.map(file => [file.path, file]));
134
+ if (entries.every(entry => observed.get(entry.operation.path)?.content === entry.operation.content)) break;
135
+ if (attempt < 2) await pauseVerification(batch.budget, 500 * (2 ** attempt));
136
+ }
137
+ assertVerificationOwner(batch.projectId, batch.isCurrent);
138
+ } catch (error) { lastError = error; }
139
+ const results = [];
140
+ for (const entry of entries) {
141
+ const operation = entry.operation;
142
+ if (!lastError && observed.get(operation.path)?.content === operation.content) {
143
+ results.push({ operation, result: { ok: true, verified: true, changedDocument: true,
144
+ method: 'overleaf.native-text-upload', verification: 'overleaf-zip',
145
+ saveReceipt: await makeSaveReceipt(batch.projectId, operation.path, operation.content) } });
146
+ } else {
147
+ results.push({ operation, result: verificationFailure(lastError || new Error(
148
+ observed.has(operation.path) ? 'Server content differs from the submitted file.' : 'The file is not yet present in the server snapshot.'
149
+ ), true, 'post-upload') });
150
+ }
151
+ }
152
+ // The final fresh snapshot can also prove unrelated edits in this batch.
153
+ if (!lastError) for (const entry of appliedEntries) {
154
+ const content = entry.result?.verifiedContent ?? (entry.operation?.type === 'create' ? entry.operation.content : undefined);
155
+ if (typeof content === 'string' && observed.get(entry.operation.path)?.content === content) {
156
+ entry.result.saveReceipt = await makeSaveReceipt(batch.projectId, entry.operation.path, content);
157
+ }
158
+ }
159
+ return results;
160
+ }
17
161
  const controlName = node => {
18
162
  const labelledBy = (node.getAttribute('aria-labelledby') || '').split(/\s+/).filter(Boolean)
19
163
  .map(id => document.getElementById?.(id)?.textContent || '').join(' ');
@@ -51,9 +195,8 @@
51
195
  const target = normalizeSafeProjectPath(entryPath);
52
196
  const projectId = getProjectId();
53
197
  const assertCurrent = () => {
54
- if (!target || !projectId || getProjectId() !== projectId || options.isCurrent?.() === false) {
55
- throw new Error('Project changed or folder creation was cancelled.');
56
- }
198
+ if (!target) throw Object.assign(new Error('A valid folder path is required.'), { code: 'invalid_project_path' });
199
+ assertVerificationOwner(projectId, options.isCurrent);
57
200
  };
58
201
  const visible = node => Boolean(node && !node.disabled && node.getClientRects?.().length);
59
202
  const dialogs = () => Array.from(document.querySelectorAll('[role="dialog"]')).filter(visible);
@@ -139,15 +282,14 @@
139
282
  return { ok: false, code: 'invalid_project_path', reason: 'A valid text-file path and project are required.' };
140
283
  }
141
284
  let mutationAttempted = false;
142
- const assertCurrent = () => {
143
- if (getProjectId() !== projectId || options.isCurrent?.() === false) {
144
- throw new Error('Project changed or the write was cancelled.');
145
- }
146
- };
285
+ let verificationPhase = 'preflight', zipReadCount = 0, lastZipFailure = null;
286
+ const readBudget = options.batch?.budget || verificationBudget();
287
+ let uploadDialog = null;
288
+ const assertCurrent = () => assertVerificationOwner(projectId, options.isCurrent);
147
289
  const visible = node => Boolean(node && !node.disabled && node.getClientRects?.().length);
148
290
  const dialogs = () => Array.from(document.querySelectorAll('[role="dialog"]')).filter(visible);
149
- const waitFor = async read => {
150
- const until = Date.now() + 5000;
291
+ const waitFor = async (read, timeoutMs = 5000) => {
292
+ const until = Date.now() + timeoutMs;
151
293
  do {
152
294
  assertCurrent();
153
295
  invalidateDomProjectPathCache();
@@ -174,13 +316,12 @@
174
316
  await waitFor(() => findFolder(folderPath)?.getAttribute('aria-expanded') === 'true');
175
317
  };
176
318
  const readServerFiles = async () => {
177
- assertCurrent();
178
- snapshotRouter.invalidateCache?.();
179
- const result = await snapshotRouter.fetchProjectZipSnapshot({ force: true, maxAgeMs: 0,
180
- includeBinaryFiles: false, includeContent: true, zipTimeoutMs: 15000 });
181
- assertCurrent();
182
- if (result?.ok !== true || !Array.isArray(result.files)) throw new Error('Server source ZIP is unavailable; text creation cannot be verified safely.');
183
- return result.files;
319
+ zipReadCount += 1;
320
+ try {
321
+ const result = await readServerSnapshot({ projectId, budget: readBudget,
322
+ isCurrent: () => { assertCurrent(); return true; } });
323
+ return result.files;
324
+ } catch (error) { lastZipFailure = { reason: error.message, diagnostics: error.diagnostics }; throw error; }
184
325
  };
185
326
  const uploadText = async () => {
186
327
  const parentPath = target.split('/').slice(0, -1).join('/');
@@ -197,6 +338,7 @@
197
338
  const tree = document.querySelector('#ide-rail-tabs-tabpane-file-tree, #ide-redesign-file-tree, .ide-redesign-file-tree, .file-tree');
198
339
  chooseButton(tree, /^(?:Upload|上传)$/i).click();
199
340
  const dialog = await waitFor(() => dialogs().length === 1 && dialogs()[0]);
341
+ uploadDialog = dialog;
200
342
  const input = await waitFor(() => uploadHelpers.chooseOverleafFileInput(dialog.querySelectorAll('input[type="file"]')));
201
343
  if (projectPathExists(target)) throw new Error('The target appeared before upload; no replacement was attempted.');
202
344
  const file = new window.File([operation.content], target.split('/').pop(), { type: 'text/plain' });
@@ -205,9 +347,22 @@
205
347
  assertCurrent();
206
348
  uploadHelpers.assignFilesToInput(input, data.files, window);
207
349
  mutationAttempted = true;
350
+ options.batch?.pending.set(target, operation);
208
351
  input.dispatchEvent(new window.Event('change', { bubbles: true }));
209
352
  // Never click Replace/Overwrite. A concurrent name conflict must stop.
210
- const deadline = Date.now() + 15000;
353
+ verificationPhase = 'post-upload';
354
+ const deadline = now() + readBudget.remainingMs;
355
+ let observations = 0;
356
+ if (options.batch) {
357
+ await waitFor(() => {
358
+ if (dialogs().some(d => /replace|overwrite|替换|覆盖/i.test(d.innerText || ''))) {
359
+ options.batch.blocked.add(target);
360
+ throw new Error('Overleaf reported an upload conflict; replacement was not authorized.');
361
+ }
362
+ return projectPathExists(target);
363
+ }, 30000);
364
+ return;
365
+ }
211
366
  do {
212
367
  assertCurrent();
213
368
  const conflict = dialogs().some(d => /replace|overwrite|替换|覆盖/i.test(d.innerText || ''));
@@ -222,15 +377,24 @@
222
377
  await waitFor(() => !dialogs().length);
223
378
  return;
224
379
  }
225
- await delay(250);
226
- } while (Date.now() < deadline);
380
+ const remaining = deadline - now();
381
+ if (remaining <= 0) break;
382
+ await pauseVerification(readBudget, Math.min(250 * (2 ** Math.min(observations++, 3)), remaining));
383
+ } while (now() < deadline && observations < MAX_CONFIRMATION_OBSERVATIONS);
227
384
  throw new Error('The new file content was not confirmed in the server source ZIP. No editor content was modified.');
228
385
  };
229
386
  try {
230
387
  assertCurrent();
388
+ if (options.batch?.error) throw options.batch.error;
389
+ const existing = options.batch?.before.get(target);
390
+ if (existing && options.allowExistingMatches === true && existing.content === operation.content) {
391
+ return { ok: true, verified: true, changedDocument: false, idempotent: true,
392
+ method: 'overleaf.existing-text-match', verification: 'overleaf-zip',
393
+ saveReceipt: await makeSaveReceipt(projectId, target, operation.content) };
394
+ }
231
395
  if (projectPathExists(target)) return { ok: false, code: 'target_file_already_exists', reason: 'The target file already exists.' };
232
396
  if (!uploadHelpers?.assignFilesToInput || !snapshotRouter?.fetchProjectZipSnapshot) throw new Error('Verified text upload support is unavailable.');
233
- if ((await readServerFiles()).some(file => file.path === target)) {
397
+ if (options.batch ? options.batch.before.has(target) : (await readServerFiles()).some(file => file.path === target)) {
234
398
  return { ok: false, code: 'target_file_already_exists', reason: 'The server already contains the target file.' };
235
399
  }
236
400
  const parts = target.split('/');
@@ -243,15 +407,21 @@
243
407
  await ensureFolderExpanded(folderPath);
244
408
  }
245
409
  await uploadText();
410
+ if (options.batch) return { ok: false, pendingVerification: true, changedDocument: true };
246
411
  return { ok: true, method: 'overleaf.native-text-upload', changedDocument: true,
247
- verified: true, verification: 'overleaf-zip' };
412
+ verified: true, verification: 'overleaf-zip',
413
+ saveReceipt: await makeSaveReceipt(projectId, target, operation.content) };
248
414
  } catch (error) {
249
415
  return {
250
- ok: false,
251
- code: mutationAttempted ? 'file_tree_operation_unverified' : 'file_tree_controls_unavailable',
252
- reason: error.message,
253
- changedDocument: mutationAttempted
416
+ ...verificationFailure(error, mutationAttempted, verificationPhase),
417
+ diagnostics: { verificationPhase, zipReadCount, lastZipFailure }
254
418
  };
419
+ } finally {
420
+ if (uploadDialog && getProjectId() === projectId && dialogs().includes(uploadDialog)) {
421
+ const close = Array.from(uploadDialog.querySelectorAll('button')).find(node => visible(node)
422
+ && /^(?:Close dialog|Close|Done|Cancel|关闭|完成|取消)$/i.test(controlName(node)));
423
+ close?.click();
424
+ }
255
425
  }
256
426
  }
257
427
  // Binary uploads use the same folder hierarchy as verified text uploads.
@@ -259,12 +429,33 @@
259
429
  async function prepareUploadParent(folderPath, options = {}) {
260
430
  const target = normalizeSafeProjectPath(folderPath);
261
431
  const projectId = getProjectId();
262
- if (!target || !projectId) throw new Error('A valid upload parent and project are required.');
263
- const assertCurrent = () => {
264
- if (getProjectId() !== projectId || options.isCurrent?.() === false) {
265
- throw new Error('Project changed or the upload was cancelled.');
432
+ if (!projectId || (!target && folderPath !== '')) throw new Error('A valid upload parent and project are required.');
433
+ const assertCurrent = () => assertVerificationOwner(projectId, options.isCurrent);
434
+ if (!target) {
435
+ // Uppy inherits the selected entity's parent. A previous nested upload
436
+ // must not redirect a root asset into that folder. Use a direct root
437
+ // file row, never the tree adapter's basename fallback.
438
+ assertCurrent();
439
+ const readRoot = () => document.querySelector('[data-testid="file-tree-list-root"], .file-tree-list[role="tree"]');
440
+ const root = readRoot();
441
+ const anchor = Array.from(root?.querySelectorAll?.('[role="treeitem"]') || []).find(row =>
442
+ row.closest?.('[role="tree"]') === root
443
+ && !row.querySelector('[data-file-type="folder"]')
444
+ && row.querySelector('[data-file-type="doc"][data-file-id], [data-file-type="file"][data-file-id]'));
445
+ if (!anchor) throw new Error('A root-level file could not be selected safely for asset upload.');
446
+ if (anchor.getAttribute('aria-selected') !== 'true') {
447
+ (anchor.querySelector('.file-tree-entity-button, .file-tree-entity-details') || anchor).click();
448
+ await delay(100);
266
449
  }
267
- };
450
+ assertCurrent();
451
+ const currentRoot = readRoot();
452
+ const selected = Array.from(currentRoot?.querySelectorAll?.('[role="treeitem"]') || [])
453
+ .filter(row => row.getAttribute('aria-selected') === 'true');
454
+ if (selected.length !== 1 || selected[0] !== anchor || anchor.closest('[role="tree"]') !== currentRoot) {
455
+ throw new Error('Overleaf did not confirm the selected root upload parent.');
456
+ }
457
+ return anchor;
458
+ }
268
459
  const expand = async path => {
269
460
  assertCurrent();
270
461
  const node = findFolder(path);
@@ -358,40 +549,88 @@
358
549
  async function deleteFile(operation, options = {}) {
359
550
  const target = normalizeSafeProjectPath(operation.path);
360
551
  const projectId = getProjectId();
552
+ const deadlineAt = Math.min(now() + 90000, Number.isFinite(options.deadlineAt) ? options.deadlineAt : Infinity);
553
+ const preflightDeadlineAt = deadlineAt - 15000;
361
554
  const expected = options.expectedContent;
555
+ const binary = operation.undoCreatedFile?.v === 1 && operation.undoCreatedFile.kind === 'binary'
556
+ && options.undoCreatedFile === true && /^[a-f0-9]{64}$/i.test(options.expectedSha256 || '');
557
+ const matchesExpected = async file => {
558
+ if (!binary) return file && file.content === expected;
559
+ if (typeof file?.contentBase64 !== 'string') return false;
560
+ const cryptoApi = window.crypto || globalThis.crypto;
561
+ if (!cryptoApi?.subtle) return false;
562
+ const decode = window.atob || globalThis.atob;
563
+ if (typeof decode !== 'function') return false;
564
+ const bytes = Uint8Array.from(decode(file.contentBase64.replace(/\s/g, '')), value => value.charCodeAt(0));
565
+ const hash = await cryptoApi.subtle.digest('SHA-256', bytes);
566
+ return Array.from(new Uint8Array(hash), byte => byte.toString(16).padStart(2, '0')).join('')
567
+ === options.expectedSha256.toLowerCase();
568
+ };
362
569
  const canDelete = options.canDelete || (() => typeof deps.readActiveEditorText === 'function'
363
570
  && treeOperations.getActiveFilePath?.() === target && deps.readActiveEditorText() === expected);
364
571
  let mutationAttempted = false;
365
572
  let stage = 'preflight';
366
573
  let ownedDialog = null;
574
+ let zipReadCount = 0;
575
+ const zipFailures = [];
576
+ const remainingMs = () => Math.max(0, deadlineAt - now());
577
+ const budgetExpired = () => Object.assign(new Error('The deletion verification budget was exhausted.'), {
578
+ code: 'delete_confirmation_budget_exhausted',
579
+ technicalMessage: mutationAttempted ? 'No time remains to confirm the deletion in the server snapshot.'
580
+ : 'Deletion was not submitted because at least 15000ms must remain for server confirmation.'
581
+ });
367
582
  const visible = node => Boolean(node && !node.disabled && node.getClientRects?.().length);
368
583
  const dialogs = () => Array.from(document.querySelectorAll('[role="dialog"]')).filter(visible);
369
584
  const assertCurrent = () => {
370
- if (!projectId || getProjectId() !== projectId || options.isCurrent?.() === false) {
371
- throw new Error('Project changed or the deletion was cancelled.');
372
- }
585
+ assertVerificationOwner(projectId, options.isCurrent);
586
+ if (now() >= (mutationAttempted ? deadlineAt : preflightDeadlineAt)) throw budgetExpired();
373
587
  };
374
588
  const waitFor = async (read, timeoutMs = 5000) => {
375
- const deadline = Date.now() + timeoutMs;
376
- do {
589
+ const deadline = Math.min(now() + timeoutMs, preflightDeadlineAt);
590
+ while (now() < deadline) {
377
591
  assertCurrent();
378
592
  invalidateDomProjectPathCache();
379
593
  const value = await read();
594
+ assertCurrent();
380
595
  if (value) return value;
381
- await delay(100);
382
- } while (Date.now() < deadline);
596
+ await delay(Math.max(0, Math.min(100, deadline - now())));
597
+ }
598
+ assertCurrent();
383
599
  throw new Error(`Overleaf did not confirm ${stage} before timeout.`);
384
600
  };
601
+ const canRetryRead = error => {
602
+ if (['codex_cancelled', 'aborted_project_changed'].includes(error?.code) || error?.retryable === false) return false;
603
+ const diagnostics = error?.diagnostics || {};
604
+ return ![error?.status, diagnostics.status, diagnostics.statusCode,
605
+ ...(Array.isArray(diagnostics.attempts) ? diagnostics.attempts.map(item => item?.status) : [])]
606
+ .some(status => [401, 403].includes(Number(status)));
607
+ };
385
608
  const readServerFiles = async () => {
386
- assertCurrent();
387
- snapshotRouter.invalidateCache?.();
388
- const result = await snapshotRouter.fetchProjectZipSnapshot({ force: true, maxAgeMs: 0,
389
- includeBinaryFiles: false, includeContent: true, zipTimeoutMs: 15000 });
390
- assertCurrent();
391
- if (result?.ok !== true || !Array.isArray(result.files)) {
392
- throw new Error('Server source ZIP is unavailable; deletion cannot be verified safely.');
609
+ const deadline = mutationAttempted ? deadlineAt : preflightDeadlineAt;
610
+ for (let attempt = 0; ; attempt++) {
611
+ assertCurrent();
612
+ snapshotRouter.invalidateCache?.();
613
+ zipReadCount++;
614
+ try {
615
+ const result = await readServerSnapshot({ projectId, includeBinaryFiles: binary, singleAttempt: true,
616
+ deadlineAt: deadline, budget: { remainingMs: Math.max(0, deadline - now()) }, isCurrent: options.isCurrent });
617
+ assertCurrent();
618
+ if ((result.skipped || []).some(file => file?.path === target)) {
619
+ throw Object.assign(new Error('The target was skipped in the server snapshot.'), {
620
+ code: 'source_zip_unavailable', retryable: false, diagnostics: result.diagnostics || null,
621
+ technicalMessage: 'The target was skipped in the server snapshot; absence cannot confirm deletion.'
622
+ });
623
+ }
624
+ return result.files;
625
+ } catch (error) {
626
+ zipFailures.push({ code: error?.originalCode || error?.code, status: error?.status,
627
+ technicalMessage: error?.technicalMessage || error?.message, diagnostics: error?.diagnostics || null,
628
+ remainingMs: remainingMs() });
629
+ if (zipFailures.length > MAX_CONFIRMATION_OBSERVATIONS) zipFailures.shift();
630
+ if (!canRetryRead(error) || now() >= deadline) throw error;
631
+ await delay(Math.min(1000 * (2 ** Math.min(attempt, 2)), deadline - now()));
632
+ }
393
633
  }
394
- return result.files;
395
634
  };
396
635
  const choose = (scope, selector, pattern) => {
397
636
  const matches = Array.from(scope.querySelectorAll(selector)).filter(node => visible(node)
@@ -406,26 +645,55 @@
406
645
  // Current Overleaf keeps the stable document ID on the draggable
407
646
  // entity inside the row. The menu only mounts on hover/selection.
408
647
  const rowIdentity = row => row?.querySelector('[data-file-type="doc"][data-file-id]')?.getAttribute('data-file-id')
648
+ || row?.querySelector('[data-file-type="file"][data-file-id]')?.getAttribute('data-file-id')
409
649
  || row?.getAttribute('data-entity-id') || row?.getAttribute('data-doc-id')
410
650
  || row?.getAttribute('data-file-id') || row?.querySelector('[id^="menu-button-"]')?.id || '';
411
651
  try {
412
- if (!target || !projectId || typeof expected !== 'string' || typeof canDelete !== 'function'
413
- || !window.CodexOverleafProjectFiles?.isTextProjectPath(target)) {
414
- throw new Error('A checked text-file pre-image is required for deletion.');
652
+ if (!target || !projectId || typeof canDelete !== 'function'
653
+ || (!binary && (typeof expected !== 'string' || !window.CodexOverleafProjectFiles?.isTextProjectPath(target)))) {
654
+ throw new Error('A checked file pre-image is required for deletion.');
415
655
  }
416
656
  assertCurrent();
417
657
  if (dialogs().length) throw new Error('An existing Overleaf dialog must be closed before deleting files.');
418
658
  if (!snapshotRouter?.fetchProjectZipSnapshot) throw new Error('Verified deletion support is unavailable.');
419
659
  const original = (await readServerFiles()).find(file => file.path === target);
420
- if (!original || original.content !== expected || canDelete() !== true) {
660
+ if (!original && options.undoCreatedFile === true && !resolveRow()) {
661
+ return { ok: true, method: 'overleaf.native-created-file-delete', changedDocument: false,
662
+ idempotent: true, verified: true, verification: 'overleaf-zip' };
663
+ }
664
+ const originalMatches = await matchesExpected(original);
665
+ assertCurrent();
666
+ if (!originalMatches) {
421
667
  throw new Error('The file no longer matches the checked pre-image; deletion was stopped.');
422
668
  }
423
669
  const parentPath = target.split('/').slice(0, -1).join('/');
424
- if (parentPath) await prepareUploadParent(parentPath, options);
670
+ // Folder expansion must share the preflight deadline too. Uploads keep their existing timing.
671
+ const parents = parentPath ? parentPath.split('/') : [];
672
+ for (let depth = 1; depth <= parents.length; depth++) {
673
+ assertCurrent();
674
+ const path = parents.slice(0, depth).join('/'), parent = findFolder(path);
675
+ if (!parent) throw new Error('The target parent folder is unavailable.');
676
+ if (parent.getAttribute('aria-expanded') !== 'false') continue;
677
+ stage = 'parent folder expansion';
678
+ const toggle = parent.querySelector('.folder-expand-collapse-button');
679
+ if (!toggle) throw new Error('The target parent folder cannot be expanded safely.');
680
+ toggle.click();
681
+ await waitFor(() => findFolder(path)?.getAttribute('aria-expanded') === 'true');
682
+ }
683
+ // Collapsed folders can hide an existing file from the tree adapter.
684
+ // Server presence proves existence; prepare its editor only after expansion.
685
+ if (!binary && canDelete() !== true && typeof options.prepareTextPreimage === 'function') {
686
+ const current = await options.prepareTextPreimage();
687
+ assertCurrent();
688
+ if (!current?.ok || current.text !== expected) {
689
+ throw new Error('The file no longer matches the checked pre-image; deletion was stopped.');
690
+ }
691
+ }
692
+ if (canDelete() !== true) throw new Error('The file no longer matches the checked pre-image; deletion was stopped.');
425
693
  const row = resolveRow();
426
694
  const identity = rowIdentity(row);
427
695
  if (!row || !identity || row.querySelector('[data-file-type="folder"]')) {
428
- throw new Error('An exact Overleaf text-file row could not be identified.');
696
+ throw new Error('An exact Overleaf file row could not be identified.');
429
697
  }
430
698
  stage = 'file selection';
431
699
  (row.querySelector('.file-tree-entity-details, .file-tree-entity-button') || row).click();
@@ -459,21 +727,42 @@
459
727
  };
460
728
  if (!verifyDialog()) throw new Error('The deletion dialog does not name exactly the authorized file.');
461
729
  const latest = (await readServerFiles()).find(file => file.path === target);
730
+ const latestMatches = await matchesExpected(latest);
462
731
  assertCurrent();
463
- if (!latest || latest.content !== expected || canDelete() !== true
732
+ if (!latestMatches || canDelete() !== true
464
733
  || rowIdentity(resolveRow()) !== identity || !verifyDialog()) {
465
734
  throw new Error('The target changed before confirmation; deletion was stopped.');
466
735
  }
467
736
  const confirm = choose(ownedDialog, 'button', /^(?:Delete|删除)$/i);
737
+ assertCurrent();
468
738
  stage = 'server deletion receipt';
469
739
  mutationAttempted = true;
470
740
  confirm.click();
471
- await waitFor(async () => !(await readServerFiles()).some(file => file.path === target), 15000);
472
- return { ok: true, method: 'overleaf.native-text-delete', changedDocument: true,
473
- verified: true, verification: 'overleaf-zip' };
741
+ for (let observation = 0; ; observation++) {
742
+ const files = await readServerFiles();
743
+ if (!files.some(file => file.path === target)) {
744
+ return { ok: true, method: binary ? 'overleaf.native-created-file-delete' : 'overleaf.native-text-delete', changedDocument: true,
745
+ verified: true, verification: 'overleaf-zip' };
746
+ }
747
+ await delay(Math.min(250 * (2 ** Math.min(observation, 3)), remainingMs()));
748
+ }
474
749
  } catch (error) {
475
- return { ok: false, code: mutationAttempted ? 'file_tree_operation_unverified' : 'file_tree_controls_unavailable',
476
- reason: error.message, stage: stage.replace(/ /g, '_'), changedDocument: mutationAttempted };
750
+ const interrupted = ['codex_cancelled', 'aborted_project_changed'].includes(error?.code);
751
+ const code = interrupted ? error.code : mutationAttempted ? 'file_tree_operation_unverified'
752
+ : error?.code || 'file_tree_controls_unavailable';
753
+ const retryable = canRetryRead(error), technicalMessage = error?.technicalMessage || error?.message || '';
754
+ const reason = interrupted ? error.message : mutationAttempted ? 'Deletion could not be confirmed.'
755
+ : code === 'source_zip_unavailable' ? 'The server copy could not be checked. No file was deleted.'
756
+ : code === 'delete_confirmation_budget_exhausted' ? 'Not enough time remains to verify deletion. No file was deleted.' : error.message;
757
+ return { ok: false, code, reason, stage: stage.replace(/ /g, '_'), changedDocument: mutationAttempted,
758
+ technicalMessage, deadlineAt, remainingMs: remainingMs(), diagnostics: {
759
+ ...(error?.diagnostics || {}), verificationPhase: 'delete-' + stage.replace(/ /g, '-'), zipReadCount, zipFailures,
760
+ reason: technicalMessage, originalCode: error?.originalCode || error?.code || '', status: error?.status,
761
+ deadlineAt, remainingMs: remainingMs(), mutationAttempted
762
+ }, failure: { code, stage: mutationAttempted ? 'verify' : 'preflight', severity: mutationAttempted ? 'warning' : 'blocked',
763
+ retryable, terminalState: interrupted ? 'cancelled' : mutationAttempted ? 'needs_review' : 'blocked',
764
+ changedDocument: mutationAttempted, file: target, operationType: 'delete', userMessage: reason,
765
+ nextAction: retryable ? 'Retry Undo to check the file.' : '', technicalMessage } };
477
766
  } finally {
478
767
  // Close only the dialog opened by this operation; never another modal.
479
768
  if (ownedDialog && dialogs().includes(ownedDialog) && getProjectId() === projectId) {
@@ -487,7 +776,9 @@
487
776
  async function createFile(operation, options = {}) {
488
777
  return createTextFileWithDom(operation, options);
489
778
  }
490
- return { createFile, deleteFile, prepareEditor, ensureWriteMode, prepareUploadParent, findFolderNode: findFolder };
779
+ return { createFile, deleteFile, prepareEditor, ensureWriteMode, prepareUploadParent, findFolderNode: findFolder,
780
+ beginCreateBatch, finishCreateBatch,
781
+ cancelVerifications() { for (const controller of verificationReads) controller.abort(); } };
491
782
  }
492
783
  return { create };
493
784
  });