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
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
3
|
+
else root.CodexOverleafFileMover = factory();
|
|
4
|
+
})(typeof window !== 'undefined' ? window : globalThis, function () {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
function create(deps = {}) {
|
|
8
|
+
const { window, document, treeOperations, snapshotRouter, textFileCreator } = deps;
|
|
9
|
+
const normalizePath = deps.normalizeSafeProjectPath || window.CodexOverleafProjectFiles?.normalizeSafeProjectPath;
|
|
10
|
+
const delay = deps.delay || (ms => new Promise(resolve => window.setTimeout(resolve, ms)));
|
|
11
|
+
const error = (code, message) => Object.assign(new Error(message), { code });
|
|
12
|
+
const parent = path => path.split('/').slice(0, -1).join('/');
|
|
13
|
+
const basename = path => path.split('/').pop();
|
|
14
|
+
const join = (folder, name) => [folder, name].filter(Boolean).join('/');
|
|
15
|
+
function entities() {
|
|
16
|
+
treeOperations.invalidateDomProjectPathCache?.();
|
|
17
|
+
const root = document.querySelector('[data-testid="file-tree-list-root"], .file-tree-list[role="tree"]');
|
|
18
|
+
const unique = new Map();
|
|
19
|
+
for (const row of root?.querySelectorAll?.('[role="treeitem"]') || []) {
|
|
20
|
+
const entity = row.querySelector?.(':scope > .entity[data-file-id]')
|
|
21
|
+
|| row.querySelector?.('[data-file-type][data-file-id]');
|
|
22
|
+
const id = entity?.dataset?.fileId || entity?.getAttribute?.('data-file-id');
|
|
23
|
+
const type = entity?.dataset?.fileType || entity?.getAttribute?.('data-file-type');
|
|
24
|
+
const path = normalizePath?.(treeOperations.readProjectPathFromNode?.(row));
|
|
25
|
+
if (id && path && ['doc', 'file', 'folder'].includes(type)) unique.set(type + ':' + id + ':' + path, { id, path, type });
|
|
26
|
+
}
|
|
27
|
+
return [...unique.values()];
|
|
28
|
+
}
|
|
29
|
+
function at(path, type) {
|
|
30
|
+
const found = entities().filter(entry => entry.path === path && (!type || entry.type === type));
|
|
31
|
+
return found.length === 1 ? found[0] : null;
|
|
32
|
+
}
|
|
33
|
+
function folderId(path, projectId) {
|
|
34
|
+
if (path) return at(path, 'folder')?.id || '';
|
|
35
|
+
for (const root of [window._ide, window.Overleaf, window.overleaf, window.OL]) {
|
|
36
|
+
const project = root?.project;
|
|
37
|
+
if ((project?._id || project?.id) !== projectId) continue;
|
|
38
|
+
const folder = Array.isArray(project.rootFolder) ? project.rootFolder[0] : project.rootFolder;
|
|
39
|
+
if (folder?._id || folder?.id) return folder._id || folder.id;
|
|
40
|
+
}
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
async function digest(file, kind) {
|
|
44
|
+
let bytes;
|
|
45
|
+
if (kind === 'text' && typeof file?.content === 'string') bytes = new TextEncoder().encode(file.content);
|
|
46
|
+
else if (kind === 'binary' && typeof file?.contentBase64 === 'string') {
|
|
47
|
+
bytes = Uint8Array.from((window.atob || globalThis.atob)(file.contentBase64), char => char.charCodeAt(0));
|
|
48
|
+
} else throw error('file_move_source_unavailable', 'The source contents could not be verified.');
|
|
49
|
+
const hash = await (window.crypto || globalThis.crypto).subtle.digest('SHA-256', bytes);
|
|
50
|
+
return [...new Uint8Array(hash)].map(byte => byte.toString(16).padStart(2, '0')).join('');
|
|
51
|
+
}
|
|
52
|
+
function csrfToken() {
|
|
53
|
+
const meta = document.querySelector('meta[name="ol-csrfToken"], meta[name="csrf-token"], meta[name="_csrf"]');
|
|
54
|
+
let value = meta ? meta.content : window.csrfToken || window._csrf;
|
|
55
|
+
if (['json', 'number'].includes(meta?.dataset?.type)) {
|
|
56
|
+
try { value = JSON.parse(value); } catch (_error) { return ''; }
|
|
57
|
+
}
|
|
58
|
+
if (meta?.dataset?.type === 'boolean') return '';
|
|
59
|
+
return typeof value === 'string' && value.length > 0 && value.length <= 4096 && !/[\0\r\n]/.test(value) ? value : '';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Compatibility consumer for identity-bound moves recorded by older builds.
|
|
63
|
+
// New mirror diffs never produce metadata moves.
|
|
64
|
+
async function moveFile(operation, options = {}) {
|
|
65
|
+
const source = normalizePath?.(operation?.path), target = normalizePath?.(operation?.to);
|
|
66
|
+
const proof = operation?.moveProof;
|
|
67
|
+
const projectId = options.runProjectId;
|
|
68
|
+
const entity = value => typeof value === 'string' && value.length > 0 && value.length <= 160 && !/[\0\r\n]/.test(value);
|
|
69
|
+
if (options.noTraceUndo !== true || !entity(proof?.sourceEntityId) || !entity(proof?.targetEntityId)
|
|
70
|
+
|| proof.sourceEntityId !== proof.targetEntityId) return { ok: false, code: 'file_tree_controls_unavailable', changedDocument: false,
|
|
71
|
+
reason: 'A recorded same-entity move is required before its location can be restored.' };
|
|
72
|
+
const deadline = Math.min(Date.now() + 90000, Number.isFinite(options.deadlineAt) ? options.deadlineAt : Infinity);
|
|
73
|
+
const createdDirectories = [], completed = [];
|
|
74
|
+
let entityId = '', entityType = '', attempted = false, currentPath = source;
|
|
75
|
+
const assertCurrent = () => {
|
|
76
|
+
if (!projectId || treeOperations.getProjectId() !== projectId) throw error('aborted_project_changed', 'The project changed during the file move.');
|
|
77
|
+
if (options.isCurrent?.() === false) throw error('codex_cancelled', 'The file move was cancelled.');
|
|
78
|
+
if (Date.now() >= deadline) throw error('file_move_deadline_exceeded', 'The file move verification deadline was reached.');
|
|
79
|
+
};
|
|
80
|
+
const inspectRemainingDirectories = async () => {
|
|
81
|
+
if (!Array.isArray(proof.createdDirectories) || !proof.createdDirectories.length) return {};
|
|
82
|
+
try {
|
|
83
|
+
const directoryCleanup = await textFileCreator?.cleanupCreatedDirectories?.(proof.createdDirectories, {
|
|
84
|
+
projectId, isCurrent: () => { assertCurrent(); return true; }
|
|
85
|
+
});
|
|
86
|
+
if (directoryCleanup) return { directoryCleanup };
|
|
87
|
+
} catch (_error) { /* The verified file move remains complete; only its directory inspection is unresolved. */ }
|
|
88
|
+
return { directoryCleanup: { ok: false, removed: [], remaining: proof.createdDirectories, requiresExplicitCleanup: true } };
|
|
89
|
+
};
|
|
90
|
+
const observedPath = () => {
|
|
91
|
+
if (treeOperations.getProjectId() !== projectId) return '';
|
|
92
|
+
const found = entities().filter(entry => entry.id === entityId && entry.type === entityType);
|
|
93
|
+
return found.length === 1 ? found[0].path : '';
|
|
94
|
+
};
|
|
95
|
+
const snapshot = async () => {
|
|
96
|
+
assertCurrent(); snapshotRouter.invalidateCache?.();
|
|
97
|
+
const result = await snapshotRouter.fetchProjectZipSnapshot({ force: true, maxAgeMs: 0, serverOnly: true,
|
|
98
|
+
includeContent: true, includeBinaryFiles: true, writebackVerification: true,
|
|
99
|
+
zipTimeoutMs: Math.max(1, Math.min(30000, deadline - Date.now())) });
|
|
100
|
+
assertCurrent();
|
|
101
|
+
const skipped = [...(result?.skipped || []), ...(result?.capabilities?.skipped || [])];
|
|
102
|
+
if (!result?.ok || !Array.isArray(result.files) || result.partialSnapshot === true
|
|
103
|
+
|| result.pathIdentityComplete === false || result.capabilities?.pathIdentityComplete === false
|
|
104
|
+
|| skipped.some(file => file?.unknownPath || ['zip_path_invalid', 'zip_path_duplicate'].includes(file?.reason))
|
|
105
|
+
|| result.capabilities?.fullProjectSnapshot === false || (result.id && result.id !== projectId)) {
|
|
106
|
+
throw error('file_move_source_unavailable', 'A complete server source snapshot is required to move a file.');
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
};
|
|
110
|
+
const contains = (data, path) => data.files.some(file => file.path === path)
|
|
111
|
+
|| [...(data.skipped || []), ...(data.capabilities?.skipped || [])].some(file => file.path === path);
|
|
112
|
+
const verify = async (path, data) => {
|
|
113
|
+
assertCurrent();
|
|
114
|
+
const entity = at(path);
|
|
115
|
+
if (!entity || !['doc', 'file'].includes(entity.type) || (entityId && entity.id !== entityId)
|
|
116
|
+
|| (entityType && entity.type !== entityType) || (proof.entityType && entity.type !== proof.entityType)
|
|
117
|
+
|| (proof.sourceEntityId && entity.id !== proof.sourceEntityId)) {
|
|
118
|
+
throw error('file_move_identity_changed', 'The exact source file identity changed or is unavailable.');
|
|
119
|
+
}
|
|
120
|
+
const file = data.files.find(file => file.path === path);
|
|
121
|
+
if (await digest(file, proof.kind) !== proof.sha256.toLowerCase()) {
|
|
122
|
+
throw error('stale_source_changed', 'The source file changed after Codex read it.');
|
|
123
|
+
}
|
|
124
|
+
assertCurrent();
|
|
125
|
+
if (at(path)?.id !== entity.id || at(path)?.type !== entity.type) throw error('file_move_identity_changed', 'The source file identity changed while checking its contents.');
|
|
126
|
+
return entity;
|
|
127
|
+
};
|
|
128
|
+
const checkStep = async step => {
|
|
129
|
+
const data = await snapshot(); await verify(step.from, data);
|
|
130
|
+
if (contains(data, step.to) || at(step.to)) throw error('file_move_target_exists', 'The destination or an intermediate move path is occupied.');
|
|
131
|
+
if (step.action === 'move' && folderId(parent(step.to), projectId) !== step.folderId) {
|
|
132
|
+
throw error('file_move_folder_changed', 'The destination folder identity changed.');
|
|
133
|
+
}
|
|
134
|
+
assertCurrent();
|
|
135
|
+
};
|
|
136
|
+
const confirmStep = async step => {
|
|
137
|
+
for (let i = 0; i < 8; i++) {
|
|
138
|
+
const data = await snapshot();
|
|
139
|
+
if (!contains(data, step.from) && contains(data, step.to) && at(step.to)?.id === entityId) {
|
|
140
|
+
await verify(step.to, data); currentPath = step.to; return true;
|
|
141
|
+
}
|
|
142
|
+
if (i < 7) await delay(Math.min(250 * (i + 1), 1000));
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
};
|
|
146
|
+
const send = async step => {
|
|
147
|
+
await checkStep(step);
|
|
148
|
+
const csrf = csrfToken();
|
|
149
|
+
if (!csrf) throw error('file_move_identity_unavailable', 'The page request identity is unavailable. Reload Overleaf before moving files.');
|
|
150
|
+
const Controller = window.AbortController || globalThis.AbortController;
|
|
151
|
+
const controller = new Controller();
|
|
152
|
+
const timer = window.setTimeout(() => controller.abort(), Math.max(1, deadline - Date.now()));
|
|
153
|
+
let response, transportError;
|
|
154
|
+
try {
|
|
155
|
+
assertCurrent(); attempted = true;
|
|
156
|
+
response = await window.fetch(`/project/${encodeURIComponent(projectId)}/${entityType}/${encodeURIComponent(entityId)}/${step.action}`, {
|
|
157
|
+
method: 'POST', credentials: 'same-origin', signal: controller.signal,
|
|
158
|
+
headers: { 'Content-Type': 'application/json', 'X-Csrf-Token': csrf, Accept: 'application/json' },
|
|
159
|
+
body: JSON.stringify(step.action === 'rename' ? { name: basename(step.to) } : { folder_id: step.folderId })
|
|
160
|
+
});
|
|
161
|
+
} catch (caught) { transportError = caught; }
|
|
162
|
+
finally { window.clearTimeout(timer); }
|
|
163
|
+
assertCurrent();
|
|
164
|
+
if (await confirmStep(step)) return;
|
|
165
|
+
if (response && !response.ok && response.status >= 400 && response.status < 500 && response.status !== 408) {
|
|
166
|
+
const rejected = error('file_move_rejected', 'Overleaf rejected the file move.');
|
|
167
|
+
rejected.definitive = true; throw rejected;
|
|
168
|
+
}
|
|
169
|
+
const unknown = error('file_move_unconfirmed', transportError
|
|
170
|
+
? 'The move response was lost. Check the file location before retrying.'
|
|
171
|
+
: 'The move result could not be confirmed. Check the file location before retrying.');
|
|
172
|
+
unknown.receiptUnconfirmed = true; throw unknown;
|
|
173
|
+
};
|
|
174
|
+
try {
|
|
175
|
+
assertCurrent();
|
|
176
|
+
if (!source || !target || source === target || operation.type !== 'move' || proof?.v !== 1
|
|
177
|
+
|| !['text', 'binary'].includes(proof.kind) || !/^[a-f0-9]{64}$/i.test(proof.sha256 || '')) {
|
|
178
|
+
throw error('invalid_project_path', 'A verified source, different destination and content hash are required for a move.');
|
|
179
|
+
}
|
|
180
|
+
if (parent(source)) await textFileCreator?.prepareUploadParent?.(parent(source), { createMissing: false, isCurrent: () => { assertCurrent(); return true; } });
|
|
181
|
+
let data = await snapshot();
|
|
182
|
+
if (!contains(data, source) && proof.sourceEntityId && at(target)?.id === proof.sourceEntityId) {
|
|
183
|
+
entityId = proof.sourceEntityId; entityType = at(target).type;
|
|
184
|
+
await verify(target, data);
|
|
185
|
+
return { ok: true, verified: true, verification: 'overleaf-zip', idempotent: true, changedDocument: false,
|
|
186
|
+
method: 'overleaf.entity-move', moveProof: { ...proof, targetEntityId: entityId }, ...await inspectRemainingDirectories() };
|
|
187
|
+
}
|
|
188
|
+
const sourceEntity = await verify(source, data); entityId = sourceEntity.id; entityType = sourceEntity.type;
|
|
189
|
+
if (contains(data, target) || at(target)) throw error('file_move_target_exists', 'The move destination already exists.');
|
|
190
|
+
const originalParent = parent(source), destinationParent = parent(target);
|
|
191
|
+
const originalFolderId = originalParent === destinationParent ? '' : folderId(originalParent, projectId);
|
|
192
|
+
if (originalParent !== destinationParent && !originalFolderId) throw error('file_move_folder_changed', 'The original folder identity is unavailable.');
|
|
193
|
+
if (destinationParent && originalParent !== destinationParent && !folderId(destinationParent, projectId)) {
|
|
194
|
+
await textFileCreator?.prepareUploadParent?.(destinationParent, { createMissing: true, isCurrent: () => { assertCurrent(); return true; },
|
|
195
|
+
onCreatedDirectory: proof => createdDirectories.push(proof) });
|
|
196
|
+
}
|
|
197
|
+
const destinationFolderId = originalParent === destinationParent ? '' : folderId(destinationParent, projectId);
|
|
198
|
+
if (originalParent !== destinationParent && !destinationFolderId) throw error('file_move_folder_changed', 'The destination folder identity is unavailable.');
|
|
199
|
+
data = await snapshot(); await verify(source, data);
|
|
200
|
+
const steps = [];
|
|
201
|
+
const rename = to => { steps.push({ action: 'rename', from: currentPath, to }); currentPath = to; };
|
|
202
|
+
const move = to => { steps.push({ action: 'move', from: currentPath, to, folderId: destinationFolderId, fromFolderId: originalFolderId }); currentPath = to; };
|
|
203
|
+
if (originalParent === destinationParent) rename(target);
|
|
204
|
+
else if (basename(source) === basename(target)) move(target);
|
|
205
|
+
else if (!contains(data, join(originalParent, basename(target))) && !at(join(originalParent, basename(target)))) {
|
|
206
|
+
rename(join(originalParent, basename(target))); move(target);
|
|
207
|
+
} else if (!contains(data, join(destinationParent, basename(source))) && !at(join(destinationParent, basename(source)))) {
|
|
208
|
+
move(join(destinationParent, basename(source))); rename(target);
|
|
209
|
+
} else {
|
|
210
|
+
const token = window.crypto?.randomUUID?.().replace(/-/g, '').slice(0, 16);
|
|
211
|
+
if (!token) throw error('file_move_target_exists', 'An unoccupied intermediate name could not be reserved safely.');
|
|
212
|
+
const extension = basename(source).match(/\.[^.]+$/)?.[0] || '';
|
|
213
|
+
const temporary = 'codex-move-' + token + extension;
|
|
214
|
+
if ([originalParent, destinationParent].some(folder => contains(data, join(folder, temporary)) || at(join(folder, temporary)))) {
|
|
215
|
+
throw error('file_move_target_exists', 'The temporary move path is occupied. Retry on a fresh project snapshot.');
|
|
216
|
+
}
|
|
217
|
+
rename(join(originalParent, temporary)); move(join(destinationParent, temporary)); rename(target);
|
|
218
|
+
}
|
|
219
|
+
currentPath = source;
|
|
220
|
+
for (const step of steps) { await send(step); completed.push(step); }
|
|
221
|
+
return { ok: true, verified: true, verification: 'overleaf-zip', changedDocument: true, method: 'overleaf.entity-move',
|
|
222
|
+
moveProof: { v: 1, kind: proof.kind, sha256: proof.sha256.toLowerCase(), sourceEntityId: entityId, targetEntityId: entityId, entityType },
|
|
223
|
+
...await inspectRemainingDirectories(),
|
|
224
|
+
...(createdDirectories.length ? { createdDirectories } : {}) };
|
|
225
|
+
} catch (caught) {
|
|
226
|
+
let rolledBack = false;
|
|
227
|
+
// Only a definite refusal permits compensation. A lost response may
|
|
228
|
+
// still be in flight; never replay or reverse that uncertain mutation.
|
|
229
|
+
if (caught.definitive && completed.length && treeOperations.getProjectId() === projectId && options.isCurrent?.() !== false) {
|
|
230
|
+
try {
|
|
231
|
+
for (const prior of completed.slice().reverse()) {
|
|
232
|
+
await send({ action: prior.action, from: prior.to, to: prior.from, folderId: prior.fromFolderId });
|
|
233
|
+
}
|
|
234
|
+
rolledBack = true;
|
|
235
|
+
} catch (_rollbackError) { /* Preserve the verified entity and report its location for recovery. */ }
|
|
236
|
+
}
|
|
237
|
+
const observed = entityId ? observedPath() : '';
|
|
238
|
+
const changedDocument = attempted || createdDirectories.length > 0;
|
|
239
|
+
const reason = rolledBack ? 'The move was not completed; the original file location was restored.' : caught.message;
|
|
240
|
+
const moveRecovery = attempted && !rolledBack ? { v: 1, projectId, kind: proof?.kind, sha256: proof?.sha256,
|
|
241
|
+
entityId, entityType, sourcePath: source, targetPath: target, observedPath: observed } : null;
|
|
242
|
+
return { ok: false, code: rolledBack ? 'file_move_rolled_back' : caught.code || 'file_move_failed', reason,
|
|
243
|
+
changedDocument, rolledBack, receiptUnconfirmed: caught.receiptUnconfirmed === true,
|
|
244
|
+
...(createdDirectories.length ? { createdDirectories } : {}),
|
|
245
|
+
...(moveRecovery ? { moveRecovery } : {}),
|
|
246
|
+
failure: { code: caught.code || 'file_move_failed', stage: attempted ? 'verify' : 'preflight',
|
|
247
|
+
severity: changedDocument ? 'warning' : 'blocked', terminalState: changedDocument ? 'needs_review' : 'blocked',
|
|
248
|
+
changedDocument, retryable: false, file: observed || source,
|
|
249
|
+
...(moveRecovery ? { evidence: { moveRecovery } } : {}),
|
|
250
|
+
userMessage: reason, nextAction: changedDocument ? 'Check the file location before retrying.' : 'Read the project again before retrying.' } };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return { moveFile };
|
|
254
|
+
}
|
|
255
|
+
return { create };
|
|
256
|
+
});
|
|
@@ -120,6 +120,7 @@
|
|
|
120
120
|
deps.getProjectId?.() || pageWindow.location.pathname || pageWindow.location.href,
|
|
121
121
|
params.preferLightweight ? 'lightweight' : 'full',
|
|
122
122
|
params.zipOnly ? 'zip-only' : 'zip-or-page',
|
|
123
|
+
params.serverOnly === true ? 'server-only' : 'editor-overlay',
|
|
123
124
|
params.allowZipFallback === false ? 'no-zip-fallback' : 'zip-fallback',
|
|
124
125
|
params.allowEditorNavigation === false ? 'no-editor-navigation' : params.allowEditorNavigation === true ? 'editor-navigation' : 'default-editor-navigation',
|
|
125
126
|
params.requireFullProject ? 'require-full-project' : 'allow-partial-project',
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
3
|
+
else root.CodexOverleafOwnedDirectoryCleanup = factory();
|
|
4
|
+
})(typeof window !== 'undefined' ? window : globalThis, function () {
|
|
5
|
+
'use strict';
|
|
6
|
+
function create(deps = {}) {
|
|
7
|
+
const { document, treeOperations, delay, now, readServerSnapshot,
|
|
8
|
+
assertVerificationOwner, normalizeDomText, normalizeSafeProjectPath } = deps;
|
|
9
|
+
const { invalidateDomProjectPathCache } = treeOperations;
|
|
10
|
+
function folderIdentity(row) {
|
|
11
|
+
const entity = row?.querySelector?.('[data-file-type="folder"]');
|
|
12
|
+
return ['data-file-id', 'data-folder-id', 'data-entity-id']
|
|
13
|
+
.map(name => entity?.getAttribute?.(name) || row?.getAttribute?.(name) || '').find(Boolean) || '';
|
|
14
|
+
}
|
|
15
|
+
// Resolve every path component within its direct native child group. A
|
|
16
|
+
// collapsed/ambiguous tree is unknown, never proof that a folder is absent.
|
|
17
|
+
function locateOwnedFolder(path) {
|
|
18
|
+
let group = document.querySelector('[data-testid="file-tree-list-root"], .file-tree-list[role="tree"]');
|
|
19
|
+
const parts = path.split('/');
|
|
20
|
+
for (let index = 0; index < parts.length; index++) {
|
|
21
|
+
if (!group?.children) throw new Error('The native folder tree is unavailable.');
|
|
22
|
+
const inner = Array.from(group.children).find(node => node.classList?.contains('file-tree-folder-list-inner')) || group;
|
|
23
|
+
const matches = Array.from(inner.children).filter(node => node.getAttribute?.('role') === 'treeitem'
|
|
24
|
+
&& node.querySelector?.('[data-file-type="folder"]') && normalizeDomText(node.getAttribute('aria-label')) === parts[index]);
|
|
25
|
+
if (matches.length > 1) throw new Error('The folder path is ambiguous.');
|
|
26
|
+
if (!matches.length) return null;
|
|
27
|
+
const row = matches[0];
|
|
28
|
+
if (index === parts.length - 1) return row;
|
|
29
|
+
if (row.getAttribute('aria-expanded') !== 'true') throw new Error('The ancestor folder must be expanded before cleanup.');
|
|
30
|
+
const sibling = row.nextElementSibling;
|
|
31
|
+
group = sibling?.getAttribute('role') === 'tree' ? sibling : row.querySelector('[role="tree"]');
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
// Native folder Delete can remove a subtree. Read/empty checks cannot
|
|
36
|
+
// make that mutation conditional on the subtree staying empty. Retain the
|
|
37
|
+
// proof for explicit native cleanup; never click Delete automatically.
|
|
38
|
+
async function cleanupCreatedDirectories(proofs, options = {}) {
|
|
39
|
+
const items = Array.isArray(proofs) ? proofs : [];
|
|
40
|
+
const removed = [], completed = new Set(), failures = [], review = [];
|
|
41
|
+
for (const proof of items.slice().sort((a, b) => String(b?.path || '').split('/').length - String(a?.path || '').split('/').length)) {
|
|
42
|
+
try {
|
|
43
|
+
const path = normalizeSafeProjectPath(proof?.path);
|
|
44
|
+
if (proof?.v !== 1 || !path || path !== proof.path || !proof.projectId || !proof.nativeFolderId
|
|
45
|
+
|| (options.projectId && proof.projectId !== options.projectId)) throw new Error('The created folder identity is not proven.');
|
|
46
|
+
const assertCurrent = () => assertVerificationOwner(proof.projectId, options.isCurrent);
|
|
47
|
+
const exactRow = () => {
|
|
48
|
+
const row = locateOwnedFolder(path);
|
|
49
|
+
if (row && folderIdentity(row) !== proof.nativeFolderId) throw new Error('The created folder identity changed.');
|
|
50
|
+
return row;
|
|
51
|
+
};
|
|
52
|
+
assertCurrent();
|
|
53
|
+
for (let depth = 1; depth <= path.split('/').length; depth++) {
|
|
54
|
+
const prefix = path.split('/').slice(0, depth).join('/'), row = locateOwnedFolder(prefix);
|
|
55
|
+
if (!row) break;
|
|
56
|
+
if (prefix === path && folderIdentity(row) !== proof.nativeFolderId) throw new Error('The created folder identity changed.');
|
|
57
|
+
if (row.getAttribute('aria-expanded') === 'true') continue;
|
|
58
|
+
const toggle = row.querySelector('.folder-expand-collapse-button');
|
|
59
|
+
if (!toggle) throw new Error('The folder cannot be expanded safely.');
|
|
60
|
+
assertCurrent(); toggle.click();
|
|
61
|
+
const deadline = now() + 5000;
|
|
62
|
+
while (locateOwnedFolder(prefix)?.getAttribute('aria-expanded') !== 'true') {
|
|
63
|
+
assertCurrent(); if (now() >= deadline) throw new Error('The folder expansion was not confirmed.');
|
|
64
|
+
await delay(100); invalidateDomProjectPathCache();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exactRow();
|
|
68
|
+
const snapshot = await readServerSnapshot({ projectId: proof.projectId, isCurrent: options.isCurrent, includeBinaryFiles: true });
|
|
69
|
+
assertCurrent();
|
|
70
|
+
const row = exactRow();
|
|
71
|
+
const entries = [...snapshot.files, ...(snapshot.skipped || []), ...(snapshot.capabilities?.skipped || [])];
|
|
72
|
+
const serverHasEntries = entries.some(entry => String(entry?.path || '').startsWith(path + '/'));
|
|
73
|
+
if (!row) {
|
|
74
|
+
if (serverHasEntries) throw new Error('Server entries remain under the folder path; absence is unconfirmed.');
|
|
75
|
+
completed.add(proof); removed.push({ ...proof, alreadyAbsent: true, verification: 'overleaf-native-tree-and-zip' }); continue;
|
|
76
|
+
}
|
|
77
|
+
const sibling = row.nextElementSibling;
|
|
78
|
+
const group = sibling?.getAttribute('role') === 'tree' ? sibling : row.querySelector('[role="tree"]');
|
|
79
|
+
if (row.getAttribute('aria-expanded') !== 'true' || !group?.children) throw new Error('The folder contents are unknown.');
|
|
80
|
+
const inner = Array.from(group.children).find(node => node.classList?.contains('file-tree-folder-list-inner')) || group;
|
|
81
|
+
const empty = inner.children.length === 0 && !serverHasEntries;
|
|
82
|
+
review.push({ ...proof, empty, requiresExplicitCleanup: true });
|
|
83
|
+
failures.push({ path, code: empty ? 'directory_requires_explicit_cleanup' : 'created_directory_not_empty',
|
|
84
|
+
reason: empty ? 'The new empty folder remains. Review and remove it through Overleaf if appropriate.'
|
|
85
|
+
: 'The created folder contains entries or unread files and was left untouched.' });
|
|
86
|
+
} catch (error) { failures.push({ path: proof?.path || '', code: error?.code || 'created_directory_cleanup_unconfirmed', reason: error?.message || '' }); }
|
|
87
|
+
}
|
|
88
|
+
return { ok: completed.size === items.length, removed, remaining: items.filter(item => !completed.has(item)),
|
|
89
|
+
failures, review, requiresExplicitCleanup: review.length > 0, changedDocument: false };
|
|
90
|
+
}
|
|
91
|
+
return { folderIdentity, cleanupCreatedDirectories };
|
|
92
|
+
}
|
|
93
|
+
return { create };
|
|
94
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
const api = factory();
|
|
3
|
+
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
4
|
+
if (root) root.CodexOverleafReferenceProjects = api;
|
|
5
|
+
})(typeof window !== 'undefined' ? window : globalThis, function () {
|
|
6
|
+
'use strict';
|
|
7
|
+
const MAX_CATALOG_BYTES = 8 * 1024 * 1024;
|
|
8
|
+
const MAX_PROJECT_BYTES = 4 * 1024 * 1024;
|
|
9
|
+
const validId = value => /^[a-f0-9]{24}$/i.test(value);
|
|
10
|
+
|
|
11
|
+
function failure(code, message) { return Object.assign(new Error(message), { code }); }
|
|
12
|
+
|
|
13
|
+
function parseCatalog(doc) {
|
|
14
|
+
const element = doc.querySelector('meta[name="ol-prefetchedProjectsBlob"]')
|
|
15
|
+
|| doc.querySelector('meta[name="ol-projects"]');
|
|
16
|
+
if (!element) throw failure('reference_catalog_unavailable',
|
|
17
|
+
'Overleaf did not return its project directory. Check that this account is signed in, then retry.');
|
|
18
|
+
let data;
|
|
19
|
+
try { data = JSON.parse(element.getAttribute('content') || ''); }
|
|
20
|
+
catch (_) { throw failure('reference_catalog_invalid', 'Overleaf returned an unreadable project directory. Retry loading projects.'); }
|
|
21
|
+
const rows = Array.isArray(data) ? data : data?.projects;
|
|
22
|
+
if (!Array.isArray(rows)) throw failure('reference_catalog_invalid', 'Overleaf returned an invalid project directory.');
|
|
23
|
+
const total = Array.isArray(data) ? rows.length : Number(data.totalSize);
|
|
24
|
+
if (Number.isFinite(total) && total > rows.length) {
|
|
25
|
+
throw failure('reference_catalog_incomplete', 'Overleaf returned only part of the project directory. Refresh Overleaf and retry loading projects.');
|
|
26
|
+
}
|
|
27
|
+
const result = new Map();
|
|
28
|
+
for (const row of rows) {
|
|
29
|
+
if (row?.trashed === true) continue;
|
|
30
|
+
const projectId = String(row?.id || row?._id || '');
|
|
31
|
+
if (!validId(projectId) || typeof row?.name !== 'string') {
|
|
32
|
+
throw failure('reference_catalog_invalid', 'A project entry could not be read. Retry loading the directory.');
|
|
33
|
+
}
|
|
34
|
+
result.set(projectId, {
|
|
35
|
+
projectId, name: row.name.slice(0, 300),
|
|
36
|
+
archived: row.archived === true,
|
|
37
|
+
shared: row.accessLevel !== 'owner',
|
|
38
|
+
readOnly: ['readOnly', 'read-only', 'read'].includes(row.accessLevel),
|
|
39
|
+
lastUpdated: typeof row.lastUpdated === 'string' ? row.lastUpdated : ''
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return [...result.values()].sort((a, b) =>
|
|
43
|
+
(Date.parse(b.lastUpdated) || 0) - (Date.parse(a.lastUpdated) || 0) || a.name.localeCompare(b.name));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function create(deps = {}) {
|
|
47
|
+
const pageWindow = deps.window || window;
|
|
48
|
+
const fetchImpl = deps.fetch || pageWindow.fetch.bind(pageWindow);
|
|
49
|
+
const reads = new Map();
|
|
50
|
+
|
|
51
|
+
async function catalogText(response) {
|
|
52
|
+
if (Number(response.headers.get('content-length')) > MAX_CATALOG_BYTES) {
|
|
53
|
+
throw failure('reference_catalog_too_large', 'The Overleaf project directory is too large to load.');
|
|
54
|
+
}
|
|
55
|
+
if (!response.body?.getReader) {
|
|
56
|
+
const text = await response.text();
|
|
57
|
+
if (new TextEncoder().encode(text).byteLength > MAX_CATALOG_BYTES) {
|
|
58
|
+
throw failure('reference_catalog_too_large', 'The Overleaf project directory is too large to load.');
|
|
59
|
+
}
|
|
60
|
+
return text;
|
|
61
|
+
}
|
|
62
|
+
const reader = response.body.getReader(), decoder = new TextDecoder();
|
|
63
|
+
let bytes = 0, text = '';
|
|
64
|
+
try {
|
|
65
|
+
while (true) {
|
|
66
|
+
const chunk = await reader.read();
|
|
67
|
+
if (chunk.done) break;
|
|
68
|
+
bytes += chunk.value.byteLength;
|
|
69
|
+
if (bytes > MAX_CATALOG_BYTES) {
|
|
70
|
+
await reader.cancel();
|
|
71
|
+
throw failure('reference_catalog_too_large', 'The Overleaf project directory is too large to load.');
|
|
72
|
+
}
|
|
73
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
74
|
+
}
|
|
75
|
+
return text + decoder.decode();
|
|
76
|
+
} finally { reader.releaseLock(); }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function listProjects() {
|
|
80
|
+
const controller = new AbortController();
|
|
81
|
+
const timer = setTimeout(() => controller.abort(), 20000);
|
|
82
|
+
try {
|
|
83
|
+
const response = await fetchImpl(new URL('/project', pageWindow.location.origin).href, {
|
|
84
|
+
credentials: 'include', cache: 'no-store', headers: { accept: 'text/html' },
|
|
85
|
+
signal: controller.signal
|
|
86
|
+
});
|
|
87
|
+
if (!response.ok) throw failure('reference_catalog_http', 'Overleaf could not load projects (HTTP ' + response.status + '). Retry loading projects.');
|
|
88
|
+
if (response.url && new URL(response.url).origin !== pageWindow.location.origin) {
|
|
89
|
+
throw failure('reference_catalog_login', 'Sign in to Overleaf on this domain, then retry loading projects.');
|
|
90
|
+
}
|
|
91
|
+
const text = await catalogText(response);
|
|
92
|
+
const parser = new pageWindow.DOMParser();
|
|
93
|
+
const projects = parseCatalog(parser.parseFromString(text, 'text/html'));
|
|
94
|
+
return { ok: true, projects, total: projects.length, capturedAt: new Date().toISOString() };
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return { ok: false, code: error.code || 'reference_catalog_failed',
|
|
97
|
+
error: controller.signal.aborted ? 'Loading Overleaf projects timed out. Retry loading projects.' : error.message };
|
|
98
|
+
} finally { clearTimeout(timer); }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function readProject(params = {}) {
|
|
102
|
+
const projectId = String(params.projectId || '');
|
|
103
|
+
const requestId = String(params.requestId || '');
|
|
104
|
+
if (!validId(projectId) || !requestId || requestId.length > 160) {
|
|
105
|
+
return { ok: false, code: 'reference_project_invalid', error: 'Reference project identity is invalid.' };
|
|
106
|
+
}
|
|
107
|
+
if (reads.has(requestId)) return { ok: false, code: 'reference_read_busy', error: 'This reference read is already running.' };
|
|
108
|
+
const controller = new AbortController();
|
|
109
|
+
reads.set(requestId, controller);
|
|
110
|
+
try {
|
|
111
|
+
const snapshot = await deps.snapshotRouter.fetchProjectZipSnapshot({
|
|
112
|
+
includeBinaryFiles: false, includeContent: true, zipTimeoutMs: 30000
|
|
113
|
+
}, { projectId, signal: controller.signal });
|
|
114
|
+
if (controller.signal.aborted) throw failure('reference_read_cancelled', 'Reference reading was cancelled.');
|
|
115
|
+
if (!snapshot?.ok) throw failure('reference_project_unavailable', snapshot?.reason || 'The reference project could not be read.');
|
|
116
|
+
if (snapshot.pathIdentityComplete === false) throw failure('reference_project_incomplete', 'Some ZIP file identities could not be read unambiguously.');
|
|
117
|
+
const files = (snapshot.files || []).filter(file => /\.tex$/i.test(file.path || ''));
|
|
118
|
+
if (!files.length) throw failure('reference_project_empty', 'This project contains no readable TeX files.');
|
|
119
|
+
if (files.length > 1000 || files.some(file => typeof file.content !== 'string')) {
|
|
120
|
+
throw failure('reference_project_incomplete', 'The reference project could not be read completely.');
|
|
121
|
+
}
|
|
122
|
+
const skipped = snapshot.skipped || [];
|
|
123
|
+
if (skipped.some(file => /\.tex$/i.test(typeof file === 'string' ? file : file?.path || ''))) {
|
|
124
|
+
throw failure('reference_project_incomplete', 'Some TeX files in the reference project could not be read.');
|
|
125
|
+
}
|
|
126
|
+
const encoder = new TextEncoder();
|
|
127
|
+
const size = files.reduce((sum, file) => sum + encoder.encode(file.content).byteLength, 0);
|
|
128
|
+
if (size > MAX_PROJECT_BYTES) throw failure('reference_project_too_large', 'Reference project text exceeds 4 MiB. Add a representative PDF instead.');
|
|
129
|
+
return {
|
|
130
|
+
ok: true, projectId, capturedAt: new Date().toISOString(),
|
|
131
|
+
files: files.map(file => ({ path: file.path, content: file.content, kind: 'text' })),
|
|
132
|
+
capabilities: { referenceTextComplete: true, method: 'overleaf-reference-zip' }
|
|
133
|
+
};
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return { ok: false, code: error.code || 'reference_project_failed', error: error.message };
|
|
136
|
+
} finally { reads.delete(requestId); }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function cancelRead(params = {}) {
|
|
140
|
+
const requestId = String(params.requestId || '');
|
|
141
|
+
const controller = reads.get(requestId);
|
|
142
|
+
if (controller) controller.abort();
|
|
143
|
+
return { ok: true, cancelled: Boolean(controller) };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { listProjects, readProject, cancelRead };
|
|
147
|
+
}
|
|
148
|
+
return { create, parseCatalog };
|
|
149
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
const api = factory();
|
|
3
|
+
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
4
|
+
if (root) root.CodexOverleafReviewContext = api;
|
|
5
|
+
})(typeof window !== 'undefined' ? window : globalThis, function () {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const cancelledOwners = new Map();
|
|
9
|
+
function cancelOwner(token) {
|
|
10
|
+
if (typeof token !== 'string' || !token || token.length > 256) return false;
|
|
11
|
+
for (const [id, until] of cancelledOwners) if (until <= Date.now()) cancelledOwners.delete(id);
|
|
12
|
+
cancelledOwners.set(token, Date.now() + 120000); return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// One context per request. The small response reserve lets an expired review
|
|
16
|
+
// return its known partial evidence before the caller's transport timer fires.
|
|
17
|
+
function create(deps, input = {}) {
|
|
18
|
+
const now = deps.now || Date.now;
|
|
19
|
+
const timers = typeof deps.window?.setTimeout === 'function' ? deps.window : globalThis;
|
|
20
|
+
const started = now(), limit = input.deadlineAt == null ? started + 120000 : input.deadlineAt;
|
|
21
|
+
const deadlineAt = Number.isFinite(limit) ? limit - Math.min(250, Math.max(0, limit - started) / 10) : started;
|
|
22
|
+
const cancellation = deps.readWriteCancellationSequence?.();
|
|
23
|
+
const appliedLists = new Set(), observations = new Map();
|
|
24
|
+
let stopped = null, mutationStarted = false;
|
|
25
|
+
const params = { ...input, deadlineAt };
|
|
26
|
+
const failure = (code, reason) => ({ ok: false, code, reason, changedDocument: false,
|
|
27
|
+
failure: { code: code === 'codex_cancelled' ? 'codex_run_cancelled' : 'tracked_changes_remain',
|
|
28
|
+
stage: 'reviewing', severity: 'blocked', terminalState: 'blocked', changedDocument: false,
|
|
29
|
+
userMessage: reason, nextAction: 'Check the retained per-file results in Overleaf before retrying.',
|
|
30
|
+
evidence: { originalCode: code, writeStarted: false } } });
|
|
31
|
+
function check() {
|
|
32
|
+
if (stopped) return stopped;
|
|
33
|
+
if (params.reviewOwnerToken && cancelledOwners.has(params.reviewOwnerToken))
|
|
34
|
+
return stopped = failure('codex_cancelled', 'The Undo owner disconnected; no further review writes were started.');
|
|
35
|
+
if (!Number.isFinite(limit) || now() >= deadlineAt)
|
|
36
|
+
return stopped = failure('writeback_deadline_exceeded', 'The review time budget expired; no further review writes were started.');
|
|
37
|
+
if (typeof deps.readWriteCancellationSequence === 'function' && deps.readWriteCancellationSequence() !== cancellation)
|
|
38
|
+
return stopped = failure('codex_cancelled', 'The review was cancelled; no further review writes were started.');
|
|
39
|
+
const project = deps.getProjectId?.() || /\/project\/([^/?#]+)/.exec(deps.window?.location?.pathname || '')?.[1];
|
|
40
|
+
if (!params.runProjectId || (project && project !== params.runProjectId))
|
|
41
|
+
return stopped = failure('aborted_project_changed', 'The original review project is no longer active.');
|
|
42
|
+
const blocked = deps.checkWritebackRunProjectId?.(params);
|
|
43
|
+
if (blocked) return stopped = blocked.skipped?.[0]?.result || blocked;
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function assert() {
|
|
47
|
+
const blocked = check();
|
|
48
|
+
if (blocked) throw Object.assign(new Error(blocked.reason || blocked.code), { reviewFailure: blocked });
|
|
49
|
+
}
|
|
50
|
+
async function wait(work) {
|
|
51
|
+
assert();
|
|
52
|
+
let timer;
|
|
53
|
+
try {
|
|
54
|
+
const timeout = new Promise((_, reject) => {
|
|
55
|
+
timer = timers.setTimeout(() => {
|
|
56
|
+
const blocked = check() || failure('writeback_deadline_exceeded', 'The review time budget expired.');
|
|
57
|
+
stopped = blocked;
|
|
58
|
+
reject(Object.assign(new Error(blocked.reason), { reviewFailure: blocked }));
|
|
59
|
+
}, Math.max(0, deadlineAt - now()));
|
|
60
|
+
});
|
|
61
|
+
const result = await Promise.race([Promise.resolve().then(() => { assert(); return work(); }), timeout]);
|
|
62
|
+
assert();
|
|
63
|
+
return result;
|
|
64
|
+
} finally { if (timer != null) timers.clearTimeout(timer); }
|
|
65
|
+
}
|
|
66
|
+
const remember = entry => {
|
|
67
|
+
if (entry.result?.changedDocument === true || entry.result?.failure?.changedDocument === true) mutationStarted = true;
|
|
68
|
+
const ref = entry.trackedChange || { path: entry.operation?.path || '', key: 'snapshot-undo:' + (entry.operation?.path || ''), id: '' };
|
|
69
|
+
observations.set(ref.path + '\0' + (ref.key || ref.id || ''), { trackedChange: ref, result: entry.result });
|
|
70
|
+
};
|
|
71
|
+
const scoped = { ...deps, reviewGuard: check, reviewAwait: work => wait(work),
|
|
72
|
+
reviewDeadlineAt: deadlineAt,
|
|
73
|
+
createReviewApplied() { const list = []; appliedLists.add(list); return list; } };
|
|
74
|
+
for (const name of ['openFileByPath', 'setReviewingEnabled', 'verifyActiveEditorText']) {
|
|
75
|
+
if (typeof deps[name] === 'function') scoped[name] = (...args) => wait(() => deps[name](...args));
|
|
76
|
+
}
|
|
77
|
+
scoped.delay = ms => wait(() => deps.delay(Math.min(ms, Math.max(0, deadlineAt - now()))));
|
|
78
|
+
if (typeof deps.readActiveEditorText === 'function') scoped.readActiveEditorText = (...args) => { assert(); return deps.readActiveEditorText(...args); };
|
|
79
|
+
if (typeof deps.clickNode === 'function') scoped.clickNode = (...args) => { assert(); mutationStarted = true; return deps.clickNode(...args); };
|
|
80
|
+
for (const name of ['applyOperationsCore', 'applyOperationsWithNoTraceUndo']) {
|
|
81
|
+
if (typeof deps[name] !== 'function') continue;
|
|
82
|
+
scoped[name] = async (operations, options = {}) => {
|
|
83
|
+
assert();
|
|
84
|
+
const result = await deps[name](operations, { ...options, deadlineAt,
|
|
85
|
+
reviewGuard: check, reviewAwait: work => wait(work),
|
|
86
|
+
onOperationResult(entry) { remember(entry); options.onOperationResult?.(entry); } });
|
|
87
|
+
for (const entry of result.applied || []) remember(entry);
|
|
88
|
+
// Preserve a completed in-flight result even if cancellation just arrived.
|
|
89
|
+
return result;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (typeof deps.confirmReviewWriteback === 'function') scoped.confirmReviewWriteback = async (original, result) => {
|
|
93
|
+
for (const entry of result.applied || []) remember(entry);
|
|
94
|
+
return deps.confirmReviewWriteback({ ...original, deadlineAt, reviewGuard: check, reviewAwait: work => wait(work) }, result);
|
|
95
|
+
};
|
|
96
|
+
async function run(work) {
|
|
97
|
+
try { assert(); return await work(scoped, params); }
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (!error?.reviewFailure) throw error;
|
|
100
|
+
for (const list of appliedLists) for (const entry of list) remember(entry);
|
|
101
|
+
const applied = [...observations.values()].filter(entry => entry.result?.ok !== false);
|
|
102
|
+
const path = deps.getActiveFilePath?.() || '';
|
|
103
|
+
const changedDocument = mutationStarted || applied.some(entry => entry.result?.changedDocument !== false);
|
|
104
|
+
const blocked = { ...error.reviewFailure, changedDocument,
|
|
105
|
+
failure: { ...error.reviewFailure.failure, changedDocument,
|
|
106
|
+
evidence: { ...error.reviewFailure.failure?.evidence, writeStarted: changedDocument } } };
|
|
107
|
+
return { ok: false, applied, skipped: [{ trackedChange: { path }, result: blocked }] };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return Object.freeze({ run, check, assert, wait, params, scoped });
|
|
111
|
+
}
|
|
112
|
+
return Object.freeze({ create, cancelOwner });
|
|
113
|
+
});
|