codex-overleaf-link 2.4.1 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (397) hide show
  1. package/README.md +275 -305
  2. package/README.zh-CN.md +290 -320
  3. package/extension/bootstrap/background.js +99 -19
  4. package/extension/bootstrap/manifest.template.json +9 -4
  5. package/extension/bootstrap/popup.html +11 -16
  6. package/extension/bootstrap/popupFallback.js +55 -0
  7. package/extension/bootstrap/update.js +25 -2
  8. package/extension/bootstrap/updateStatus.js +1 -0
  9. package/extension/runtime-manifest.json +3 -1
  10. package/extension/src/background.js +234 -56
  11. package/extension/src/backgroundBootstrapMigration.js +181 -0
  12. package/extension/src/backgroundSharedSessions.js +440 -0
  13. package/extension/src/backgroundUpdateCoordinator.js +541 -381
  14. package/extension/src/backgroundUpdateSafety.js +173 -0
  15. package/extension/src/content/activeTurnControl.js +16 -2
  16. package/extension/src/content/applyResultFormatters.js +46 -73
  17. package/extension/src/content/assetTransferBroker.js +244 -12
  18. package/extension/src/content/changeHistoryView.js +175 -0
  19. package/extension/src/content/composerAttachments.js +117 -67
  20. package/extension/src/content/composerPanel.js +41 -8
  21. package/extension/src/content/contentRuntime.js +1084 -1099
  22. package/extension/src/content/diagnosticsController.js +122 -97
  23. package/extension/src/content/diagnosticsExport.js +236 -0
  24. package/extension/src/content/diagnosticsPanel.js +78 -19
  25. package/extension/src/content/diffReviewPanel.js +8 -5
  26. package/extension/src/content/directoryCleanupRecovery.js +54 -0
  27. package/extension/src/content/generated/content.bundle.js +14680 -3606
  28. package/extension/src/content/generated/content.bundle.meta.json +30 -3
  29. package/extension/src/content/globalPreferencesController.js +1 -1
  30. package/extension/src/content/legacyGlobalRegistry.js +25 -1
  31. package/extension/src/content/localSkillsPanel.js +141 -16
  32. package/extension/src/content/markdownDomRenderer.js +9 -4
  33. package/extension/src/content/markdownText.js +5 -6
  34. package/extension/src/content/modelPicker.js +8 -5
  35. package/extension/src/content/nativeCompatibilityController.js +45 -20
  36. package/extension/src/content/otWarmMirror.js +4 -5
  37. package/extension/src/content/otWarmMirrorController.js +17 -20
  38. package/extension/src/content/pageBridgeClient.js +33 -5
  39. package/extension/src/content/panelMaintenance.js +87 -132
  40. package/extension/src/content/panelRenderer.js +10 -0
  41. package/extension/src/content/pendingInputView.js +109 -76
  42. package/extension/src/content/projectProviderSelection.js +13 -4
  43. package/extension/src/content/projectSessionCleanup.js +109 -62
  44. package/extension/src/content/projectSettingsCoordinator.js +39 -2
  45. package/extension/src/content/providerSettingsCoordinator.js +63 -14
  46. package/extension/src/content/providerSettingsDialog.js +724 -113
  47. package/extension/src/content/recentProjects.js +21 -6
  48. package/extension/src/content/runActivitySummary.js +665 -0
  49. package/extension/src/content/runCompletionMeta.js +344 -0
  50. package/extension/src/content/runController.js +40 -15
  51. package/extension/src/content/runFailureNotice.js +34 -12
  52. package/extension/src/content/runGuidanceController.js +76 -30
  53. package/extension/src/content/runGuidanceView.js +59 -10
  54. package/extension/src/content/runPresence.js +306 -0
  55. package/extension/src/content/runQueueScheduler.js +150 -43
  56. package/extension/src/content/runRecoveryActions.js +146 -0
  57. package/extension/src/content/runResultActions.js +11 -8
  58. package/extension/src/content/runReviewController.js +574 -0
  59. package/extension/src/content/runScrollLayout.js +21 -2
  60. package/extension/src/content/runTimelineView.js +315 -290
  61. package/extension/src/content/scopedPersistencePanelState.js +51 -6
  62. package/extension/src/content/selectionContextView.js +300 -0
  63. package/extension/src/content/sensitivePreflight.js +140 -0
  64. package/extension/src/content/sessionForkController.js +80 -17
  65. package/extension/src/content/sessionManager.js +1 -1
  66. package/extension/src/content/sessionPersistence.js +6 -1
  67. package/extension/src/content/settingsPanel.js +84 -55
  68. package/extension/src/content/settingsWorkbench.js +379 -0
  69. package/extension/src/content/sharedSessionViewSync.js +109 -0
  70. package/extension/src/content/subagentActivityView.js +195 -0
  71. package/extension/src/content/trackedChangeCaptureController.js +7 -3
  72. package/extension/src/content/updateIdle.js +4 -1
  73. package/extension/src/content/updateNotice.js +179 -56
  74. package/extension/src/content/updateSafetyReceiver.js +58 -0
  75. package/extension/src/content/writebackController.js +308 -2
  76. package/extension/src/content/writebackOrchestrator.js +213 -199
  77. package/extension/src/content/writebackPlan.js +231 -0
  78. package/extension/src/content/writebackRecovery.js +122 -0
  79. package/extension/src/content/writingStyleSettings.js +600 -0
  80. package/extension/src/content/writtenChangesView.js +152 -0
  81. package/extension/src/page/binaryAssetUploader.js +223 -23
  82. package/extension/src/page/fileMover.js +256 -0
  83. package/extension/src/page/overleafProjectSnapshot.js +1 -0
  84. package/extension/src/page/ownedDirectoryCleanup.js +94 -0
  85. package/extension/src/page/referenceProjects.js +149 -0
  86. package/extension/src/page/reviewContext.js +113 -0
  87. package/extension/src/page/saveState.js +20 -6
  88. package/extension/src/page/snapshotRouter.js +124 -48
  89. package/extension/src/page/textCoordinates.js +102 -0
  90. package/extension/src/page/textFileCreator.js +442 -71
  91. package/extension/src/page/trackedChangeCapture.js +53 -57
  92. package/extension/src/page/trackedChangeOwnership.js +216 -0
  93. package/extension/src/page/trackedChangeReplay.js +184 -0
  94. package/extension/src/page/trackedChangesLifecycle.js +343 -339
  95. package/extension/src/page/treeOperations.js +33 -12
  96. package/extension/src/page/updateSafetyProbe.js +41 -0
  97. package/extension/src/page/writebackReceiptJournal.js +114 -0
  98. package/extension/src/page/writebackRouter.js +242 -201
  99. package/extension/src/pageBridge.js +73 -102
  100. package/extension/src/popup.js +261 -65
  101. package/extension/src/shared/agentTranscript.js +41 -4
  102. package/extension/src/shared/auditRecords.js +73 -0
  103. package/extension/src/shared/changeHistoryModel.js +94 -0
  104. package/extension/src/shared/compatibility.js +22 -6
  105. package/extension/src/shared/compileAdapter.js +101 -37
  106. package/extension/src/shared/composerAttachmentDraft.js +151 -0
  107. package/extension/src/shared/directoryCleanupProof.js +43 -0
  108. package/extension/src/shared/failureReasons.js +329 -3
  109. package/extension/src/shared/globalPreferences.js +31 -14
  110. package/extension/src/shared/governanceRules.js +4 -37
  111. package/extension/src/shared/i18n.js +389 -25
  112. package/extension/src/shared/managedUpdateProjection.js +8 -5
  113. package/extension/src/shared/nativeRequestIdentity.js +1 -0
  114. package/extension/src/shared/pageRpcContract.js +9 -2
  115. package/extension/src/shared/projectFiles.js +21 -44
  116. package/extension/src/shared/providerProfiles.js +114 -3
  117. package/extension/src/shared/runActivityModel.js +361 -0
  118. package/extension/src/shared/runExecutionSnapshot.js +8 -2
  119. package/extension/src/shared/runExecutionSnapshotCodec.js +16 -4
  120. package/extension/src/shared/runInputQueue.js +48 -5
  121. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +6 -3
  122. package/extension/src/shared/selectionContext.js +37 -0
  123. package/extension/src/shared/sessionState.js +166 -30
  124. package/extension/src/shared/settlementFacts.js +10 -2
  125. package/extension/src/shared/sharedSessionBridge.js +311 -0
  126. package/extension/src/shared/sharedSessionTransfer.js +140 -0
  127. package/extension/src/shared/staleGuard.js +118 -13
  128. package/extension/src/shared/storageDb.js +77 -78
  129. package/extension/src/shared/storageMigration.js +74 -111
  130. package/extension/src/shared/storageRunActions.js +211 -22
  131. package/extension/src/shared/storageValuePruning.js +40 -0
  132. package/extension/src/shared/subagentActivityModel.js +174 -0
  133. package/extension/src/shared/undoOperations.js +216 -6
  134. package/extension/src/shared/updateConsent.js +42 -11
  135. package/extension/src/shared/updateRuntimeIdentity.js +1 -1
  136. package/extension/src/shared/writebackIntent.js +78 -0
  137. package/extension/src/shared/writebackOperationEvidence.js +4 -15
  138. package/extension/src/shared/writebackSettlement.js +67 -48
  139. package/extension/src/shared/writingStyle.js +33 -0
  140. package/extension/styles/panel.css +1987 -385
  141. package/extension/styles/popup.css +110 -0
  142. package/native-host/src/anthropicBridgeRequest.js +6 -3
  143. package/native-host/src/anthropicBridgeResponse.js +6 -2
  144. package/native-host/src/anthropicMessagesBridge.js +24 -16
  145. package/native-host/src/bootstrapMigration.js +296 -0
  146. package/native-host/src/bootstrapMigrationContract.js +101 -0
  147. package/native-host/src/bootstrapUpdate.js +233 -0
  148. package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
  149. package/native-host/src/chatBridgeResponse.js +10 -0
  150. package/native-host/src/chatCompletionsBridge.js +21 -15
  151. package/native-host/src/codexConfigPaths.js +106 -0
  152. package/native-host/src/codexHome.js +66 -73
  153. package/native-host/src/codexHomeScope.js +124 -0
  154. package/native-host/src/codexInheritedAuth.js +90 -0
  155. package/native-host/src/codexLocalSkills.js +301 -0
  156. package/native-host/src/codexModelRecovery.js +107 -0
  157. package/native-host/src/codexPromptAssembly.js +33 -12
  158. package/native-host/src/codexProviderLaunch.js +3 -3
  159. package/native-host/src/codexProviderTest.js +24 -26
  160. package/native-host/src/codexRuntimeIdentity.js +1 -0
  161. package/native-host/src/codexSessionRunner.js +211 -318
  162. package/native-host/src/codexSessionTiming.js +21 -8
  163. package/native-host/src/codexSkillIsolation.js +170 -0
  164. package/native-host/src/codexThreadFork.js +19 -3
  165. package/native-host/src/index.js +19 -7
  166. package/native-host/src/managedInstall.js +115 -39
  167. package/native-host/src/managedInstallRecovery.js +140 -0
  168. package/native-host/src/managedRuntimeEvidence.js +57 -0
  169. package/native-host/src/mirrorWorkspace.js +92 -22
  170. package/native-host/src/nativeDoctor.js +15 -0
  171. package/native-host/src/nativeEnvironment.js +70 -0
  172. package/native-host/src/nativeHelperLifecycle.js +21 -0
  173. package/native-host/src/nativeHostRegistration.js +33 -0
  174. package/native-host/src/nativeQuotas.js +15 -8
  175. package/native-host/src/nativeRunLifecycle.js +71 -0
  176. package/native-host/src/overleafWorkspaceRules.js +16 -0
  177. package/native-host/src/processStopBarrier.js +115 -0
  178. package/native-host/src/projectPathPlatform.js +30 -0
  179. package/native-host/src/providerHttp.js +80 -0
  180. package/native-host/src/providerModelCatalog.js +4 -3
  181. package/native-host/src/providerProfile.js +17 -3
  182. package/native-host/src/providerReasoning.js +10 -11
  183. package/native-host/src/providerRuntime.js +1 -0
  184. package/native-host/src/providerStore.js +76 -14
  185. package/native-host/src/releaseTransport.js +14 -5
  186. package/native-host/src/responsesPassthroughBridge.js +266 -52
  187. package/native-host/src/runtimeInstaller.js +21 -1
  188. package/native-host/src/selectionScope.js +86 -0
  189. package/native-host/src/skills/parallel-subagents/SKILL.md +31 -3
  190. package/native-host/src/skills/writing-style-builder/SKILL.md +146 -0
  191. package/native-host/src/subagentAdmission.js +26 -0
  192. package/native-host/src/subagentBroker.js +176 -159
  193. package/native-host/src/subagentTelemetry.js +325 -0
  194. package/native-host/src/subagentWorkspace.js +103 -0
  195. package/native-host/src/subagentWorkspacePath.js +34 -0
  196. package/native-host/src/taskRunnerRuntime.js +130 -57
  197. package/native-host/src/tomlStatements.js +140 -0
  198. package/native-host/src/turnAttachments.js +113 -0
  199. package/native-host/src/updateManager.js +406 -38
  200. package/native-host/src/updateNetworkControl.js +75 -0
  201. package/native-host/src/updateTrust.js +21 -2
  202. package/native-host/src/vendor/pdfjs/LICENSE +177 -0
  203. package/native-host/src/vendor/pdfjs/SOURCE.txt +3 -0
  204. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  205. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  206. package/native-host/src/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
  207. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  208. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  209. package/native-host/src/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
  210. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  211. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  212. package/native-host/src/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  213. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  214. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  215. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  216. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  217. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  218. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  219. package/native-host/src/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
  220. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  221. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  222. package/native-host/src/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
  223. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  224. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  225. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  226. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  227. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  228. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  229. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  230. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  231. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  232. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  233. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  234. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  235. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  236. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  237. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  238. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  239. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  240. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  241. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  242. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  243. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  244. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  245. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  246. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  247. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  248. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  249. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  250. package/native-host/src/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
  251. package/native-host/src/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
  252. package/native-host/src/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  253. package/native-host/src/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  254. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  255. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  256. package/native-host/src/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  257. package/native-host/src/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  258. package/native-host/src/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  259. package/native-host/src/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  260. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  261. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  262. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  263. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  264. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  265. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  266. package/native-host/src/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
  267. package/native-host/src/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
  268. package/native-host/src/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
  269. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  270. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  271. package/native-host/src/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
  272. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  273. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  274. package/native-host/src/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
  275. package/native-host/src/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
  276. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  277. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  278. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  279. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  280. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  281. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  282. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  283. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  284. package/native-host/src/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
  285. package/native-host/src/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
  286. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  287. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  288. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  289. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  290. package/native-host/src/vendor/pdfjs/cmaps/H.bcmap +0 -0
  291. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  292. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  293. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  294. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  295. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  296. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  297. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  298. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  299. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  300. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  301. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  302. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  303. package/native-host/src/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
  304. package/native-host/src/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
  305. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  306. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  307. package/native-host/src/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
  308. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  309. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  310. package/native-host/src/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
  311. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  312. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  313. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  314. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  315. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  316. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  317. package/native-host/src/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
  318. package/native-host/src/vendor/pdfjs/cmaps/LICENSE +36 -0
  319. package/native-host/src/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
  320. package/native-host/src/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
  321. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  322. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  323. package/native-host/src/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
  324. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  325. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  326. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  327. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  328. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  329. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  330. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  331. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  332. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  333. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  334. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  335. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  336. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  337. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  338. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  339. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  340. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  341. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  342. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  343. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  344. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  345. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  346. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  347. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  348. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  349. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  350. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  351. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  352. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  353. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  354. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  355. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  356. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  357. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  358. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  359. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  360. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  361. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  362. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  363. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  364. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  365. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  366. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  367. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  368. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  369. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  370. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  371. package/native-host/src/vendor/pdfjs/cmaps/V.bcmap +0 -0
  372. package/native-host/src/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  373. package/native-host/src/vendor/pdfjs/pdf.mjs +26740 -0
  374. package/native-host/src/vendor/pdfjs/pdf.worker.mjs +62201 -0
  375. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  376. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  377. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  378. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  379. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  380. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  381. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  382. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  383. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  384. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  385. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  386. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +102 -0
  387. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  388. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  389. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  390. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  391. package/native-host/src/writingStyleEvidence.js +102 -0
  392. package/native-host/src/writingStylePdfWorker.js +35 -0
  393. package/native-host/src/writingStyleProfile.js +165 -0
  394. package/native-host/src/writingStyleRuntime.js +475 -0
  395. package/native-host/src/writingStyleStore.js +73 -0
  396. package/package.json +1 -1
  397. package/scripts/install-managed.mjs +1 -0
