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.
- package/README.md +275 -305
- package/README.zh-CN.md +290 -320
- package/extension/bootstrap/background.js +99 -19
- package/extension/bootstrap/manifest.template.json +9 -4
- package/extension/bootstrap/popup.html +11 -16
- package/extension/bootstrap/popupFallback.js +55 -0
- package/extension/bootstrap/update.js +25 -2
- package/extension/bootstrap/updateStatus.js +1 -0
- package/extension/runtime-manifest.json +3 -1
- package/extension/src/background.js +234 -56
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +440 -0
- package/extension/src/backgroundUpdateCoordinator.js +541 -381
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +16 -2
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +244 -12
- package/extension/src/content/changeHistoryView.js +175 -0
- package/extension/src/content/composerAttachments.js +117 -67
- package/extension/src/content/composerPanel.js +41 -8
- package/extension/src/content/contentRuntime.js +1084 -1099
- package/extension/src/content/diagnosticsController.js +122 -97
- package/extension/src/content/diagnosticsExport.js +236 -0
- package/extension/src/content/diagnosticsPanel.js +78 -19
- package/extension/src/content/diffReviewPanel.js +8 -5
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +14680 -3606
- package/extension/src/content/generated/content.bundle.meta.json +30 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +25 -1
- package/extension/src/content/localSkillsPanel.js +141 -16
- package/extension/src/content/markdownDomRenderer.js +9 -4
- package/extension/src/content/markdownText.js +5 -6
- package/extension/src/content/modelPicker.js +8 -5
- package/extension/src/content/nativeCompatibilityController.js +45 -20
- package/extension/src/content/otWarmMirror.js +4 -5
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +33 -5
- package/extension/src/content/panelMaintenance.js +87 -132
- package/extension/src/content/panelRenderer.js +10 -0
- package/extension/src/content/pendingInputView.js +109 -76
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +39 -2
- package/extension/src/content/providerSettingsCoordinator.js +63 -14
- package/extension/src/content/providerSettingsDialog.js +724 -113
- package/extension/src/content/recentProjects.js +21 -6
- package/extension/src/content/runActivitySummary.js +665 -0
- package/extension/src/content/runCompletionMeta.js +344 -0
- package/extension/src/content/runController.js +40 -15
- package/extension/src/content/runFailureNotice.js +34 -12
- package/extension/src/content/runGuidanceController.js +76 -30
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runPresence.js +306 -0
- package/extension/src/content/runQueueScheduler.js +150 -43
- package/extension/src/content/runRecoveryActions.js +146 -0
- package/extension/src/content/runResultActions.js +11 -8
- package/extension/src/content/runReviewController.js +574 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +315 -290
- package/extension/src/content/scopedPersistencePanelState.js +51 -6
- package/extension/src/content/selectionContextView.js +300 -0
- package/extension/src/content/sensitivePreflight.js +140 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +1 -1
- package/extension/src/content/sessionPersistence.js +6 -1
- package/extension/src/content/settingsPanel.js +84 -55
- package/extension/src/content/settingsWorkbench.js +379 -0
- package/extension/src/content/sharedSessionViewSync.js +109 -0
- package/extension/src/content/subagentActivityView.js +195 -0
- package/extension/src/content/trackedChangeCaptureController.js +7 -3
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +179 -56
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +308 -2
- package/extension/src/content/writebackOrchestrator.js +213 -199
- package/extension/src/content/writebackPlan.js +231 -0
- package/extension/src/content/writebackRecovery.js +122 -0
- package/extension/src/content/writingStyleSettings.js +600 -0
- package/extension/src/content/writtenChangesView.js +152 -0
- package/extension/src/page/binaryAssetUploader.js +223 -23
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/overleafProjectSnapshot.js +1 -0
- package/extension/src/page/ownedDirectoryCleanup.js +94 -0
- package/extension/src/page/referenceProjects.js +149 -0
- package/extension/src/page/reviewContext.js +113 -0
- package/extension/src/page/saveState.js +20 -6
- package/extension/src/page/snapshotRouter.js +124 -48
- package/extension/src/page/textCoordinates.js +102 -0
- package/extension/src/page/textFileCreator.js +442 -71
- package/extension/src/page/trackedChangeCapture.js +53 -57
- package/extension/src/page/trackedChangeOwnership.js +216 -0
- package/extension/src/page/trackedChangeReplay.js +184 -0
- package/extension/src/page/trackedChangesLifecycle.js +343 -339
- package/extension/src/page/treeOperations.js +33 -12
- package/extension/src/page/updateSafetyProbe.js +41 -0
- package/extension/src/page/writebackReceiptJournal.js +114 -0
- package/extension/src/page/writebackRouter.js +242 -201
- package/extension/src/pageBridge.js +73 -102
- package/extension/src/popup.js +261 -65
- package/extension/src/shared/agentTranscript.js +41 -4
- package/extension/src/shared/auditRecords.js +73 -0
- package/extension/src/shared/changeHistoryModel.js +94 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +101 -37
- package/extension/src/shared/composerAttachmentDraft.js +151 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +329 -3
- package/extension/src/shared/globalPreferences.js +31 -14
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +389 -25
- package/extension/src/shared/managedUpdateProjection.js +8 -5
- package/extension/src/shared/nativeRequestIdentity.js +1 -0
- package/extension/src/shared/pageRpcContract.js +9 -2
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +114 -3
- package/extension/src/shared/runActivityModel.js +361 -0
- package/extension/src/shared/runExecutionSnapshot.js +8 -2
- package/extension/src/shared/runExecutionSnapshotCodec.js +16 -4
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/scopedPersistenceBrowserAdapter.js +6 -3
- package/extension/src/shared/selectionContext.js +37 -0
- package/extension/src/shared/sessionState.js +166 -30
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +311 -0
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +118 -13
- package/extension/src/shared/storageDb.js +77 -78
- package/extension/src/shared/storageMigration.js +74 -111
- package/extension/src/shared/storageRunActions.js +211 -22
- package/extension/src/shared/storageValuePruning.js +40 -0
- package/extension/src/shared/subagentActivityModel.js +174 -0
- package/extension/src/shared/undoOperations.js +216 -6
- package/extension/src/shared/updateConsent.js +42 -11
- package/extension/src/shared/updateRuntimeIdentity.js +1 -1
- package/extension/src/shared/writebackIntent.js +78 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +67 -48
- package/extension/src/shared/writingStyle.js +33 -0
- package/extension/styles/panel.css +1987 -385
- package/extension/styles/popup.css +110 -0
- package/native-host/src/anthropicBridgeRequest.js +6 -3
- package/native-host/src/anthropicBridgeResponse.js +6 -2
- package/native-host/src/anthropicMessagesBridge.js +24 -16
- package/native-host/src/bootstrapMigration.js +296 -0
- package/native-host/src/bootstrapMigrationContract.js +101 -0
- package/native-host/src/bootstrapUpdate.js +233 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeResponse.js +10 -0
- package/native-host/src/chatCompletionsBridge.js +21 -15
- package/native-host/src/codexConfigPaths.js +106 -0
- package/native-host/src/codexHome.js +66 -73
- package/native-host/src/codexHomeScope.js +124 -0
- package/native-host/src/codexInheritedAuth.js +90 -0
- package/native-host/src/codexLocalSkills.js +301 -0
- package/native-host/src/codexModelRecovery.js +107 -0
- package/native-host/src/codexPromptAssembly.js +33 -12
- package/native-host/src/codexProviderLaunch.js +3 -3
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexRuntimeIdentity.js +1 -0
- package/native-host/src/codexSessionRunner.js +211 -318
- package/native-host/src/codexSessionTiming.js +21 -8
- package/native-host/src/codexSkillIsolation.js +170 -0
- package/native-host/src/codexThreadFork.js +19 -3
- package/native-host/src/index.js +19 -7
- package/native-host/src/managedInstall.js +115 -39
- package/native-host/src/managedInstallRecovery.js +140 -0
- package/native-host/src/managedRuntimeEvidence.js +57 -0
- package/native-host/src/mirrorWorkspace.js +92 -22
- package/native-host/src/nativeDoctor.js +15 -0
- package/native-host/src/nativeEnvironment.js +70 -0
- package/native-host/src/nativeHelperLifecycle.js +21 -0
- package/native-host/src/nativeHostRegistration.js +33 -0
- package/native-host/src/nativeQuotas.js +15 -8
- package/native-host/src/nativeRunLifecycle.js +71 -0
- package/native-host/src/overleafWorkspaceRules.js +16 -0
- package/native-host/src/processStopBarrier.js +115 -0
- package/native-host/src/projectPathPlatform.js +30 -0
- package/native-host/src/providerHttp.js +80 -0
- package/native-host/src/providerModelCatalog.js +4 -3
- package/native-host/src/providerProfile.js +17 -3
- package/native-host/src/providerReasoning.js +10 -11
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/providerStore.js +76 -14
- package/native-host/src/releaseTransport.js +14 -5
- package/native-host/src/responsesPassthroughBridge.js +266 -52
- package/native-host/src/runtimeInstaller.js +21 -1
- package/native-host/src/selectionScope.js +86 -0
- package/native-host/src/skills/parallel-subagents/SKILL.md +31 -3
- package/native-host/src/skills/writing-style-builder/SKILL.md +146 -0
- package/native-host/src/subagentAdmission.js +26 -0
- package/native-host/src/subagentBroker.js +176 -159
- package/native-host/src/subagentTelemetry.js +325 -0
- package/native-host/src/subagentWorkspace.js +103 -0
- package/native-host/src/subagentWorkspacePath.js +34 -0
- package/native-host/src/taskRunnerRuntime.js +130 -57
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/turnAttachments.js +113 -0
- package/native-host/src/updateManager.js +406 -38
- package/native-host/src/updateNetworkControl.js +75 -0
- package/native-host/src/updateTrust.js +21 -2
- package/native-host/src/vendor/pdfjs/LICENSE +177 -0
- package/native-host/src/vendor/pdfjs/SOURCE.txt +3 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
- package/native-host/src/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/LICENSE +36 -0
- package/native-host/src/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/pdf.mjs +26740 -0
- package/native-host/src/vendor/pdfjs/pdf.worker.mjs +62201 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +102 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
- package/native-host/src/writingStyleEvidence.js +102 -0
- package/native-host/src/writingStylePdfWorker.js +35 -0
- package/native-host/src/writingStyleProfile.js +165 -0
- package/native-host/src/writingStyleRuntime.js +475 -0
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +1 -1
- 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,
|
|
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
|
|
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
|
|
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.
|
|
94
|
-
return okResponse(request.id, await
|
|
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
|
-
|
|
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 = () =>
|
|
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
|
|
149
|
-
|
|
150
|
-
|
|
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,
|
|
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 &&
|
|
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:
|
|
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'
|
|
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
|
-
|
|
643
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
1101
|
+
installedAligned: Boolean(readAlignedManagedVersion(context)),
|
|
791
1102
|
previousVersion: readVersionPointer(context.nativeRoot, 'previous-version'),
|
|
792
|
-
transaction: journal ? publicTransaction(journal)
|
|
793
|
-
|
|
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 ||
|
|
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 ===
|
|
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 + '-' +
|
|
1433
|
+
const temp = target + '.tmp-' + process.pid + '-' + crypto.randomBytes(6).toString('hex');
|
|
1083
1434
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
1084
|
-
fs.
|
|
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
|
};
|