@@ -4,6 +4,7 @@ const crypto = require('node:crypto');
4
4
  const fs = require('node:fs');
5
5
  const path = require('node:path');
6
6
  const {
7
+ BOOTSTRAP_PROTOCOL,
7
8
  compareSemver,
8
9
  isNewerStableVersion,
9
10
  parseSemver,
@@ -12,6 +13,9 @@ const {
12
13
  } = require('./updateTrust');
13
14
  const { extractVerifiedUpdateBundle } = require('./updateArchive');
14
15
  const { requestRelease } = require('./releaseTransport');
16
+ const updateNetwork = require('./updateNetworkControl');
17
+ const { readManagedRuntimeEvidence, sameRuntimeEvidence } = require('./managedRuntimeEvidence');
18
+ const BootstrapUpdate = require('./bootstrapUpdate');
15
19
  const RUNNING_VERSION = require('../../package.json').version;
16
20
 
17
21
  const GITHUB_LATEST_URL = 'https://github.com/Ghqqqq/codex-overleaf-link/releases/latest';
@@ -20,6 +24,7 @@ const EXTENSION_MARKER = '.codex-overleaf-managed-extension.json';
20
24
  const NATIVE_MARKER = '.codex-overleaf-managed-native.json';
21
25
  const JOURNAL_FILE = 'transaction.json';
22
26
  const CANDIDATE_FILE = 'candidate.json';
27
+ const MANUAL_CANDIDATE_FILE = 'manual-candidate.json';
23
28
  const COOLDOWN_FILE = 'cooldowns.json';
24
29
  const AUTHORIZATION_FILE = 'authorization.json';
25
30
  const MUTATION_LOCK_FILE = 'mutation.lock';
@@ -27,9 +32,12 @@ const AUTHORIZATION_TTL_MS = 2 * 60 * 60 * 1000;
27
32
  const MUTATION_LOCK_WAIT_MS = 10 * 1000;
28
33
  const MUTATION_LOCK_STALE_MS = 15 * 60 * 1000;
29
34
  const ACTIVATION_DELAY_MS = 125;
35
+ const APPLYING_TRANSACTION_STATES = new Set(['activation_pending', 'applying', 'awaiting_health',
36
+ ...BootstrapUpdate.BOOTSTRAP_STATES]);
30
37
  const CONSENT_MIGRATION_TARGET_VERSION = '1.9.4';
31
38
  const UPDATE_METHODS = new Set([
32
39
  'update.status',
40
+ 'update.bootstrap',
33
41
  'update.check',
34
42
  'update.authorize',
35
43
  'update.stage',
@@ -38,7 +46,8 @@ const UPDATE_METHODS = new Set([
38
46
  'update.activate',
39
47
  'update.confirm',
40
48
  'update.rollback',
41
- 'update.revoke'
49
+ 'update.revoke',
50
+ 'update.cancel'
42
51
  ]);
43
52
  const LAYOUT_GATED_METHODS = new Set([
44
53
  'update.check',
@@ -64,7 +73,7 @@ async function handleUpdateRequest(request, options = {}) {
64
73
  return errorResponse(request?.id, 'unknown_update_method', 'Unknown managed update method.');
65
74
  }
66
75
  try {
67
- const context = getManagedContext(options);
76
+ const context = { ...getManagedContext(options), operationId: request.params?.operationId || '' };
68
77
  if (!context.managed) {
69
78
  throw updateError('update_not_managed', 'Run install-managed once to enable coordinated automatic updates.');
70
79
  }
@@ -73,13 +82,22 @@ async function handleUpdateRequest(request, options = {}) {
73
82
  }
74
83
  switch (request.method) {
75
84
  case 'update.status':
76
- return okResponse(request.id, await withUpdateMutex(context, () => recoverAndReadStatus(context)));
85
+ return okResponse(request.id, readStatusWithoutWaiting(context, request.params || {}));
86
+ case 'update.bootstrap':
87
+ return okResponse(request.id, await withUpdateMutex(context, () => {
88
+ const gate = getApplyGate(options);
89
+ if (!gate.idle) throw updateError('update_native_busy', 'Native host is still processing a Codex task.');
90
+ return BootstrapUpdate.advanceBootstrapMigration(context, readJournal(context),
91
+ request.params || {}, bootstrapDependencies(context), options.bootstrapMigration || {});
92
+ }));
77
93
  case 'update.check':
78
- return okResponse(request.id, await withUpdateMutex(context, () => checkForUpdate(context, request.params || {}, options)));
94
+ return okResponse(request.id, await runNetworkUpdate(context, request, options,
95
+ networkOptions => checkForUpdate(context, request.params || {}, networkOptions)));
79
96
  case 'update.authorize':
80
97
  return okResponse(request.id, await withUpdateMutex(context, () => authorizeUpdate(context, request.params || {})));
81
98
  case 'update.stage':
82
- return okResponse(request.id, await withUpdateMutex(context, () => stageCandidate(context, options)));
99
+ return okResponse(request.id, await runNetworkUpdate(context, request, options,
100
+ networkOptions => stageCandidate(context, networkOptions)));
83
101
  case 'update.canApply':
84
102
  return okResponse(request.id, getApplyGate(options));
85
103
  case 'update.apply':
@@ -90,18 +108,53 @@ async function handleUpdateRequest(request, options = {}) {
90
108
  return okResponse(request.id, await withUpdateMutex(context, () => confirmUpdate(context, request.params || {})));
91
109
  case 'update.rollback':
92
110
  return okResponse(request.id, await withUpdateMutex(context, () => rollbackUpdate(context, request.params || {})));
93
- case 'update.revoke':
94
- return okResponse(request.id, await withUpdateMutex(context, () => revokeUpdate(context, request.params || {})));
111
+ case 'update.cancel':
112
+ return okResponse(request.id, await cancelNetworkUpdate(context, request.params || {}));
113
+ case 'update.revoke': {
114
+ const authorization = readAuthorization(context);
115
+ const params = request.params || {};
116
+ if (authorization?.id === params.authorizationId && authorization.targetVersion === params.targetVersion) {
117
+ const operationId = params.operationId || updateNetwork.readLock(context)?.operationId;
118
+ if (operationId) await cancelNetworkUpdate(context, { ...params, operationId });
119
+ }
120
+ return okResponse(request.id, await withUpdateMutex({ ...context, operationId: '' },
121
+ () => revokeUpdate(context, params)));
122
+ }
95
123
  default:
96
124
  throw updateError('unknown_update_method', 'Unknown managed update method.');
97
125
  }
98
126
  } catch (error) {
99
- return errorResponse(request.id, safeErrorCode(error), safeErrorMessage(error));
127
+ const code = safeErrorCode(error);
128
+ return errorResponse(request.id, request.method === 'update.bootstrap' && code === 'update_internal_error'
129
+ ? 'update_bootstrap_failed' : code, safeErrorMessage(error));
130
+ }
131
+ }
132
+
133
+ async function runNetworkUpdate(context, request, options, action) {
134
+ const operationId = context.operationId || (isUuid(request.id) ? request.id : crypto.randomUUID());
135
+ const owned = { ...context, operationId };
136
+ return updateNetwork.run(owned, operationId, signal => withUpdateMutex(owned, () => {
137
+ signal.throwIfAborted();
138
+ return action({ ...options, network: { ...options.network, signal } });
139
+ }), options.network?.signal);
140
+ }
141
+
142
+ async function cancelNetworkUpdate(context, params) {
143
+ const journal = readJournal(context);
144
+ if (journal && (APPLYING_TRANSACTION_STATES.has(journal.state) || journal.sourceActivationPending)) {
145
+ throw updateError('update_revoke_too_late', 'The update is already being installed.');
100
146
  }
147
+ return updateNetwork.cancel(context, params.operationId);
101
148
  }
102
149
 
103
150
  function withUpdateMutex(context, action) {
104
- const guardedAction = () => withUpdateFileLock(context, action);
151
+ const guardedAction = () => {
152
+ updateNetwork.assertActive(context, context.operationId);
153
+ return withUpdateFileLock(context, () => {
154
+ updateNetwork.assertActive(context, context.operationId);
155
+ return action();
156
+ });
157
+ };
105
158
  const result = updateMutationTail.then(guardedAction, guardedAction);
106
159
  updateMutationTail = result.catch(() => {});
107
160
  return result;
@@ -127,6 +180,7 @@ async function acquireUpdateFileLock(context) {
127
180
  fs.writeFileSync(descriptor, JSON.stringify({
128
181
  token,
129
182
  pid: process.pid,
183
+ operationId: context.operationId || '',
130
184
  createdAt: Date.now(),
131
185
  expiresAt: Date.now() + MUTATION_LOCK_STALE_MS
132
186
  }) + '\n');
@@ -145,9 +199,16 @@ async function acquireUpdateFileLock(context) {
145
199
 
146
200
  function removeStaleUpdateFileLock(lockPath) {
147
201
  const owner = readJsonSafe(lockPath, null);
148
- const expired = !owner || Number(owner.expiresAt || 0) <= Date.now();
149
- const abandoned = Number.isInteger(Number(owner?.pid)) && !isProcessAlive(Number(owner.pid));
150
- if (!expired && !abandoned) return false;
202
+ const pid = Number(owner?.pid);
203
+ if (Number.isInteger(pid) && pid > 0) {
204
+ // Expiry is not exit evidence. A slow filesystem must not let another
205
+ // process steal a live install/apply lock.
206
+ if (isProcessAlive(pid)) return false;
207
+ } else {
208
+ // The creator may be between exclusive open and writing its owner record.
209
+ try { if (Date.now() - fs.statSync(lockPath).mtimeMs < MUTATION_LOCK_STALE_MS) return false; }
210
+ catch (_error) { return false; }
211
+ }
151
212
  try {
152
213
  fs.rmSync(lockPath, { force: true });
153
214
  return true;
@@ -172,6 +233,46 @@ function isProcessAlive(pid) {
172
233
  }
173
234
  }
174
235
 
236
+ function bootstrapDependencies(context) {
237
+ return {
238
+ runningVersion: RUNNING_VERSION,
239
+ verifyManifest: verifySignedReleaseManifest,
240
+ verifyArchive: verifyStagedArchive,
241
+ writeJournal: value => writeJournal(context, value),
242
+ assertAuthorization(journal) {
243
+ const authorization = requireAuthorization(context, journal.targetVersion, ['bound'], { activeVersion: journal.sourceVersion });
244
+ if (authorization.id !== journal.authorizationId || authorization.transactionId !== journal.id) {
245
+ throw updateError('update_consent_mismatch', 'Bootstrap update does not match the authorized transaction.');
246
+ }
247
+ }
248
+ };
249
+ }
250
+
251
+ async function recoverBootstrapTransaction(options, transactionId, token) {
252
+ const context = getManagedContext(options);
253
+ if (!context.managed) throw updateError('update_not_managed', 'Managed installation markers are unavailable.');
254
+ return withUpdateMutex(context, () => {
255
+ const journal = readJournal(context);
256
+ if (journal?.id !== transactionId || journal.bootstrapMigration?.watchdogToken !== token) return { state: 'obsolete' };
257
+ if (journal.sourceActivationPending && journal.state === 'rolled_back') {
258
+ finishSourceActivation(context, journal);
259
+ return { state: 'rolled_back', version: journal.sourceVersion };
260
+ }
261
+ if (!BootstrapUpdate.bootstrapExpired(journal)) return { state: journal.state };
262
+ return rollbackUpdate(context, { transactionId, expectedState: journal.state, reasonCode: 'update_bootstrap_health_timeout' });
263
+ });
264
+ }
265
+
266
+ function assertBootstrapBusinessAvailable(request, options = {}) {
267
+ if (isUpdateMethod(request?.method) || ['bridge.ping', 'codex.cancel'].includes(request?.method)) return;
268
+ const context = getManagedContext(options);
269
+ if (!context.managed) return;
270
+ const journal = readJournal(context);
271
+ if (BootstrapUpdate.migrationHint(context, journal) || journal?.sourceActivationPending) {
272
+ throw updateError('update_applying', 'The managed update is still restoring or loading its startup files.');
273
+ }
274
+ }
275
+
175
276
  function delay(milliseconds) {
176
277
  return new Promise(resolve => setTimeout(resolve, milliseconds));
177
278
  }
@@ -198,7 +299,8 @@ async function checkForUpdate(context, params = {}, options = {}) {
198
299
  });
199
300
  if (releaseResponse.status === 304) {
200
301
  const candidate = readJsonSafe(path.join(context.updatesRoot, CANDIDATE_FILE), null);
201
- if (candidate?.manifestBase64 && candidate?.signatureBase64) {
302
+ if (candidate?.manifestBase64 && candidate?.signatureBase64
303
+ && params.etag && candidate.etag === params.etag && candidate.currentVersion === currentVersion) {
202
304
  try {
203
305
  const manifest = verifySignedReleaseManifest(
204
306
  Buffer.from(candidate.manifestBase64, 'base64'),
@@ -218,6 +320,8 @@ async function checkForUpdate(context, params = {}, options = {}) {
218
320
  // Invalid cached candidates are treated as cache misses.
219
321
  }
220
322
  }
323
+ const manual = readManualCandidate(context, currentVersion, params.etag);
324
+ if (manual) return { ...manual, etag: params.etag || manual.etag || '', cached: true };
221
325
  return { managed: true, available: false, reason: 'not_modified', currentVersion, etag: params.etag || '' };
222
326
  }
223
327
  if (!releaseResponse.ok) {
@@ -228,6 +332,10 @@ async function checkForUpdate(context, params = {}, options = {}) {
228
332
  throw updateError('update_github_response_invalid', 'GitHub latest release did not resolve to a stable semantic-version tag.');
229
333
  }
230
334
  const latestVersion = releaseTag.slice(1);
335
+ // A successful latest response replaces the previous discovery result,
336
+ // including when a release was withdrawn or is now in cooldown.
337
+ fs.rmSync(path.join(context.updatesRoot, MANUAL_CANDIDATE_FILE), { force: true });
338
+ fs.rmSync(path.join(context.updatesRoot, CANDIDATE_FILE), { force: true });
231
339
  if (!isNewerStableVersion(latestVersion, currentVersion)) {
232
340
  const activeMarker = readJsonSafe(path.join(context.extensionRoot, EXTENSION_MARKER), null);
233
341
  const equalVersionPrerelease = compareSemver(latestVersion, currentVersion) === 0
@@ -245,7 +353,8 @@ async function checkForUpdate(context, params = {}, options = {}) {
245
353
  }
246
354
  const cooldowns = readJsonSafe(path.join(context.updatesRoot, COOLDOWN_FILE), {});
247
355
  if (Number(cooldowns[latestVersion]?.until || 0) > Date.now()) {
248
- return { managed: true, available: false, reason: 'cooldown', currentVersion, latestVersion, etag: releaseResponse.headers.get('etag') || '' };
356
+ return { managed: true, available: false, reason: 'cooldown', currentVersion, latestVersion,
357
+ cooldownUntil: Number(cooldowns[latestVersion].until), etag: releaseResponse.headers.get('etag') || '' };
249
358
  }
250
359
 
251
360
  const manifestBytes = await fetchReleaseAsset(
@@ -258,10 +367,32 @@ async function checkForUpdate(context, params = {}, options = {}) {
258
367
  buildReleaseAssetUrl(releaseTag, 'release-manifest.sig'),
259
368
  16 * 1024, { ...network, stage: 'release_signature' }
260
369
  );
261
- const manifest = verifySignedReleaseManifest(manifestBytes, signatureBytes);
370
+ const manifest = verifySignedReleaseManifest(manifestBytes, signatureBytes, { allowBootstrapMismatch: true });
262
371
  if (manifest.version !== latestVersion || manifest.tag !== releaseTag) {
263
372
  throw updateError('update_release_manifest_mismatch', 'GitHub release tag does not match the signed update manifest.');
264
373
  }
374
+ if (manifest.bootstrapProtocol !== BOOTSTRAP_PROTOCOL) {
375
+ // Never staged: only a reinstall can replace the Bootstrap shell. The user
376
+ // gets the target version and the signed reason instead of a bare error.
377
+ const manual = {
378
+ managed: true,
379
+ available: false,
380
+ reason: 'manual_install_required',
381
+ currentVersion,
382
+ latestVersion,
383
+ installedBootstrapProtocol: BOOTSTRAP_PROTOCOL,
384
+ requiredBootstrapProtocol: manifest.bootstrapProtocol,
385
+ manualInstall: manifest.manualInstall?.reason ? { reason: manifest.manualInstall.reason } : {},
386
+ etag: releaseResponse.headers.get('etag') || ''
387
+ };
388
+ atomicWriteJson(path.join(context.updatesRoot, MANUAL_CANDIDATE_FILE), {
389
+ ...manual,
390
+ checkedAt: new Date().toISOString(),
391
+ manifestBase64: manifestBytes.toString('base64'),
392
+ signatureBase64: signatureBytes.toString('base64')
393
+ });
394
+ return manual;
395
+ }
265
396
  const candidate = {
266
397
  checkedAt: new Date().toISOString(),
267
398
  currentVersion,
@@ -275,6 +406,31 @@ async function checkForUpdate(context, params = {}, options = {}) {
275
406
  return { managed: true, available: true, currentVersion, latestVersion, etag: candidate.etag };
276
407
  }
277
408
 
409
+ function readManualCandidate(context, currentVersion, etag) {
410
+ const manual = readJsonSafe(path.join(context.updatesRoot, MANUAL_CANDIDATE_FILE), null);
411
+ if (!etag || manual?.etag !== etag || manual.currentVersion !== currentVersion
412
+ || !manual.manifestBase64 || !manual.signatureBase64) return null;
413
+ try {
414
+ const manifest = verifySignedReleaseManifest(
415
+ Buffer.from(manual.manifestBase64, 'base64'),
416
+ Buffer.from(manual.signatureBase64, 'base64'),
417
+ { allowBootstrapMismatch: true }
418
+ );
419
+ if (!isNewerStableVersion(manifest.version, currentVersion)
420
+ || manifest.bootstrapProtocol === BOOTSTRAP_PROTOCOL) return null;
421
+ return {
422
+ managed: true, available: false, reason: 'manual_install_required',
423
+ currentVersion, latestVersion: manifest.version,
424
+ installedBootstrapProtocol: BOOTSTRAP_PROTOCOL,
425
+ requiredBootstrapProtocol: manifest.bootstrapProtocol,
426
+ manualInstall: manifest.manualInstall?.reason ? { reason: manifest.manualInstall.reason } : {},
427
+ etag
428
+ };
429
+ } catch (_error) {
430
+ return null;
431
+ }
432
+ }
433
+
278
434
  function readAuthorizedCandidate(context, currentVersion) {
279
435
  const authorization = readAuthorization(context);
280
436
  if (!authorization || !['authorized', 'bound'].includes(authorization.state)) return null;
@@ -313,6 +469,10 @@ function buildReleaseAssetUrl(tag, assetName) {
313
469
  }
314
470
 
315
471
  function authorizeUpdate(context, params = {}) {
472
+ const activeJournal = readJournal(context);
473
+ if (BootstrapUpdate.migrationHint(context, activeJournal) || activeJournal?.sourceActivationPending) {
474
+ throw updateError('update_transaction_in_progress', 'The current Bootstrap update must finish before authorizing another update.');
475
+ }
316
476
  const authorizationId = String(params.authorizationId || '');
317
477
  const targetVersion = String(params.targetVersion || '');
318
478
  const requestedCurrentVersion = String(params.currentVersion || '');
@@ -360,6 +520,10 @@ function authorizeUpdate(context, params = {}) {
360
520
  }
361
521
 
362
522
  async function stageCandidate(context, options = {}) {
523
+ const activeJournal = readJournal(context);
524
+ if (BootstrapUpdate.migrationHint(context, activeJournal) || activeJournal?.sourceActivationPending) {
525
+ throw updateError('update_transaction_in_progress', 'The current Bootstrap update must finish before staging another update.');
526
+ }
363
527
  const candidate = readJsonSafe(path.join(context.updatesRoot, CANDIDATE_FILE), null);
364
528
  if (!candidate) {
365
529
  throw updateError('update_candidate_missing', 'Check for an update before staging it.');
@@ -399,7 +563,7 @@ async function stageCandidate(context, options = {}) {
399
563
  );
400
564
  }
401
565
  }
402
- if (existingJournal && ['activation_pending', 'applying', 'awaiting_health'].includes(existingJournal.state)) {
566
+ if (existingJournal && APPLYING_TRANSACTION_STATES.has(existingJournal.state)) {
403
567
  throw updateError('update_transaction_in_progress', 'A managed update transaction is already being applied.');
404
568
  }
405
569
  if (authorization.state === 'bound') {
@@ -411,7 +575,13 @@ async function stageCandidate(context, options = {}) {
411
575
  try {
412
576
  const archivePath = path.join(stageRoot, manifest.updateBundle.name);
413
577
  const bundleBytes = await fetchReleaseAsset(options.fetch, candidate.bundleUrl, manifest.updateBundle.size + 1,
414
- { ...options.network, totalTimeoutMs: 90000, timeoutMs: 30000, stage: 'update_bundle' });
578
+ { ...options.network, totalTimeoutMs: 300000, timeoutMs: 30000,
579
+ idleTimeoutMs: 30000, stage: 'update_bundle' });
580
+ options.network?.signal?.throwIfAborted();
581
+ const currentAuthorization = requireAuthorization(context, manifest.version, ['authorized', 'bound']);
582
+ if (currentAuthorization.id !== authorization.id) {
583
+ throw updateError('update_consent_mismatch', 'The update authorization changed while downloading.');
584
+ }
415
585
  if (bundleBytes.length !== manifest.updateBundle.size) {
416
586
  throw updateError('update_bundle_size_mismatch', 'Downloaded update bundle size does not match the signed manifest.');
417
587
  }
@@ -459,6 +629,7 @@ function getApplyGate(options = {}) {
459
629
  const blockers = [];
460
630
  if (Number(state.projectLocks || 0) > 0) blockers.push('native_project_locked');
461
631
  if (Number(state.runControllers || 0) > 0) blockers.push('native_run_active');
632
+ if (Number(state.processStopBarriers || 0) > 0) blockers.push('native_process_stop_unconfirmed');
462
633
  return { idle: blockers.length === 0, blockers, workState: state };
463
634
  }
464
635
 
@@ -503,7 +674,8 @@ function scheduleStagedActivation(context, params = {}, options = {}) {
503
674
 
504
675
  function recordScheduledActivationFailure(context, pending, error) {
505
676
  const current = readJournal(context);
506
- if (!current || current.id !== pending.id || ['awaiting_health', 'rolled_back'].includes(current.state)) return;
677
+ if (!current || current.id !== pending.id || ['awaiting_health', 'rolled_back', 'committed',
678
+ ...BootstrapUpdate.BOOTSTRAP_STATES].includes(current.state)) return;
507
679
  writeJournal(context, {
508
680
  ...current,
509
681
  state: 'rolled_back',
@@ -613,6 +785,7 @@ function confirmUpdate(context, params = {}) {
613
785
  if (params.transactionId !== journal.id || params.extensionVersion !== journal.targetVersion || params.nativeVersion !== journal.targetVersion) {
614
786
  throw updateError('update_health_version_mismatch', 'Extension and native host did not confirm the same target version.');
615
787
  }
788
+ BootstrapUpdate.assertBootstrapConfirmation(context, journal, params);
616
789
  if (!isLegacyInboundConsentMigration(journal)) {
617
790
  const authorization = requireAuthorization(context, journal.targetVersion, ['bound'], {
618
791
  activeVersion: journal.sourceVersion
@@ -625,9 +798,15 @@ function confirmUpdate(context, params = {}) {
625
798
  if (readVersionPointer(context.nativeRoot, 'active-version') !== journal.targetVersion || manifest?.version !== journal.targetVersion) {
626
799
  throw updateError('update_health_version_mismatch', 'Managed files do not match the confirmed target version.');
627
800
  }
801
+ let committedEvidence;
802
+ try { committedEvidence = readManagedRuntimeEvidence(context, journal.targetVersion); }
803
+ catch (cause) {
804
+ throw updateError('update_health_failed', 'The installed runtime files did not pass the update health check.', { cause });
805
+ }
628
806
  writeJournal(context, {
629
807
  ...journal,
630
808
  state: 'committed',
809
+ committedEvidence,
631
810
  confirmedAt: new Date().toISOString(),
632
811
  updatedAt: new Date().toISOString()
633
812
  });
@@ -639,29 +818,53 @@ function confirmUpdate(context, params = {}) {
639
818
  }
640
819
 
641
820
  function rollbackUpdate(context, params = {}) {
642
- const journal = readJournal(context);
643
- if (!journal || !['applying', 'awaiting_health', 'committed'].includes(journal.state)) {
821
+ let journal = readJournal(context);
822
+ if (params.transactionId && journal?.id !== params.transactionId) {
823
+ throw updateError('update_transaction_mismatch', 'Rollback transaction identity changed.');
824
+ }
825
+ if (params.expectedState && journal?.state !== params.expectedState) {
826
+ throw updateError('update_transaction_state_changed', 'The update state changed before rollback.');
827
+ }
828
+ journal = BootstrapUpdate.bindBootstrapMigration(context, journal, bootstrapDependencies(context));
829
+ if (!journal || !['applying', 'awaiting_health', 'committed', 'bootstrap_pending', 'bootstrap_rolling_back'].includes(journal.state)) {
644
830
  throw updateError('update_rollback_unavailable', 'No applied update is available for rollback.');
645
831
  }
646
- rollbackFiles(context, journal);
832
+ if (journal.bootstrapMigration && !params.transactionId && journal.bootstrapMigration.phase !== 'pending') {
833
+ throw updateError('update_transaction_mismatch', 'Bootstrap rollback must identify its update transaction.');
834
+ }
835
+ const migration = Boolean(journal.bootstrapMigration);
836
+ if (migration) writeJournal(context, { ...journal, state: 'bootstrap_rolling_back' });
837
+ const rollbackEvidence = rollbackFiles(context, journal, { deferSourceActivation: migration });
647
838
  const cooldowns = readJsonSafe(path.join(context.updatesRoot, COOLDOWN_FILE), {});
648
839
  cooldowns[journal.targetVersion] = {
649
840
  until: Date.now() + 24 * 60 * 60 * 1000,
650
841
  reasonCode: normalizeReasonCode(params.reasonCode)
651
842
  };
652
843
  atomicWriteJson(path.join(context.updatesRoot, COOLDOWN_FILE), cooldowns);
653
- writeJournal(context, {
844
+ const restoredJournal = {
654
845
  ...journal,
655
846
  state: 'rolled_back',
656
847
  reasonCode: normalizeReasonCode(params.reasonCode),
657
848
  rolledBackAt: new Date().toISOString(),
658
849
  updatedAt: new Date().toISOString(),
659
- previousManifest: undefined
660
- });
850
+ previousManifest: undefined,
851
+ rollbackEvidence,
852
+ ...(migration ? { bootstrapMigration: { ...journal.bootstrapMigration, phase: 'restored',
853
+ sourceUnchanged: rollbackEvidence.bootstrapSourceUnchanged === true },
854
+ sourceActivationPending: true } : {})
855
+ };
856
+ writeJournal(context, restoredJournal);
857
+ // The old launcher must not select a source process that cannot understand
858
+ // our recovery record until every file and the durable terminal record exist.
859
+ if (migration) {
860
+ settleAuthorization(context, journal.authorizationId, 'revoked');
861
+ atomicWriteText(path.join(context.nativeRoot, 'active-version'), journal.sourceVersion + '\n');
862
+ writeJournal(context, { ...restoredJournal, sourceActivationPending: false });
863
+ }
661
864
  settleAuthorization(context, journal.authorizationId, 'revoked');
662
865
  cleanupStage(journal.stageRoot);
663
866
  cleanupOrphanStageRoots(context.updatesRoot);
664
- return { version: journal.sourceVersion, state: 'rolled_back' };
867
+ return { transactionId: journal.id, version: journal.sourceVersion, state: 'rolled_back', restoredPair: rollbackEvidence };
665
868
  }
666
869
 
667
870
  function revokeUpdate(context, params = {}) {
@@ -673,7 +876,7 @@ function revokeUpdate(context, params = {}) {
673
876
  }
674
877
  const journal = readJournal(context);
675
878
  if (journal?.targetVersion === targetVersion &&
676
- ['activation_pending', 'applying', 'awaiting_health', 'committed'].includes(journal.state)) {
879
+ (APPLYING_TRANSACTION_STATES.has(journal.state) || journal.state === 'committed' || journal.sourceActivationPending)) {
677
880
  throw updateError('update_revoke_too_late', 'The update is already being applied.');
678
881
  }
679
882
  const authorization = readAuthorization(context);
@@ -702,9 +905,15 @@ function revokeUpdate(context, params = {}) {
702
905
  return { state: 'revoked', targetVersion };
703
906
  }
704
907
 
705
- function rollbackFiles(context, journal) {
908
+ function rollbackFiles(context, journal, options = {}) {
706
909
  const runtime = path.join(context.extensionRoot, 'runtime');
707
910
  const previousRuntime = path.join(context.extensionRoot, 'slots', 'previous', 'runtime');
911
+ // An early interrupted apply may still have the source in the current slot.
912
+ // A missing previous slot is recoverable only when these actual bytes match
913
+ // the source pair; the source manifest and markers alone are insufficient.
914
+ const source = readManagedRuntimeEvidence(context, journal.sourceVersion,
915
+ fs.existsSync(previousRuntime) ? previousRuntime : runtime);
916
+ if (journal.bootstrapMigration) BootstrapUpdate.restoreBootstrap(context, journal);
708
917
  if (fs.existsSync(previousRuntime)) {
709
918
  fs.rmSync(runtime, { recursive: true, force: true });
710
919
  fs.renameSync(previousRuntime, runtime);
@@ -713,7 +922,7 @@ function rollbackFiles(context, journal) {
713
922
  if (journal.previousManifest) {
714
923
  atomicWriteText(path.join(context.extensionRoot, 'manifest.json'), journal.previousManifest);
715
924
  }
716
- if (parseSemver(journal.sourceVersion)) {
925
+ if (parseSemver(journal.sourceVersion) && !options.deferSourceActivation) {
717
926
  atomicWriteText(path.join(context.nativeRoot, 'active-version'), journal.sourceVersion + '\n');
718
927
  }
719
928
  const sourcePrevious = parseSemver(journal.sourcePreviousVersion) ? journal.sourcePreviousVersion : '';
@@ -725,6 +934,18 @@ function rollbackFiles(context, journal) {
725
934
  if (parseSemver(journal.sourceVersion)) {
726
935
  syncManagedMarkerVersions(context, journal.sourceVersion);
727
936
  }
937
+ const restored = readManagedRuntimeEvidence(context, journal.sourceVersion);
938
+ if (!sameRuntimeEvidence(source, restored)) {
939
+ throw updateError('update_rollback_unconfirmed', 'The source runtime readback did not match the restored files.');
940
+ }
941
+ if (journal.bootstrapMigration) {
942
+ const bootstrap = BootstrapUpdate.bootstrapEvidence(context, journal, 'source');
943
+ if (bootstrap) {
944
+ restored.bootstrapSha256 = bootstrap.digest;
945
+ if (bootstrap.unmodified) restored.bootstrapSourceUnchanged = true;
946
+ }
947
+ }
948
+ return restored;
728
949
  }
729
950
 
730
951
  function syncManagedMarkerVersions(context, version) {
@@ -763,8 +984,11 @@ function cleanupOrphanStageRoots(updatesRoot, retainedRoots = []) {
763
984
  }
764
985
  }
765
986
 
766
- function recoverAndReadStatus(context) {
987
+ function recoverAndReadStatus(context, params = {}) {
767
988
  let journal = readJournal(context);
989
+ if (journal?.sourceActivationPending && journal.state === 'rolled_back') {
990
+ journal = finishSourceActivation(context, journal);
991
+ }
768
992
  const alignedVersion = readAlignedManagedVersion(context);
769
993
  journal = supersedeObsoleteJournal(context, journal, alignedVersion);
770
994
  if (journal?.state === 'activation_pending') {
@@ -772,28 +996,145 @@ function recoverAndReadStatus(context) {
772
996
  writeJournal(context, journal);
773
997
  }
774
998
  if (journal?.state === 'applying') {
775
- rollbackFiles(context, journal);
776
- journal = { ...journal, state: 'rolled_back', reasonCode: 'update_interrupted', updatedAt: new Date().toISOString() };
999
+ const rollbackEvidence = rollbackFiles(context, journal);
1000
+ journal = { ...journal, rollbackEvidence, state: 'rolled_back', reasonCode: 'update_interrupted', updatedAt: new Date().toISOString() };
777
1001
  writeJournal(context, journal);
778
1002
  settleAuthorization(context, journal.authorizationId, 'revoked');
779
1003
  }
1004
+ journal = BootstrapUpdate.bindBootstrapMigration(context, journal, bootstrapDependencies(context));
1005
+ if (BootstrapUpdate.bootstrapExpired(journal, { recoveryRequested: params.bootstrapRecovery === true, context })) {
1006
+ rollbackUpdate(context, { transactionId: journal.id, expectedState: journal.state, reasonCode: 'update_bootstrap_health_timeout' });
1007
+ journal = readJournal(context);
1008
+ }
780
1009
  if (journal?.state === 'rolled_back' || journal?.state === 'superseded') {
1010
+ if (journal.state === 'superseded') cleanupStage(journal.stageRoot);
781
1011
  pruneNativeVersions(context.nativeRoot, new Set([
782
1012
  readVersionPointer(context.nativeRoot, 'active-version'),
783
1013
  readVersionPointer(context.nativeRoot, 'previous-version')
784
1014
  ]));
785
1015
  }
1016
+ if (journal?.state === 'committed' && journal.committedEvidence && alignedVersion === journal.targetVersion) {
1017
+ const observed = readManagedRuntimeEvidence(context, journal.targetVersion);
1018
+ if (sameRuntimeEvidence(journal.committedEvidence, observed)) {
1019
+ if (journal.bootstrapMigration) BootstrapUpdate.assertBootstrapConfirmation(context, journal,
1020
+ { bootstrapRevision: journal.bootstrapMigration.targetRevision });
1021
+ const authorization = readAuthorization(context);
1022
+ if (['bound', 'consumed'].includes(authorization?.state) && authorization.id === journal.authorizationId
1023
+ && authorization.transactionId === journal.id && authorization.sourceVersion === journal.sourceVersion
1024
+ && authorization.targetVersion === journal.targetVersion) {
1025
+ settleAuthorization(context, journal.authorizationId, 'consumed');
1026
+ }
1027
+ cleanupStage(journal.stageRoot);
1028
+ }
1029
+ }
1030
+ return currentStatus(context, journal);
1031
+ }
1032
+
1033
+ function finishSourceActivation(context, journal) {
1034
+ const actual = readManagedRuntimeEvidence(context, journal.sourceVersion);
1035
+ if (!sameRuntimeEvidence(actual, journal.rollbackEvidence)) {
1036
+ throw updateError('update_rollback_unconfirmed', 'The recovered source files changed before activation.');
1037
+ }
1038
+ BootstrapUpdate.bootstrapEvidence(context, journal, 'source');
1039
+ settleAuthorization(context, journal.authorizationId, 'revoked');
1040
+ atomicWriteText(path.join(context.nativeRoot, 'active-version'), journal.sourceVersion + '\n');
1041
+ const finished = { ...journal, sourceActivationPending: false };
1042
+ writeJournal(context, finished);
1043
+ cleanupStage(journal.stageRoot);
1044
+ return finished;
1045
+ }
1046
+
1047
+ // Called by the full installer while holding the same mutation lock, only
1048
+ // after both runtime trees and browser registration have succeeded.
1049
+ function settleReinstalledUpdate(context, version, expected) {
1050
+ if (!expected?.id || !parseSemver(expected.sourceVersion) || !parseSemver(expected.targetVersion)
1051
+ || compareSemver(version, expected.targetVersion) < 0
1052
+ || compareSemver(version, expected.sourceVersion) <= 0) return;
1053
+ const journal = readJournal(context);
1054
+ if (!journal || ['id', 'state', 'sourceVersion', 'targetVersion'].some(key => journal[key] !== expected[key])) {
1055
+ throw updateError('update_transaction_mismatch', 'The update transaction changed during full installation.');
1056
+ }
1057
+ if (assertManagedLayout(context) !== version) {
1058
+ throw updateError('update_managed_layout_invalid', 'Full installation did not produce the requested pair.');
1059
+ }
1060
+ const reinstalledPair = readManagedRuntimeEvidence(context, version);
1061
+ const timestamp = new Date().toISOString();
1062
+ writeJournal(context, {
1063
+ ...journal, state: 'superseded', supersededFromState: journal.state,
1064
+ supersededByVersion: version, supersededAt: timestamp,
1065
+ updatedAt: timestamp, reasonCode: 'update_reinstalled', reinstalledPair
1066
+ });
1067
+ const authorization = readAuthorization(context);
1068
+ if (authorization?.state === 'bound' && authorization.id === journal.authorizationId
1069
+ && authorization.transactionId === journal.id && authorization.sourceVersion === journal.sourceVersion
1070
+ && authorization.targetVersion === journal.targetVersion) {
1071
+ settleAuthorization(context, authorization.id, 'revoked');
1072
+ }
1073
+ }
1074
+
1075
+ function currentStatus(context, journal = readJournal(context)) {
1076
+ let restoredPair = null;
1077
+ let reinstalledPair = null;
1078
+ if (journal?.state === 'rolled_back') {
1079
+ try {
1080
+ const observed = readManagedRuntimeEvidence(context, journal.sourceVersion);
1081
+ if (!journal.rollbackEvidence || sameRuntimeEvidence(journal.rollbackEvidence, observed)) {
1082
+ if (journal.bootstrapMigration) {
1083
+ const bootstrap = BootstrapUpdate.bootstrapEvidence(context, journal, 'source');
1084
+ if (bootstrap) observed.bootstrapSha256 = bootstrap.digest;
1085
+ if (journal.rollbackEvidence?.bootstrapSha256 !== observed.bootstrapSha256) throw new Error('Bootstrap restoration changed.');
1086
+ }
1087
+ restoredPair = observed;
1088
+ }
1089
+ } catch (_) { /* Missing or changed bytes cannot confirm a recovered pair. */ }
1090
+ }
1091
+ if (journal?.state === 'superseded' && journal.reasonCode === 'update_reinstalled' && journal.reinstalledPair) {
1092
+ try {
1093
+ const observed = readManagedRuntimeEvidence(context, journal.supersededByVersion);
1094
+ if (sameRuntimeEvidence(journal.reinstalledPair, observed)) reinstalledPair = observed;
1095
+ } catch (_) { /* A reinstall receipt cannot authenticate later changed files. */ }
1096
+ }
786
1097
  return {
787
1098
  managed: true,
788
1099
  activeVersion: readVersionPointer(context.nativeRoot, 'active-version'),
789
1100
  runtimeVersion: context.env?.CODEX_OVERLEAF_ACTIVE_VERSION || RUNNING_VERSION,
790
- installedAligned: Boolean(alignedVersion),
1101
+ installedAligned: Boolean(readAlignedManagedVersion(context)),
791
1102
  previousVersion: readVersionPointer(context.nativeRoot, 'previous-version'),
792
- transaction: journal ? publicTransaction(journal) : null,
793
- authorization: publicAuthorization(readAuthorization(context))
1103
+ transaction: journal ? { ...publicTransaction(journal),
1104
+ ...(journal.state === 'awaiting_health' && BootstrapUpdate.migrationHint(context, journal)
1105
+ && journal.bootstrapMigration?.phase !== 'loaded' ? { state: 'bootstrap_pending' } : {}) } : null,
1106
+ authorization: publicAuthorization(readAuthorization(context)),
1107
+ restoredPair,
1108
+ reinstalledPair
794
1109
  };
795
1110
  }
796
1111
 
1112
+ function readStatusWithoutWaiting(context, params = {}) {
1113
+ const lockPath = path.join(context.updatesRoot, MUTATION_LOCK_FILE);
1114
+ const token = crypto.randomUUID();
1115
+ let descriptor;
1116
+ try {
1117
+ descriptor = fs.openSync(lockPath, 'wx', 0o600);
1118
+ } catch (error) {
1119
+ if (error.code !== 'EEXIST') throw error;
1120
+ const owner = readJsonSafe(lockPath, null);
1121
+ if (Number.isInteger(owner?.pid) && !isProcessAlive(owner.pid) && removeStaleUpdateFileLock(lockPath)) {
1122
+ return readStatusWithoutWaiting(context, params);
1123
+ }
1124
+ // A live mutation owns recovery; report an atomic-file snapshot without
1125
+ // queueing behind its network transfer or changing its journal.
1126
+ return currentStatus(context);
1127
+ }
1128
+ try {
1129
+ fs.writeFileSync(descriptor, JSON.stringify({ token, pid: process.pid,
1130
+ createdAt: Date.now(), expiresAt: Date.now() + MUTATION_LOCK_STALE_MS }));
1131
+ return recoverAndReadStatus(context, params);
1132
+ } finally {
1133
+ fs.closeSync(descriptor);
1134
+ releaseUpdateFileLock({ lockPath, token });
1135
+ }
1136
+ }
1137
+
797
1138
  function readAlignedManagedVersion(context) {
798
1139
  try {
799
1140
  return assertManagedLayout(context);
@@ -803,12 +1144,21 @@ function readAlignedManagedVersion(context) {
803
1144
  }
804
1145
 
805
1146
  function supersedeObsoleteJournal(context, journal, activeVersion) {
806
- if (!journal || journal.state === 'superseded' || !parseSemver(activeVersion) ||
1147
+ if (!journal || !parseSemver(activeVersion) ||
807
1148
  !parseSemver(journal.sourceVersion) || !parseSemver(journal.targetVersion) ||
808
1149
  compareSemver(activeVersion, journal.sourceVersion) <= 0 ||
809
1150
  compareSemver(activeVersion, journal.targetVersion) <= 0) {
810
1151
  return journal;
811
1152
  }
1153
+ // A previous runtime may already have retired the journal without settling
1154
+ // its bound authorization. Never consume an authorization owned elsewhere.
1155
+ const authorization = readAuthorization(context);
1156
+ if (authorization?.state === 'bound' && journal.authorizationId
1157
+ && authorization.id === journal.authorizationId && authorization.transactionId === journal.id
1158
+ && authorization.sourceVersion === journal.sourceVersion && authorization.targetVersion === journal.targetVersion) {
1159
+ settleAuthorization(context, authorization.id, 'revoked');
1160
+ }
1161
+ if (journal.state === 'superseded') return journal;
812
1162
  const timestamp = new Date().toISOString();
813
1163
  const superseded = {
814
1164
  ...journal,
@@ -886,7 +1236,7 @@ function assertManagedLayout(context) {
886
1236
  }
887
1237
 
888
1238
  function isManagedMarker(marker, kind) {
889
- return marker?.managedBy === 'codex-overleaf-link' && marker?.kind === kind && marker?.bootstrapProtocol === 2;
1239
+ return marker?.managedBy === 'codex-overleaf-link' && marker?.kind === kind && marker?.bootstrapProtocol === BOOTSTRAP_PROTOCOL;
890
1240
  }
891
1241
 
892
1242
  function verifyStagedPair(payloadRoot, targetVersion) {
@@ -988,6 +1338,7 @@ function publicTransaction(journal) {
988
1338
  state: journal.state,
989
1339
  sourceVersion: journal.sourceVersion,
990
1340
  targetVersion: journal.targetVersion,
1341
+ ...(journal.bootstrapMigration ? { bootstrapRevision: journal.bootstrapMigration.targetRevision } : {}),
991
1342
  createdAt: journal.createdAt,
992
1343
  activationRequestedAt: journal.activationRequestedAt,
993
1344
  appliedAt: journal.appliedAt,
@@ -1079,10 +1430,22 @@ function atomicWriteJson(target, value) {
1079
1430
  }
1080
1431
 
1081
1432
  function atomicWriteText(target, value) {
1082
- const temp = target + '.tmp-' + process.pid + '-' + Date.now();
1433
+ const temp = target + '.tmp-' + process.pid + '-' + crypto.randomBytes(6).toString('hex');
1083
1434
  fs.mkdirSync(path.dirname(target), { recursive: true });
1084
- fs.writeFileSync(temp, value, { mode: 0o600 });
1435
+ let descriptor = fs.openSync(temp, 'wx', 0o600);
1436
+ try {
1437
+ // Windows requires a writable handle for FlushFileBuffers (Node fsync).
1438
+ fs.writeFileSync(descriptor, value);
1439
+ fs.fsyncSync(descriptor);
1440
+ } finally { if (descriptor !== undefined) fs.closeSync(descriptor); }
1085
1441
  fs.renameSync(temp, target);
1442
+ descriptor = undefined;
1443
+ try {
1444
+ descriptor = fs.openSync(path.dirname(target), 'r');
1445
+ fs.fsyncSync(descriptor);
1446
+ } catch (error) {
1447
+ if (process.platform !== 'win32' || !['EPERM', 'EISDIR', 'EINVAL', 'EBADF', 'EACCES'].includes(error.code)) throw error;
1448
+ } finally { if (descriptor !== undefined) fs.closeSync(descriptor); }
1086
1449
  }
1087
1450
 
1088
1451
  function readJsonSafe(target, fallback) {
@@ -1136,8 +1499,10 @@ function errorResponse(id, code, message) {
1136
1499
 
1137
1500
  module.exports = {
1138
1501
  GITHUB_LATEST_URL,
1502
+ MUTATION_LOCK_STALE_MS,
1139
1503
  applyStagedUpdate,
1140
1504
  assertManagedLayout,
1505
+ assertBootstrapBusinessAvailable,
1141
1506
  authorizeUpdate,
1142
1507
  checkForUpdate,
1143
1508
  cleanupOrphanStageRoots,
@@ -1147,8 +1512,11 @@ module.exports = {
1147
1512
  handleUpdateRequest,
1148
1513
  isUpdateMethod,
1149
1514
  revokeUpdate,
1515
+ removeStaleUpdateFileLock,
1516
+ recoverBootstrapTransaction,
1150
1517
  rollbackUpdate,
1151
1518
  scheduleStagedActivation,
1519
+ settleReinstalledUpdate,
1152
1520
  stageCandidate,
1153
1521
  syncManagedMarkerVersions
1154
1522
  };