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
|
@@ -56,7 +56,12 @@
|
|
|
56
56
|
'codex.history.clearPlugin',
|
|
57
57
|
'skills.list',
|
|
58
58
|
'skills.install',
|
|
59
|
-
'skills.remove'
|
|
59
|
+
'skills.remove',
|
|
60
|
+
'writing-style.get',
|
|
61
|
+
'writing-style.set',
|
|
62
|
+
'codex.thread.fork',
|
|
63
|
+
'asset.readChunk',
|
|
64
|
+
'asset.release'
|
|
60
65
|
]);
|
|
61
66
|
const PageRpcContract = Modules.PageRpcContract;
|
|
62
67
|
const PANEL_DEFAULT_WIDTH = 380;
|
|
@@ -64,7 +69,9 @@
|
|
|
64
69
|
const PANEL_MAX_WIDTH = 760;
|
|
65
70
|
const PAGE_MIN_WIDTH = 520;
|
|
66
71
|
const CodexOverleafCompatibility = Modules.Compatibility;
|
|
67
|
-
const
|
|
72
|
+
const MANAGED_INSTALLATION = chrome.runtime.getManifest?.()?.background?.service_worker === 'bootstrap/background.js'
|
|
73
|
+
|| root.__CODEX_OVERLEAF_RUNTIME_PREFIX__ === 'runtime/';
|
|
74
|
+
const INSTALL_COMMAND = MANAGED_INSTALLATION ? '' : CodexOverleafCompatibility?.buildInstallCommand?.(
|
|
68
75
|
undefined,
|
|
69
76
|
undefined,
|
|
70
77
|
getCurrentExtensionId()
|
|
@@ -99,6 +106,7 @@
|
|
|
99
106
|
const {
|
|
100
107
|
buildExpectedFilesAfterOperations,
|
|
101
108
|
buildSnapshotRestoreUndo,
|
|
109
|
+
buildCreatedFileUndoCheckpoint,
|
|
102
110
|
buildUndoCheckpoint
|
|
103
111
|
} = Modules.UndoOperations;
|
|
104
112
|
const { buildHumanCompletionReport, mapAgentEventToActivity, stripEmptyHtmlCommentPlaceholders,
|
|
@@ -206,6 +214,8 @@
|
|
|
206
214
|
closeSlashMenu: () => closeSlashMenu(),
|
|
207
215
|
getCurrentProjectId: () => getCurrentProjectId(),
|
|
208
216
|
getLocalSkillsPanel: () => localSkillsPanel,
|
|
217
|
+
getCodexLocalSkills: () => getCodexLocalSkillsCatalog(),
|
|
218
|
+
onSkillsChange: () => updateSlashMenuForTaskInput(),
|
|
209
219
|
getLocale: () => getLocale(),
|
|
210
220
|
getPanel: () => panel,
|
|
211
221
|
getPanelRendererInstance: () => panelRendererInstance,
|
|
@@ -215,7 +225,7 @@
|
|
|
215
225
|
refreshStorageUsageSummary: () => refreshStorageUsageSummary(),
|
|
216
226
|
renderAuditHistoryPanel: () => renderAuditHistoryPanel(),
|
|
217
227
|
renderRecentProjectsVariant: (...args) => renderRecentProjectsVariant(...args),
|
|
218
|
-
saveStateSoon:
|
|
228
|
+
saveStateSoon: delay => saveStateSoon(delay),
|
|
219
229
|
updateGlobalPreferences: patch => getGlobalPreferences().update(patch),
|
|
220
230
|
setState: next => { state = next; },
|
|
221
231
|
tr,
|
|
@@ -228,6 +238,9 @@
|
|
|
228
238
|
closeCustomInstructionsSettings,
|
|
229
239
|
closeSkillsView,
|
|
230
240
|
getCodexOverleafSkillEnabled,
|
|
241
|
+
getCodexLocalSkillEnabled,
|
|
242
|
+
isCodexLocalSkillEnabled,
|
|
243
|
+
setCodexLocalSkillEnabled,
|
|
231
244
|
getCustomInstructionsForCurrentProject,
|
|
232
245
|
getGovernanceRulesForCurrentProject,
|
|
233
246
|
getSkillLoadingSettings,
|
|
@@ -334,6 +347,7 @@
|
|
|
334
347
|
otWarmMirrorController,
|
|
335
348
|
getActiveFocusFiles,
|
|
336
349
|
getCurrentProjectId,
|
|
350
|
+
getScope: () => ({ accountScopeId: cachedAccountScopeId, projectId: getCurrentProjectId(), generation: spaRouteGeneration }),
|
|
337
351
|
isExperimentalOtEnabled,
|
|
338
352
|
readOtBridgeStatus,
|
|
339
353
|
formatOtStatusLabel,
|
|
@@ -365,6 +379,9 @@
|
|
|
365
379
|
// whose deps reference the recovery handlers.
|
|
366
380
|
const panelMaintenance = Modules.PanelMaintenance.create({
|
|
367
381
|
StorageDb: Modules.StorageDb,
|
|
382
|
+
ChangeHistoryView: Modules.ChangeHistoryView,
|
|
383
|
+
getAccountScopeId: () => cachedAccountScopeId || '',
|
|
384
|
+
sanitizeText: sanitizeAssistantVisibleText,
|
|
368
385
|
tx,
|
|
369
386
|
showPluginToast: (...args) => showPluginToast(...args),
|
|
370
387
|
showPluginConfirm: (...args) => showPluginConfirm(...args),
|
|
@@ -379,12 +396,17 @@
|
|
|
379
396
|
openCustomInstructionsSettings: () => openCustomInstructionsSettings(),
|
|
380
397
|
onHistoryRowJump: record => jumpToHistoryRun(record),
|
|
381
398
|
resetProjectNameCache: () => recentProjects.resetProjectNameCache(),
|
|
399
|
+
clearHistoryComposer: () => {
|
|
400
|
+
pendingRetryReplacement = null;
|
|
401
|
+
clearTaskComposer();
|
|
402
|
+
persistComposerAttachmentsToState();
|
|
403
|
+
},
|
|
382
404
|
getPanel: () => panel,
|
|
383
405
|
getState: () => state,
|
|
384
406
|
setState: next => { state = next; },
|
|
385
407
|
getCurrentRunView: () => currentRunView,
|
|
386
408
|
getSettingsPanelInstance: () => settingsPanelInstance,
|
|
387
|
-
prepareRetryReplacement: run => prepareRetryReplacement(run)
|
|
409
|
+
prepareRetryReplacement: (run, task) => prepareRetryReplacement(run, task)
|
|
388
410
|
});
|
|
389
411
|
const {
|
|
390
412
|
showUndoFileSelection,
|
|
@@ -403,8 +425,15 @@
|
|
|
403
425
|
SessionState: Modules.SessionState,
|
|
404
426
|
getState: () => state,
|
|
405
427
|
setState: next => { state = next; },
|
|
406
|
-
|
|
407
|
-
saveState,
|
|
428
|
+
getScope: () => ({ accountScopeId: cachedAccountScopeId, projectId: getCurrentProjectId(), generation: spaRouteGeneration }),
|
|
429
|
+
sendBackgroundNative, saveState,
|
|
430
|
+
getEnabledCodexLocalSkillIds: () => getEnabledCodexLocalSkillIds(),
|
|
431
|
+
captureLocalSkillIdsForFork: async preferences => {
|
|
432
|
+
if (preferences.loadCodexLocalSkills === false) return [];
|
|
433
|
+
const choices = { ...preferences.codexLocalSkillEnabled };
|
|
434
|
+
const skills = await primeCodexLocalSkillsCatalog({ required: true });
|
|
435
|
+
return skills.filter(skill => Object.hasOwn(choices, skill.id) ? choices[skill.id] !== false : skill.enabled !== false).map(skill => skill.id);
|
|
436
|
+
},
|
|
408
437
|
applyStateToPanel,
|
|
409
438
|
showPluginToast,
|
|
410
439
|
tr
|
|
@@ -415,10 +444,22 @@
|
|
|
415
444
|
forkRunFromNode: sessionForkController.forkRunFromNode
|
|
416
445
|
});
|
|
417
446
|
const runTimelineView = Modules.RunTimelineView.create({
|
|
447
|
+
RunActivitySummary: Modules.RunActivitySummary,
|
|
448
|
+
RunPresence: Modules.RunPresence,
|
|
449
|
+
// recentProjects is composed below; the empty state only reads it after startup.
|
|
450
|
+
getProjectName: () => { try { return recentProjects.readCurrentProjectNameFromDom(); } catch { return ''; } },
|
|
451
|
+
getCurrentProjectId: () => getCurrentProjectId(),
|
|
452
|
+
RunActivityModel: Modules.RunActivityModel,
|
|
453
|
+
SubagentActivityView: Modules.SubagentActivityView,
|
|
418
454
|
RunGuidanceView: Modules.RunGuidanceView,
|
|
455
|
+
getPendingGuidance: run => runGuidanceController.getPending(run),
|
|
419
456
|
RunResultActions: runResultActions,
|
|
420
457
|
RunScrollLayout: Modules.RunScrollLayout,
|
|
421
458
|
RunFailureNotice: Modules.RunFailureNotice,
|
|
459
|
+
I18n: i18n,
|
|
460
|
+
FailureReasons,
|
|
461
|
+
localizeVisibleReason: value => localizeVisibleReason(value),
|
|
462
|
+
localizeCompileReason: value => Modules.CompileAdapter.localizeCompileReason(value, getLocale()),
|
|
422
463
|
tr,
|
|
423
464
|
tx,
|
|
424
465
|
getLocale,
|
|
@@ -428,6 +469,7 @@
|
|
|
428
469
|
formatEventDetail,
|
|
429
470
|
formatEventTime,
|
|
430
471
|
renderAttachmentPreviewList,
|
|
472
|
+
renderSentSelection: Modules.SelectionContextView.renderSent,
|
|
431
473
|
formatModeLabel,
|
|
432
474
|
undoRun,
|
|
433
475
|
acceptRun,
|
|
@@ -443,6 +485,10 @@
|
|
|
443
485
|
getState: () => state,
|
|
444
486
|
getCurrentRunView: () => currentRunView,
|
|
445
487
|
refillComposerForRetry,
|
|
488
|
+
canEditRun: run => runController.isRetryReplacementEligible(run, WritebackSettlement.projectRunSettlement(run)),
|
|
489
|
+
retryWriteback: retryRunWriteback,
|
|
490
|
+
getCurrentProjectId,
|
|
491
|
+
getAccountScopeId: () => cachedAccountScopeId,
|
|
446
492
|
showNativeSetupGuidance: () => showNativeUpdateGuidanceModal({}),
|
|
447
493
|
openProjectFileForFailure,
|
|
448
494
|
openStorageSettings,
|
|
@@ -465,6 +511,8 @@
|
|
|
465
511
|
upsertStreamEvent,
|
|
466
512
|
renderCompletionReport,
|
|
467
513
|
isTrackedChangeLifecycleRun,
|
|
514
|
+
configureEditButton,
|
|
515
|
+
configureWritebackButton,
|
|
468
516
|
configureUndoButton,
|
|
469
517
|
configureAcceptButton
|
|
470
518
|
} = runTimelineView;
|
|
@@ -536,7 +584,88 @@
|
|
|
536
584
|
// lives in writebackOrchestrator.js. Every dep below is either a hoisted
|
|
537
585
|
// function declaration, an already-initialized const above, or a lazy
|
|
538
586
|
// accessor thunk — safe in the top-of-init() wiring zone.
|
|
587
|
+
const selectionContextView = Modules.SelectionContextView.create({
|
|
588
|
+
getPanel: () => panel,
|
|
589
|
+
getState: () => state,
|
|
590
|
+
getProjectId: getCurrentProjectId,
|
|
591
|
+
openComposer: () => ensurePanelOpen(),
|
|
592
|
+
setSelection: (value, options = {}) => {
|
|
593
|
+
if (options.edit && panel?.querySelector('[data-task]')) readPanelInputs();
|
|
594
|
+
state = updateActiveSession(state, {
|
|
595
|
+
selectionContext: value,
|
|
596
|
+
...(options.edit ? { mode: 'auto' } : {})
|
|
597
|
+
});
|
|
598
|
+
if (options.edit && panel) {
|
|
599
|
+
const modeInput = panel.querySelector('[data-mode]');
|
|
600
|
+
if (modeInput) modeInput.value = 'auto';
|
|
601
|
+
syncModeControls();
|
|
602
|
+
}
|
|
603
|
+
saveStateSoon();
|
|
604
|
+
},
|
|
605
|
+
callPageBridge, tx, toast: showPluginToast
|
|
606
|
+
});
|
|
607
|
+
selectionContextView.start();
|
|
608
|
+
const writingStyleSettings = Modules.WritingStyleSettings.create({
|
|
609
|
+
tx,
|
|
610
|
+
getScope: () => {
|
|
611
|
+
const projectId = getCurrentProjectId();
|
|
612
|
+
return cachedAccountScopeId && projectId ? { accountScopeId: cachedAccountScopeId, projectId } : null;
|
|
613
|
+
},
|
|
614
|
+
request: sendBackgroundNative,
|
|
615
|
+
getReferenceSnapshot: async (projectId, requestId) => {
|
|
616
|
+
const result = await callPageBridge('getReferenceProjectSnapshot', { projectId, requestId });
|
|
617
|
+
if (!result?.ok) throw Object.assign(new Error(result?.error || result?.reason || tx(
|
|
618
|
+
'The reference project could not be read.', '参考项目未能读取。')), { code: result?.code });
|
|
619
|
+
return result;
|
|
620
|
+
},
|
|
621
|
+
cancelReferenceRead: async requestId => {
|
|
622
|
+
const result = await callPageBridge('cancelReferenceRead', { requestId });
|
|
623
|
+
if (!result?.ok) throw new Error(result?.error || tx('The reference read could not be cancelled.', '参考资料读取未能取消。'));
|
|
624
|
+
return result;
|
|
625
|
+
},
|
|
626
|
+
isRunning: () => Boolean(currentRunView),
|
|
627
|
+
listProjects: async () => {
|
|
628
|
+
const result = await callPageBridge('listReferenceProjects', {});
|
|
629
|
+
if (!result?.ok) throw new Error(result?.error || tx(
|
|
630
|
+
'The Overleaf project directory could not be loaded.', 'Overleaf 项目目录未能加载。'));
|
|
631
|
+
return result.projects;
|
|
632
|
+
},
|
|
633
|
+
getModelSettings: async () => {
|
|
634
|
+
await providerSettingsCoordinator.ensureLoaded();
|
|
635
|
+
if (panel?.querySelector('[data-task]')) readPanelInputs();
|
|
636
|
+
return {
|
|
637
|
+
model: state.model,
|
|
638
|
+
reasoningEffort: state.reasoningEffort,
|
|
639
|
+
speedTier: state.speedTier,
|
|
640
|
+
providerSelection: providerSettingsCoordinator.getRunSelection(state.providerId || 'builtin'),
|
|
641
|
+
locale: getLocale()
|
|
642
|
+
};
|
|
643
|
+
},
|
|
644
|
+
preflightProjectReferences: ({ project, assertCurrent }) => {
|
|
645
|
+
assertCurrent();
|
|
646
|
+
return Modules.SensitivePreflight.create({
|
|
647
|
+
SensitiveScan, normalizeGovernanceRules, isTextSnapshotFile, getCurrentProjectId,
|
|
648
|
+
appendRunEvent: () => {}, appendCompletionReport: () => {},
|
|
649
|
+
showPluginConfirm, tr, tx
|
|
650
|
+
}).run({ task: '', project, rules: getGovernanceRulesForCurrentProject(), assertCurrent });
|
|
651
|
+
},
|
|
652
|
+
startBuild: async params => {
|
|
653
|
+
const gate = await nativeCompatibilityController.ensureForMethod('codex.run');
|
|
654
|
+
if (!gate.ok) throw new Error(gate.response?.error?.message || 'Native host update required.');
|
|
655
|
+
const id = crypto.randomUUID();
|
|
656
|
+
const promise = nativeChannel.sendBackgroundNative(nativeCompatibilityController.attachEvidence({
|
|
657
|
+
id, method: 'codex.run', params
|
|
658
|
+
}, gate.compatibility));
|
|
659
|
+
return { id, promise };
|
|
660
|
+
}
|
|
661
|
+
});
|
|
539
662
|
const assetTransferBroker = Modules.AssetTransferBroker.create({ callPageBridge, sendBackgroundNative });
|
|
663
|
+
const { partitionUnsafeProjectPathOperations, normalizeOperationProjectPaths, getInvalidOperationProjectPath,
|
|
664
|
+
evaluateGovernedOperations, buildGovernanceSkippedApplyResult, filterSyncChangesByOperations, mergeApplyResultSkipped,
|
|
665
|
+
confirmBinaryOperations, buildAuditSummaryFromApply, summarizeOperationForAudit } = Modules.WritebackPlan.create({
|
|
666
|
+
tx, tr, normalizeSafeProjectPath, GovernanceRules, getGovernanceRulesForCurrentProject,
|
|
667
|
+
showPluginConfirm, formatOperationFiles, getAppliedEntries, getSkippedEntries
|
|
668
|
+
});
|
|
540
669
|
const writebackOrchestrator = Modules.WritebackOrchestrator.create({
|
|
541
670
|
tr,
|
|
542
671
|
tx,
|
|
@@ -577,6 +706,8 @@
|
|
|
577
706
|
filterSyncChangesByOperations,
|
|
578
707
|
writebackController,
|
|
579
708
|
assetTransferBroker,
|
|
709
|
+
stageWritebackRecovery,
|
|
710
|
+
settleWritebackRecovery,
|
|
580
711
|
writebackSettlement: WritebackSettlement,
|
|
581
712
|
compileAdapter: Modules.CompileAdapter,
|
|
582
713
|
RUN_SNAPSHOT_ZIP_TIMEOUT_MS,
|
|
@@ -674,8 +805,10 @@
|
|
|
674
805
|
window,
|
|
675
806
|
getSettingsPanelInstance: () => settingsPanelInstance,
|
|
676
807
|
getSelectedModel: () => state?.model || '', getSelectedProviderId: () => state?.providerId || 'builtin',
|
|
808
|
+
getSelectedProviderRevision: () => state?.providerRevision,
|
|
677
809
|
setSelectedProviderId: projectProviderSelection.commit,
|
|
678
810
|
confirmProviderSwitch: projectProviderSelection.confirmSwitch,
|
|
811
|
+
assertProviderSwitchAllowed: projectProviderSelection.assertSwitchAllowed,
|
|
679
812
|
refreshModelOptions: loadModelOptions, persistInputs: persistPanelInputs
|
|
680
813
|
});
|
|
681
814
|
// v1.8.1 D3: the dashboard was a one-shot render — another tab finishing a
|
|
@@ -757,6 +890,7 @@
|
|
|
757
890
|
tr,
|
|
758
891
|
tx,
|
|
759
892
|
appendPlainLog,
|
|
893
|
+
onChange: () => getComposerDraftPersistence().changed(),
|
|
760
894
|
limits: {
|
|
761
895
|
maxAttachmentBytes: MAX_COMPOSER_ATTACHMENT_BYTES,
|
|
762
896
|
maxAttachmentTotalBytes: MAX_COMPOSER_ATTACHMENT_TOTAL_BYTES,
|
|
@@ -774,6 +908,7 @@
|
|
|
774
908
|
getRunEvents: () => currentRunView?.events,
|
|
775
909
|
appendRunEvent,
|
|
776
910
|
scrollLogToBottom,
|
|
911
|
+
renderWrittenChanges: changes => Modules.WrittenChangesView.render(changes, { document, tx }),
|
|
777
912
|
onRejectedHunks: summaries => {
|
|
778
913
|
if (currentRunView && Array.isArray(summaries) && summaries.length) {
|
|
779
914
|
currentRunView.rejectedHunks = summaries;
|
|
@@ -813,9 +948,15 @@
|
|
|
813
948
|
saveState,
|
|
814
949
|
sendBackgroundNative,
|
|
815
950
|
getCurrentProjectId,
|
|
951
|
+
getScope: () => getCodexLocalSkillsCatalogScope(),
|
|
816
952
|
getSkillLoadingSettings,
|
|
817
953
|
isCodexOverleafSkillEnabled,
|
|
818
954
|
setCodexOverleafSkillEnabled,
|
|
955
|
+
isCodexLocalSkillEnabled,
|
|
956
|
+
setCodexLocalSkillEnabled,
|
|
957
|
+
getCodexLocalSkills: () => getCodexLocalSkillsCatalog(),
|
|
958
|
+
getCodexLocalSkillWarnings: () => getCodexLocalSkillWarnings(),
|
|
959
|
+
refreshCodexLocalSkills: options => primeCodexLocalSkillsCatalog(options),
|
|
819
960
|
setProjectSettingsStatus,
|
|
820
961
|
tr,
|
|
821
962
|
tx,
|
|
@@ -848,6 +989,7 @@
|
|
|
848
989
|
let storageKey = LEGACY_STORAGE_KEY;
|
|
849
990
|
let globalPreferencesController = null;
|
|
850
991
|
let currentRunView = null;
|
|
992
|
+
let runSubmissionPending = false;
|
|
851
993
|
let pendingRetryReplacement = null;
|
|
852
994
|
let runQueueScheduler = null;
|
|
853
995
|
let runInputCoordinator = null;
|
|
@@ -868,6 +1010,7 @@
|
|
|
868
1010
|
let pendingStreamRenderEvents = new Map();
|
|
869
1011
|
let storageNoticeKeys = new Set();
|
|
870
1012
|
let composerSkillInvocation = null;
|
|
1013
|
+
let composerDraftRevision = 0;
|
|
871
1014
|
let slashCodexOverleafSkills = [];
|
|
872
1015
|
let slashCodexOverleafSkillsLoaded = false;
|
|
873
1016
|
let slashCodexOverleafSkillsLoading = false;
|
|
@@ -922,6 +1065,16 @@
|
|
|
922
1065
|
rolled_back: tx('The update failed its health check, so the previous version was restored.', '更新未通过健康检查,已自动恢复上一版本。')
|
|
923
1066
|
}[state] || '')
|
|
924
1067
|
});
|
|
1068
|
+
const diagnosticsExporter = Modules.DiagnosticsExport.create({
|
|
1069
|
+
root, window, document, Blob, URL, TextEncoder, AuditRecords, StorageDb: Modules.StorageDb,
|
|
1070
|
+
CodexOverleafCompatibility, getState: () => state, getCurrentRunView: () => currentRunView,
|
|
1071
|
+
getAccountScopeId: () => cachedAccountScopeId || '',
|
|
1072
|
+
getCurrentProjectId, findRunRecord, getMirrorFreshness, getExtensionCompatibilityMetadata,
|
|
1073
|
+
getModelDiscovery, getGovernanceRulesForCurrentProject, sendBackgroundNative,
|
|
1074
|
+
fallbackNativeCompatibility, isNativeCompatibilityCompatible, getNativeCompatibilityClassification,
|
|
1075
|
+
callPageBridge, showPluginToast, tr, RUN_SNAPSHOT_ZIP_TIMEOUT_MS
|
|
1076
|
+
});
|
|
1077
|
+
|
|
925
1078
|
root[RUNTIME_INSTALLED_FLAG] = true;
|
|
926
1079
|
root[RUNTIME_STATE_KEY] = { ok: true, alreadyInstalled: false };
|
|
927
1080
|
|
|
@@ -948,7 +1101,6 @@
|
|
|
948
1101
|
if (message?.type === 'codex-overleaf/toggle-launcher') {
|
|
949
1102
|
const visible = !PanelRenderer.isLauncherVisible(panelRendererInstance);
|
|
950
1103
|
PanelRenderer.setLauncherVisible(panelRendererInstance, visible, { persist: true });
|
|
951
|
-
if (!visible) closePanel();
|
|
952
1104
|
sendResponse?.(getPanelStateResponse());
|
|
953
1105
|
return;
|
|
954
1106
|
}
|
|
@@ -969,7 +1121,7 @@
|
|
|
969
1121
|
}
|
|
970
1122
|
});
|
|
971
1123
|
|
|
972
|
-
exposeSmokeHelper();
|
|
1124
|
+
diagnosticsExporter.exposeSmokeHelper();
|
|
973
1125
|
init().catch(error => {
|
|
974
1126
|
root[RUNTIME_STATE_KEY] = {
|
|
975
1127
|
ok: false,
|
|
@@ -981,11 +1133,20 @@
|
|
|
981
1133
|
throw error;
|
|
982
1134
|
});
|
|
983
1135
|
|
|
1136
|
+
function normalizeLoadedPanelState(input) {
|
|
1137
|
+
// Shared history may belong to a live run in another tab. Loading it is
|
|
1138
|
+
// read-only; only an owner-lost journal may mark that run interrupted.
|
|
1139
|
+
return normalizePanelState(getGlobalPreferences().overlay(input), {
|
|
1140
|
+
restoreRunningRuns: Modules.StorageDb.sharedSessionsEnabled?.() !== true, recoverSettledRuns: true
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
|
|
984
1144
|
async function init() {
|
|
985
1145
|
await getGlobalPreferences().initialize();
|
|
986
1146
|
await refreshAccountScopeId();
|
|
987
1147
|
storageKey = getProjectStorageKey(LEGACY_STORAGE_KEY, window.location.href);
|
|
988
|
-
state =
|
|
1148
|
+
state = normalizeLoadedPanelState(await loadStoredState());
|
|
1149
|
+
await getRunReviewController().recoverInterruptedUndoStatuses();
|
|
989
1150
|
initializeRunQueueScheduler();
|
|
990
1151
|
recoverInterruptedRunJournals()
|
|
991
1152
|
.then(() => applyStateToPanel())
|
|
@@ -1057,7 +1218,8 @@
|
|
|
1057
1218
|
version: CodexOverleafCompatibility?.BUILD_TARGET_VERSION ||
|
|
1058
1219
|
chrome.runtime.getManifest?.().version ||
|
|
1059
1220
|
'',
|
|
1060
|
-
extensionId: getCurrentExtensionId()
|
|
1221
|
+
extensionId: getCurrentExtensionId(),
|
|
1222
|
+
installIdentity: { managed: MANAGED_INSTALLATION }
|
|
1061
1223
|
};
|
|
1062
1224
|
}
|
|
1063
1225
|
|
|
@@ -1067,129 +1229,9 @@
|
|
|
1067
1229
|
: '';
|
|
1068
1230
|
}
|
|
1069
1231
|
|
|
1070
|
-
function exposeSmokeHelper() {
|
|
1071
|
-
const helper = Object.freeze({
|
|
1072
|
-
probeNative: smokeProbeNative,
|
|
1073
|
-
probeProject: smokeProbeProject,
|
|
1074
|
-
getProjectSnapshotMetrics: smokeProbeProject
|
|
1075
|
-
});
|
|
1076
|
-
try {
|
|
1077
|
-
Object.defineProperty(globalThis, 'CodexOverleafSmoke', {
|
|
1078
|
-
configurable: true,
|
|
1079
|
-
enumerable: false,
|
|
1080
|
-
value: helper
|
|
1081
|
-
});
|
|
1082
|
-
} catch (_error) {
|
|
1083
|
-
globalThis.CodexOverleafSmoke = helper;
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
async function smokeProbeNative() {
|
|
1088
|
-
try {
|
|
1089
|
-
const params = CodexOverleafCompatibility?.buildBridgePingParams
|
|
1090
|
-
? CodexOverleafCompatibility.buildBridgePingParams(getExtensionCompatibilityMetadata())
|
|
1091
|
-
: {};
|
|
1092
|
-
const response = await sendBackgroundNative({ method: 'bridge.ping', params });
|
|
1093
|
-
const compatibility = CodexOverleafCompatibility?.evaluateNativeCompatibility
|
|
1094
|
-
? CodexOverleafCompatibility.evaluateNativeCompatibility(response, getExtensionCompatibilityMetadata())
|
|
1095
|
-
: fallbackNativeCompatibility(response);
|
|
1096
|
-
return {
|
|
1097
|
-
supported: true,
|
|
1098
|
-
ok: response?.ok === true && isNativeCompatibilityCompatible(compatibility),
|
|
1099
|
-
status: compatibility?.status || (response?.ok ? 'ok' : 'native_missing'),
|
|
1100
|
-
classification: getNativeCompatibilityClassification(compatibility),
|
|
1101
|
-
errorCode: response?.error?.code || compatibility?.status || '',
|
|
1102
|
-
nativeCompatibility: summarizeSmokeNativeCompatibility(compatibility, response)
|
|
1103
|
-
};
|
|
1104
|
-
} catch (_error) {
|
|
1105
|
-
return {
|
|
1106
|
-
supported: true,
|
|
1107
|
-
ok: false,
|
|
1108
|
-
status: 'native_probe_failed',
|
|
1109
|
-
errorCode: 'native_probe_failed'
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
1232
|
|
|
1114
|
-
async function smokeProbeProject(options = {}) {
|
|
1115
|
-
try {
|
|
1116
|
-
const project = await callPageBridge('getProjectSnapshot', {
|
|
1117
|
-
force: Boolean(options.force),
|
|
1118
|
-
preferLightweight: true,
|
|
1119
|
-
allowZipFallback: true,
|
|
1120
|
-
allowEditorNavigation: false,
|
|
1121
|
-
requireFullProject: false,
|
|
1122
|
-
includeBinaryFiles: true,
|
|
1123
|
-
includeContent: false,
|
|
1124
|
-
zipTimeoutMs: RUN_SNAPSHOT_ZIP_TIMEOUT_MS
|
|
1125
|
-
});
|
|
1126
|
-
const files = Array.isArray(project?.files) ? project.files : [];
|
|
1127
|
-
const skipped = Array.isArray(project?.capabilities?.skipped) ? project.capabilities.skipped : [];
|
|
1128
|
-
const bytes = summarizeSmokeProjectBytes(files);
|
|
1129
|
-
const ok = project?.ok !== false && files.length > 0;
|
|
1130
|
-
return {
|
|
1131
|
-
supported: true,
|
|
1132
|
-
ok,
|
|
1133
|
-
status: ok ? 'ok' : 'project_snapshot_unavailable',
|
|
1134
|
-
errorCode: ok ? '' : project?.code || 'project_snapshot_unavailable',
|
|
1135
|
-
counts: {
|
|
1136
|
-
files: files.length,
|
|
1137
|
-
skipped: skipped.length
|
|
1138
|
-
},
|
|
1139
|
-
bytes,
|
|
1140
|
-
method: project?.capabilities?.method || ''
|
|
1141
|
-
};
|
|
1142
|
-
} catch (_error) {
|
|
1143
|
-
return {
|
|
1144
|
-
supported: true,
|
|
1145
|
-
ok: false,
|
|
1146
|
-
status: 'project_probe_failed',
|
|
1147
|
-
errorCode: 'project_probe_failed',
|
|
1148
|
-
counts: {
|
|
1149
|
-
files: 0,
|
|
1150
|
-
skipped: 0
|
|
1151
|
-
},
|
|
1152
|
-
bytes: {
|
|
1153
|
-
text: 0,
|
|
1154
|
-
binary: 0
|
|
1155
|
-
}
|
|
1156
|
-
};
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
1233
|
|
|
1160
|
-
function summarizeSmokeNativeCompatibility(compatibility = {}, response = {}) {
|
|
1161
|
-
const native = compatibility?.native || response?.result || {};
|
|
1162
|
-
return {
|
|
1163
|
-
status: compatibility?.status || (response?.ok ? 'ok' : 'native_missing'),
|
|
1164
|
-
classification: getNativeCompatibilityClassification(compatibility),
|
|
1165
|
-
nativeVersion: compatibility?.nativeVersion || native.version || '',
|
|
1166
|
-
version: compatibility?.version || native.version || '',
|
|
1167
|
-
minimumNativeVersion: compatibility?.minimumNativeVersion || compatibility?.minNativeVersion || '',
|
|
1168
|
-
protocolVersion: compatibility?.protocolVersion || native.protocolVersion || '',
|
|
1169
|
-
supportedProtocol: compatibility?.supportedProtocol || native.supportedProtocol || ''
|
|
1170
|
-
};
|
|
1171
|
-
}
|
|
1172
1234
|
|
|
1173
|
-
function summarizeSmokeProjectBytes(files = []) {
|
|
1174
|
-
return files.reduce((bytes, file) => {
|
|
1175
|
-
let size = Number(file?.size || file?.byteLength || 0);
|
|
1176
|
-
if ((!Number.isFinite(size) || size <= 0) && typeof file?.content === 'string') {
|
|
1177
|
-
size = new TextEncoder().encode(file.content).byteLength;
|
|
1178
|
-
}
|
|
1179
|
-
if (!Number.isFinite(size) || size <= 0) {
|
|
1180
|
-
return bytes;
|
|
1181
|
-
}
|
|
1182
|
-
if (file?.kind === 'binary') {
|
|
1183
|
-
bytes.binary += size;
|
|
1184
|
-
} else {
|
|
1185
|
-
bytes.text += size;
|
|
1186
|
-
}
|
|
1187
|
-
return bytes;
|
|
1188
|
-
}, {
|
|
1189
|
-
text: 0,
|
|
1190
|
-
binary: 0
|
|
1191
|
-
});
|
|
1192
|
-
}
|
|
1193
1235
|
|
|
1194
1236
|
function listSeparator() {
|
|
1195
1237
|
return getLocale() === 'zh' ? '、' : ', ';
|
|
@@ -1209,7 +1251,6 @@
|
|
|
1209
1251
|
callbacks: {
|
|
1210
1252
|
onLauncherToggle: () => ensurePanelOpen(),
|
|
1211
1253
|
onClosePanel: () => closePanel(),
|
|
1212
|
-
onLauncherVisibilityChange: visible => { if (!visible) closePanel(); },
|
|
1213
1254
|
onRefresh: () => refreshProbe({ userInitiated: true }),
|
|
1214
1255
|
onNewSession: () => startNewSession(),
|
|
1215
1256
|
onChangeHistory: () => openChangeHistory(),
|
|
@@ -1285,6 +1326,7 @@
|
|
|
1285
1326
|
onOtToggleClick: handleExperimentalOtToggleClick
|
|
1286
1327
|
}
|
|
1287
1328
|
});
|
|
1329
|
+
writingStyleSettings.mount(panel);
|
|
1288
1330
|
providerSettingsCoordinator.refreshSummary().catch(() => {});
|
|
1289
1331
|
|
|
1290
1332
|
sessionPanelInstance = SessionPanel.create({
|
|
@@ -1321,6 +1363,7 @@
|
|
|
1321
1363
|
onTaskKeydown: handleTaskInputKeydown,
|
|
1322
1364
|
onTaskInput: handleTaskInput,
|
|
1323
1365
|
onSlashMenuClick: handleSlashMenuClick,
|
|
1366
|
+
onMenuLayoutChange: updateSlashMenuLayout,
|
|
1324
1367
|
onClearSkillInvocation: clearComposerSkillInvocation,
|
|
1325
1368
|
onAddContext: toggleContextTray,
|
|
1326
1369
|
onAttachClick: () => panel.querySelector('[data-attach-input]')?.click(),
|
|
@@ -1347,34 +1390,87 @@
|
|
|
1347
1390
|
renderContextSelection();
|
|
1348
1391
|
scheduleMirrorPrefetch({ reason: 'cold-start', delayMs: 2500 });
|
|
1349
1392
|
primeCodexOverleafSkillsCatalog().catch(() => { /* non-blocking */ });
|
|
1393
|
+
primeCodexLocalSkillsCatalog().catch(() => { /* retry through settings, slash, or submission */ });
|
|
1350
1394
|
}
|
|
1351
1395
|
|
|
1352
1396
|
PanelRenderer.setVisible(panel, true);
|
|
1353
1397
|
}
|
|
1354
1398
|
|
|
1355
|
-
//
|
|
1356
|
-
//
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
// the Skills page. Fire-and-forget on panel open; only fetches when the
|
|
1360
|
-
// catalog is empty.
|
|
1361
|
-
async function primeCodexOverleafSkillsCatalog() {
|
|
1399
|
+
// Panel prefetch and first submission share the same official catalog read.
|
|
1400
|
+
// A required read fails through the existing task-start error presentation.
|
|
1401
|
+
let codexOverleafSkillsCatalogPromise = null;
|
|
1402
|
+
async function primeCodexOverleafSkillsCatalog({ required = false } = {}) {
|
|
1362
1403
|
if (Array.isArray(state?.codexOverleafSkills) && state.codexOverleafSkills.length) {
|
|
1363
|
-
return;
|
|
1364
|
-
}
|
|
1365
|
-
if (getSkillLoadingSettings().loadCodexOverleafSkills === false)
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1404
|
+
return state.codexOverleafSkills;
|
|
1405
|
+
}
|
|
1406
|
+
if (!required && getSkillLoadingSettings().loadCodexOverleafSkills === false) return [];
|
|
1407
|
+
if (!codexOverleafSkillsCatalogPromise) {
|
|
1408
|
+
codexOverleafSkillsCatalogPromise = sendBackgroundNative({
|
|
1409
|
+
method: 'skills.list', params: { scope: 'codex-overleaf' }
|
|
1410
|
+
}).then(response => {
|
|
1411
|
+
const skills = Array.isArray(response?.result?.skills) ? response.result.skills : [];
|
|
1412
|
+
if (!response?.ok || !skills.length) throw new Error(tx(
|
|
1413
|
+
'Could not load Codex Overleaf skills. Try again.', '无法加载 Codex Overleaf 技能,请重试。'));
|
|
1414
|
+
state = { ...state, codexOverleafSkills: skills };
|
|
1415
|
+
saveStateSoon();
|
|
1416
|
+
return skills;
|
|
1417
|
+
}).finally(() => { codexOverleafSkillsCatalogPromise = null; });
|
|
1418
|
+
}
|
|
1419
|
+
return codexOverleafSkillsCatalogPromise;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// Local metadata stays in memory. A stale page/account read cannot become the
|
|
1423
|
+
// next page's selected catalog, and an empty successful directory is valid.
|
|
1424
|
+
let codexLocalSkillsCatalog = null;
|
|
1425
|
+
let codexLocalSkillsWarnings = [];
|
|
1426
|
+
let codexLocalSkillsCatalogScope = '';
|
|
1427
|
+
let codexLocalSkillsCatalogRequest = null;
|
|
1428
|
+
function getCodexLocalSkillsCatalogScope() {
|
|
1429
|
+
return [cachedAccountScopeId, getCurrentProjectId(), spaRouteGeneration].join(':');
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
function getCodexLocalSkillsCatalog() {
|
|
1433
|
+
return codexLocalSkillsCatalogScope === getCodexLocalSkillsCatalogScope() ? codexLocalSkillsCatalog : null;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
function getCodexLocalSkillWarnings() {
|
|
1437
|
+
return codexLocalSkillsCatalogScope === getCodexLocalSkillsCatalogScope() ? codexLocalSkillsWarnings : [];
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
async function primeCodexLocalSkillsCatalog({ required = false, force = false } = {}) {
|
|
1441
|
+
const scope = getCodexLocalSkillsCatalogScope();
|
|
1442
|
+
if (!required && !force && getSkillLoadingSettings().loadCodexLocalSkills === false) return [];
|
|
1443
|
+
if (!force && codexLocalSkillsCatalogScope === scope && Array.isArray(codexLocalSkillsCatalog)) return codexLocalSkillsCatalog;
|
|
1444
|
+
if (codexLocalSkillsCatalogRequest?.scope === scope) return codexLocalSkillsCatalogRequest.promise;
|
|
1445
|
+
const request = { scope, promise: null };
|
|
1446
|
+
request.promise = sendBackgroundNative({ method: 'skills.list', params: { scope: 'codex-local' } }).then(response => {
|
|
1447
|
+
if (!response?.ok || !Array.isArray(response.result?.skills)) throw new Error(tx(
|
|
1448
|
+
'Could not load local Codex skills. Try again.', '无法加载 Codex 本地技能,请重试。'));
|
|
1449
|
+
if (scope !== getCodexLocalSkillsCatalogScope()) throw new Error(tx(
|
|
1450
|
+
'The active project changed while loading skills. Try again.', '加载技能时活动项目已改变,请重试。'));
|
|
1451
|
+
const seen = new Set();
|
|
1452
|
+
const skills = response.result.skills.filter(skill => {
|
|
1453
|
+
const id = String(skill?.id || '');
|
|
1454
|
+
if (!isSafeSkillId(id) || seen.has(id)) return false;
|
|
1455
|
+
seen.add(id); return true;
|
|
1456
|
+
});
|
|
1457
|
+
codexLocalSkillsCatalog = skills;
|
|
1458
|
+
codexLocalSkillsWarnings = (Array.isArray(response.result.discoveryWarnings) ? response.result.discoveryWarnings : [])
|
|
1459
|
+
.filter(warning => warning?.code === 'codex_local_skills_skipped' && Number.isSafeInteger(warning.count) && warning.count > 0)
|
|
1460
|
+
.map(warning => ({ code: warning.code, count: warning.count }));
|
|
1461
|
+
codexLocalSkillsCatalogScope = scope;
|
|
1462
|
+
updateSkillsEntrySummary();
|
|
1463
|
+
updateSlashMenuForTaskInput();
|
|
1464
|
+
return skills;
|
|
1465
|
+
}).catch(() => {
|
|
1466
|
+
if (scope !== getCodexLocalSkillsCatalogScope()) throw new Error(tx(
|
|
1467
|
+
'The active project changed while loading skills. Try again.', '加载技能时活动项目已改变,请重试。'));
|
|
1468
|
+
throw new Error(tx('Could not load local Codex skills. Try again.', '无法加载 Codex 本地技能,请重试。'));
|
|
1469
|
+
}).finally(() => {
|
|
1470
|
+
if (codexLocalSkillsCatalogRequest === request) codexLocalSkillsCatalogRequest = null;
|
|
1371
1471
|
});
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
return;
|
|
1375
|
-
}
|
|
1376
|
-
state = { ...state, codexOverleafSkills: skills };
|
|
1377
|
-
saveStateSoon();
|
|
1472
|
+
codexLocalSkillsCatalogRequest = request;
|
|
1473
|
+
return request.promise;
|
|
1378
1474
|
}
|
|
1379
1475
|
|
|
1380
1476
|
function closePanel() {
|
|
@@ -1457,6 +1553,10 @@
|
|
|
1457
1553
|
panel.querySelectorAll('[data-i18n]').forEach(element => {
|
|
1458
1554
|
element.textContent = tr(element.dataset.i18n);
|
|
1459
1555
|
});
|
|
1556
|
+
panel.querySelectorAll('[data-i18n-aria-label]').forEach(element => {
|
|
1557
|
+
element.setAttribute('aria-label', tr(element.dataset.i18nAriaLabel));
|
|
1558
|
+
});
|
|
1559
|
+
panelRendererInstance?.setLocale?.({ launcher: tx('Show or hide Codex panel', '显示或隐藏 Codex 边栏'), close: tx('Close Codex panel', '关闭 Codex 边栏') });
|
|
1460
1560
|
|
|
1461
1561
|
setElementTitleAndAria('[data-panel-resize-handle]', tr('resizePanel'), tr('resizePanel'));
|
|
1462
1562
|
setElementTitleAndAria('[data-refresh]', tr('refreshProbe'), tr('refreshProbe'));
|
|
@@ -1476,7 +1576,7 @@
|
|
|
1476
1576
|
setElementTitleAndAria('[data-context-refresh]', tr('refreshFileList'), tr('refreshFileList'));
|
|
1477
1577
|
setElementTitleAndAria('[data-reasoning]', tr('reasoningLabel'), tr('reasoningLabel'));
|
|
1478
1578
|
setElementTitleAndAria('[data-speed]', tr('speedLabel'), tr('speedLabel'));
|
|
1479
|
-
|
|
1579
|
+
syncComposerSendAvailability();
|
|
1480
1580
|
setElementTitleAndAria('[data-stop-run]', tr('cancelRun'), tr('cancelRun'));
|
|
1481
1581
|
|
|
1482
1582
|
const actions = panel.querySelector('.codex-vscode-head-actions');
|
|
@@ -1525,10 +1625,8 @@
|
|
|
1525
1625
|
if (contextStatus && !contextStatus.dataset.customStatus) {
|
|
1526
1626
|
contextStatus.textContent = tr('contextStatus');
|
|
1527
1627
|
}
|
|
1528
|
-
|
|
1529
|
-
if (
|
|
1530
|
-
emptyRunLabel.textContent = tr('emptyRunLabel');
|
|
1531
|
-
}
|
|
1628
|
+
// Re-render rather than patch text: the first div is the animated mark.
|
|
1629
|
+
if (arguments[0]?.reprojectDynamic !== false) runTimelineView.refreshLocale?.();
|
|
1532
1630
|
|
|
1533
1631
|
syncModeControls();
|
|
1534
1632
|
updateOtStatusDisplay();
|
|
@@ -1537,16 +1635,19 @@
|
|
|
1537
1635
|
updateModelDisplay();
|
|
1538
1636
|
renderSessionList();
|
|
1539
1637
|
renderContextSelection();
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
probeStatus.textContent = userInitiated && !failed ? tr('refreshProbeDone', { status: text }) : text;
|
|
1546
|
-
if (!failed) updateExistingProbeNotice(probe);
|
|
1547
|
-
}
|
|
1638
|
+
renderContextSummary();
|
|
1639
|
+
renderPendingInputs();
|
|
1640
|
+
renderComposerAttachments();
|
|
1641
|
+
renderComposerSkillInvocation();
|
|
1642
|
+
writingStyleSettings.sync();
|
|
1548
1643
|
updateSkillsEntrySummary();
|
|
1644
|
+
localSkillsPanel.refreshLocale?.();
|
|
1549
1645
|
providerSettingsCoordinator.renderSummary();
|
|
1646
|
+
providerSettingsCoordinator.refreshLocale?.();
|
|
1647
|
+
settingsPanelInstance?.refreshLocale?.();
|
|
1648
|
+
Modules.UpdateNotice?.refreshLocale?.();
|
|
1649
|
+
DiagnosticsPanel.refreshLocale?.(diagnosticsPanelInstance, diagnosticsController.refreshLocale?.());
|
|
1650
|
+
if (!panel.querySelector('[data-context-tray]')?.hidden) renderContextFiles();
|
|
1550
1651
|
if (settingsPanelInstance?.container?.querySelector('[data-storage-card]')?.open) refreshStorageUsageSummary();
|
|
1551
1652
|
}
|
|
1552
1653
|
|
|
@@ -1603,88 +1704,14 @@
|
|
|
1603
1704
|
}
|
|
1604
1705
|
|
|
1605
1706
|
|
|
1606
|
-
async function exportDiagnosticsBundle() {
|
|
1607
|
-
if (!AuditRecords?.buildDiagnosticBundle) {
|
|
1608
|
-
throw new Error('Audit diagnostics helper is unavailable');
|
|
1609
|
-
}
|
|
1610
|
-
const [auditLogs, mirror, nativeDiagnostics] = await Promise.all([
|
|
1611
|
-
getRecentAuditLogsForCurrentProject(),
|
|
1612
|
-
getMirrorFreshness().catch(error => ({ status: 'unavailable', errorCode: error.message })),
|
|
1613
|
-
getNativeDiagnosticsSummaryForBundle()
|
|
1614
|
-
]);
|
|
1615
|
-
const bundle = AuditRecords.buildDiagnosticBundle({
|
|
1616
|
-
excludeContent: true,
|
|
1617
|
-
compatibility: {
|
|
1618
|
-
extension: getExtensionCompatibilityMetadata(),
|
|
1619
|
-
modelDiscovery: getModelDiscovery()
|
|
1620
|
-
},
|
|
1621
|
-
platform: nativeDiagnostics.platform,
|
|
1622
|
-
nativeEnvironment: nativeDiagnostics.nativeEnvironment,
|
|
1623
|
-
mirror,
|
|
1624
|
-
auditLogs,
|
|
1625
|
-
run: currentRunView ? {
|
|
1626
|
-
id: currentRunView.recordId || '',
|
|
1627
|
-
status: 'running'
|
|
1628
|
-
} : {},
|
|
1629
|
-
governance: getGovernanceRulesForCurrentProject(),
|
|
1630
|
-
projectId: getCurrentProjectId()
|
|
1631
|
-
});
|
|
1632
|
-
const blob = new Blob([JSON.stringify(bundle, null, 2)], { type: 'application/json' });
|
|
1633
|
-
const url = URL.createObjectURL(blob);
|
|
1634
|
-
const link = document.createElement('a');
|
|
1635
|
-
link.href = url;
|
|
1636
|
-
link.download = `codex-overleaf-diagnostics-${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
|
|
1637
|
-
document.body.append(link);
|
|
1638
|
-
link.click();
|
|
1639
|
-
link.remove();
|
|
1640
|
-
window.setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
1641
|
-
showPluginToast(tr('diagnosticsExportDone'), { status: 'completed' });
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
async function getNativeDiagnosticsSummaryForBundle() {
|
|
1645
|
-
try {
|
|
1646
|
-
const params = CodexOverleafCompatibility?.buildBridgePingParams
|
|
1647
|
-
? CodexOverleafCompatibility.buildBridgePingParams(getExtensionCompatibilityMetadata())
|
|
1648
|
-
: {};
|
|
1649
|
-
const response = await sendBackgroundNative({ method: 'bridge.ping', params });
|
|
1650
|
-
if (!response?.ok) {
|
|
1651
|
-
const errorCode = response?.error?.code || 'native_unavailable';
|
|
1652
|
-
return {
|
|
1653
|
-
platform: { status: 'unavailable', errorCode },
|
|
1654
|
-
nativeEnvironment: { status: 'unavailable', errorCode }
|
|
1655
|
-
};
|
|
1656
|
-
}
|
|
1657
|
-
return {
|
|
1658
|
-
platform: {
|
|
1659
|
-
host: response.result?.host || '',
|
|
1660
|
-
platform: response.result?.platform || '',
|
|
1661
|
-
version: response.result?.version || '',
|
|
1662
|
-
protocolVersion: response.result?.protocolVersion || ''
|
|
1663
|
-
},
|
|
1664
|
-
nativeEnvironment: response.result?.environment || {}
|
|
1665
|
-
};
|
|
1666
|
-
} catch (error) {
|
|
1667
|
-
const errorCode = error?.message || 'native_unavailable';
|
|
1668
|
-
return {
|
|
1669
|
-
platform: { status: 'unavailable', errorCode },
|
|
1670
|
-
nativeEnvironment: { status: 'unavailable', errorCode }
|
|
1671
|
-
};
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
1707
|
|
|
1675
|
-
async function getRecentAuditLogsForCurrentProject(limit = 12) {
|
|
1676
|
-
const StorageDb = Modules.StorageDb;
|
|
1677
|
-
if (!StorageDb?.getAllByIndex) {
|
|
1678
|
-
return [];
|
|
1679
|
-
}
|
|
1680
|
-
const records = await StorageDb.getAllByIndex('auditLogs', 'projectId', getCurrentProjectId());
|
|
1681
|
-
return (records || [])
|
|
1682
|
-
.slice()
|
|
1683
|
-
.sort((left, right) => String(right.createdAt || '').localeCompare(String(left.createdAt || '')))
|
|
1684
|
-
.slice(0, limit);
|
|
1685
|
-
}
|
|
1686
1708
|
|
|
1687
1709
|
|
|
1710
|
+
|
|
1711
|
+
function exportDiagnosticsBundle() {
|
|
1712
|
+
return diagnosticsExporter.exportDiagnosticsBundle();
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1688
1715
|
function isContextTrayClickTarget(target) {
|
|
1689
1716
|
return contextTrayController.isContextTrayClickTarget(target);
|
|
1690
1717
|
}
|
|
@@ -1780,15 +1807,7 @@
|
|
|
1780
1807
|
function resetContextProject() { return contextTrayController.resetContextProject(); }
|
|
1781
1808
|
|
|
1782
1809
|
async function runTask(options = {}) {
|
|
1783
|
-
//
|
|
1784
|
-
// turn (v1.7.5) BEFORE startRunView creates the new run view: the mirror's
|
|
1785
|
-
// wrap-up events must land in the run that produced them, not the new
|
|
1786
|
-
// one, and mirror.sync landing mid-run would swap the local baseline
|
|
1787
|
-
// under Codex.
|
|
1788
|
-
const pendingMirror = getPendingMirrorRefresh?.();
|
|
1789
|
-
if (pendingMirror) {
|
|
1790
|
-
await pendingMirror.catch(() => {});
|
|
1791
|
-
}
|
|
1810
|
+
// Capture the click before any preparation can yield to another draft/session.
|
|
1792
1811
|
readPanelInputs();
|
|
1793
1812
|
const queuedInput = options.queuedInput || null;
|
|
1794
1813
|
const task = String(queuedInput?.text || state.task || '').trim();
|
|
@@ -1796,6 +1815,12 @@
|
|
|
1796
1815
|
appendLog(tx('Enter a task first.', '请先输入任务。'));
|
|
1797
1816
|
return;
|
|
1798
1817
|
}
|
|
1818
|
+
const submittedSessionId = state.activeSessionId;
|
|
1819
|
+
const submittedSession = runController.captureSessionContext(state);
|
|
1820
|
+
const submittedThreadId = getActiveSession(state)?.codexThreadId || '';
|
|
1821
|
+
const submittedDraft = state.task;
|
|
1822
|
+
const submittedRetryReplacement = pendingRetryReplacement;
|
|
1823
|
+
const submittedWritingStyleScope = { accountScopeId: cachedAccountScopeId, projectId: getCurrentProjectId() };
|
|
1799
1824
|
const submittedPanelState = {
|
|
1800
1825
|
mode: state.mode,
|
|
1801
1826
|
providerId: state.providerId,
|
|
@@ -1804,13 +1829,50 @@
|
|
|
1804
1829
|
speedTier: state.speedTier,
|
|
1805
1830
|
autoRecompile: state.autoRecompile !== false,
|
|
1806
1831
|
requireReviewing: state.requireReviewing === true,
|
|
1807
|
-
focusFiles: getActiveFocusFiles()
|
|
1832
|
+
focusFiles: getActiveFocusFiles(),
|
|
1833
|
+
selectionContext: selectionContextView.capture()
|
|
1808
1834
|
};
|
|
1809
|
-
const
|
|
1810
|
-
|
|
1811
|
-
const
|
|
1812
|
-
const
|
|
1835
|
+
const queuedContext = queuedInput
|
|
1836
|
+
? RunInputQueue.normalizeSubmissionContext(queuedInput.payload?.submissionContext) : null;
|
|
1837
|
+
const submittedCustomInstructions = queuedContext ? queuedContext.customInstructions : getCustomInstructionsForCurrentProject();
|
|
1838
|
+
const submittedSkillLoadingSettings = queuedContext ? queuedContext.skillLoadingSettings : getSkillLoadingSettings();
|
|
1839
|
+
const submittedAttachments = queuedInput ? [] : getComposerAttachmentsForRun();
|
|
1840
|
+
const submittedSkillInvocation = queuedContext ? queuedContext.skillInvocation : getComposerSkillInvocationForRun();
|
|
1841
|
+
let submittedEnabledSkillIds = queuedContext ? queuedContext.enabledCodexOverleafSkillIds : getEnabledCodexOverleafSkillIds();
|
|
1842
|
+
const submittedSkillEnabled = { ...state.codexOverleafSkillEnabled };
|
|
1843
|
+
const submittedLocalSkillEnabled = { ...state.codexLocalSkillEnabled };
|
|
1844
|
+
const submittedSelectionRevision = selectionContextView.getRevision?.();
|
|
1845
|
+
let submittedEnabledLocalSkillIds = queuedContext ? queuedContext.enabledCodexLocalSkillIds : [];
|
|
1846
|
+
if (!queuedInput && submittedSkillLoadingSettings.loadCodexLocalSkills !== false) {
|
|
1847
|
+
const localSkills = await primeCodexLocalSkillsCatalog({ required: true, force: submittedSkillInvocation?.scope === 'codex-local' });
|
|
1848
|
+
submittedEnabledLocalSkillIds = localSkills.filter(skill => Object.hasOwn(submittedLocalSkillEnabled, skill.id)
|
|
1849
|
+
? submittedLocalSkillEnabled[skill.id] !== false : skill.enabled !== false).map(skill => skill.id);
|
|
1850
|
+
}
|
|
1851
|
+
if (!queuedInput && submittedSkillInvocation?.scope === 'codex-local'
|
|
1852
|
+
&& (submittedSkillLoadingSettings.loadCodexLocalSkills === false || !submittedEnabledLocalSkillIds.includes(submittedSkillInvocation.id))) {
|
|
1853
|
+
throw new Error(tx('The selected local Codex skill is unavailable or disabled. Select an enabled skill and try again.',
|
|
1854
|
+
'所选 Codex 本地技能不可用或已关闭,请选择已启用的技能后重试。'));
|
|
1855
|
+
}
|
|
1856
|
+
if (!queuedInput && submittedSkillLoadingSettings.loadCodexOverleafSkills !== false) {
|
|
1857
|
+
const skills = await primeCodexOverleafSkillsCatalog({ required: true });
|
|
1858
|
+
submittedEnabledSkillIds = skills.map(skill => String(skill?.id || '').trim())
|
|
1859
|
+
.filter(id => id && submittedSkillEnabled[id] !== false);
|
|
1860
|
+
}
|
|
1861
|
+
// Keep the previous mirror's completion events on its own run.
|
|
1862
|
+
const pendingMirror = getPendingMirrorRefresh?.();
|
|
1863
|
+
if (pendingMirror) await pendingMirror.catch(() => {});
|
|
1813
1864
|
await providerSettingsCoordinator.ensureLoaded();
|
|
1865
|
+
if (!queuedInput) await writingStyleSettings.ensureLoaded(true);
|
|
1866
|
+
if (submittedWritingStyleScope.accountScopeId !== cachedAccountScopeId
|
|
1867
|
+
|| submittedWritingStyleScope.projectId !== getCurrentProjectId()) {
|
|
1868
|
+
throw new Error(tx('The active project changed before submission. Submit the task again.',
|
|
1869
|
+
'提交前活动项目已改变,请重新提交任务。'));
|
|
1870
|
+
}
|
|
1871
|
+
if (!findSessionById(submittedSessionId) || currentRunView) {
|
|
1872
|
+
throw new Error(tx('The submitted session changed before submission. Submit the task again.',
|
|
1873
|
+
'提交所属会话已改变,请重新提交任务。'));
|
|
1874
|
+
}
|
|
1875
|
+
submittedPanelState.writingStyle = queuedInput ? null : writingStyleSettings.capture();
|
|
1814
1876
|
const executionSnapshot = queuedInput
|
|
1815
1877
|
? RunExecutionSnapshot.resolveForExecution(
|
|
1816
1878
|
RunExecutionSnapshot.fromQueuePayload(queuedInput.payload),
|
|
@@ -1821,9 +1883,9 @@
|
|
|
1821
1883
|
const submittedMode = executionSnapshot.mode;
|
|
1822
1884
|
const submittedRequireReviewing = executionSnapshot.requireReviewing === true;
|
|
1823
1885
|
const replaceRunId = queuedInput ? '' : runController.resolveRetryReplacement({
|
|
1824
|
-
candidate:
|
|
1825
|
-
activeSessionId:
|
|
1826
|
-
runs:
|
|
1886
|
+
candidate: submittedRetryReplacement,
|
|
1887
|
+
activeSessionId: submittedSessionId,
|
|
1888
|
+
runs: findSessionById(submittedSessionId)?.runs,
|
|
1827
1889
|
projectRunSettlement: run => WritebackSettlement.projectRunSettlement(run)
|
|
1828
1890
|
});
|
|
1829
1891
|
|
|
@@ -1831,6 +1893,9 @@
|
|
|
1831
1893
|
runCancellationController = new AbortController();
|
|
1832
1894
|
setRunning(true);
|
|
1833
1895
|
currentRunView = startRunView({
|
|
1896
|
+
sessionId: submittedSessionId,
|
|
1897
|
+
sessionContext: submittedSession,
|
|
1898
|
+
codexThreadId: submittedThreadId,
|
|
1834
1899
|
task,
|
|
1835
1900
|
mode: submittedMode,
|
|
1836
1901
|
model: executionSnapshot.model,
|
|
@@ -1842,9 +1907,10 @@
|
|
|
1842
1907
|
queueItemId: queuedInput?.id || '',
|
|
1843
1908
|
replaceRunId
|
|
1844
1909
|
});
|
|
1845
|
-
if (!queuedInput) pendingRetryReplacement = null;
|
|
1910
|
+
if (!queuedInput && pendingRetryReplacement === submittedRetryReplacement) pendingRetryReplacement = null;
|
|
1846
1911
|
const runSessionId = currentRunView.sessionId;
|
|
1847
1912
|
let runAuditDraft = null;
|
|
1913
|
+
let localSkillRestorePoint = null;
|
|
1848
1914
|
try {
|
|
1849
1915
|
if (queuedInput?.id) {
|
|
1850
1916
|
await runQueueScheduler?.markExecuting(runSessionId, queuedInput.id, currentRunView.recordId);
|
|
@@ -1856,14 +1922,32 @@
|
|
|
1856
1922
|
focusFiles: executionSnapshot.focusFiles,
|
|
1857
1923
|
executionSnapshot
|
|
1858
1924
|
}));
|
|
1925
|
+
if (submittedWritingStyleScope.accountScopeId !== cachedAccountScopeId
|
|
1926
|
+
|| submittedWritingStyleScope.projectId !== getCurrentProjectId()) {
|
|
1927
|
+
throw Object.assign(createRunCancellationError(), {
|
|
1928
|
+
message: 'The active project or account changed before dispatch.'
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1859
1931
|
announceCrossTabRunStart();
|
|
1860
1932
|
// Binary attachments belong to this submitted turn. The run record has
|
|
1861
1933
|
// already captured their snapshots, so clear them from the composer
|
|
1862
1934
|
// together with the submitted text. Persistent @context remains intact.
|
|
1863
|
-
clearTaskComposer({ keepAttachments: false
|
|
1935
|
+
const submittedComposerCleared = !queuedInput && clearTaskComposer({ keepAttachments: false, submission: {
|
|
1936
|
+
...submittedWritingStyleScope,
|
|
1937
|
+
sessionId: submittedSessionId, task: submittedDraft, attachments: submittedAttachments,
|
|
1938
|
+
skillInvocation: submittedSkillInvocation, selectionContext: submittedPanelState.selectionContext,
|
|
1939
|
+
selectionRevision: submittedSelectionRevision
|
|
1940
|
+
} });
|
|
1941
|
+
if (submittedComposerCleared && submittedSkillInvocation?.scope === 'codex-local'
|
|
1942
|
+
&& state.activeSessionId === submittedSessionId && !panel?.querySelector('[data-task]')?.value
|
|
1943
|
+
&& !getComposerAttachmentsForRun().length && !composerAttachmentController.getAttachmentByteUsage?.().pending
|
|
1944
|
+
&& !getComposerSkillInvocationForRun() && !selectionContextView.capture()) {
|
|
1945
|
+
localSkillRestorePoint = { draftRevision: composerDraftRevision,
|
|
1946
|
+
attachmentRevision: composerAttachmentController.getRevision?.(), selectionRevision: selectionContextView.getRevision?.() };
|
|
1947
|
+
}
|
|
1864
1948
|
syncComposerSendAvailability(true);
|
|
1865
1949
|
appendRunEvent({
|
|
1866
|
-
title: submittedSkillInvocation?.id === 'skill-installer'
|
|
1950
|
+
title: submittedSkillInvocation?.id === 'skill-installer' && !submittedSkillInvocation.scope
|
|
1867
1951
|
? tx('I will use the Codex skill installer for this request.', '我会用 Codex skill installer 处理这个请求。')
|
|
1868
1952
|
: tx('I will first understand your request, then inspect the relevant Overleaf files.', '我会先理解你的请求,再检查相关 Overleaf 文件。'),
|
|
1869
1953
|
status: 'running',
|
|
@@ -1878,7 +1962,7 @@
|
|
|
1878
1962
|
'@context': formatContextItems(executionSnapshot.focusFiles)
|
|
1879
1963
|
}
|
|
1880
1964
|
});
|
|
1881
|
-
if (submittedSkillInvocation?.id === 'skill-installer') {
|
|
1965
|
+
if (submittedSkillInvocation?.id === 'skill-installer' && !submittedSkillInvocation.scope) {
|
|
1882
1966
|
await awaitRunStep(runSkillInstallerTask({
|
|
1883
1967
|
task,
|
|
1884
1968
|
runSessionId,
|
|
@@ -1886,6 +1970,8 @@
|
|
|
1886
1970
|
submittedMode,
|
|
1887
1971
|
submittedCustomInstructions,
|
|
1888
1972
|
submittedSkillLoadingSettings,
|
|
1973
|
+
submittedEnabledLocalSkillIds,
|
|
1974
|
+
submittedEnabledSkillIds,
|
|
1889
1975
|
submittedAttachments,
|
|
1890
1976
|
submittedSkillInvocation,
|
|
1891
1977
|
executionSnapshot
|
|
@@ -1946,6 +2032,7 @@
|
|
|
1946
2032
|
project,
|
|
1947
2033
|
snapshotWarnings,
|
|
1948
2034
|
focusFiles,
|
|
2035
|
+
focusFilesAreLiteral: true,
|
|
1949
2036
|
isUsableProjectFileContent: Modules.ProjectFiles.isUsableProjectFileContent
|
|
1950
2037
|
});
|
|
1951
2038
|
let restrictToFocusFiles = runController.shouldRestrictWritebackToFocus({ focusFiles, focusedPartialSnapshot });
|
|
@@ -2014,8 +2101,7 @@
|
|
|
2014
2101
|
}
|
|
2015
2102
|
}
|
|
2016
2103
|
|
|
2017
|
-
const
|
|
2018
|
-
const codexThreadId = activeSession?.codexThreadId || '';
|
|
2104
|
+
const codexThreadId = submittedThreadId;
|
|
2019
2105
|
|
|
2020
2106
|
// Resolve @compile-log if mentioned in task
|
|
2021
2107
|
let compileLogContext = null;
|
|
@@ -2043,14 +2129,14 @@
|
|
|
2043
2129
|
}
|
|
2044
2130
|
});
|
|
2045
2131
|
appendRunEvent({
|
|
2046
|
-
title: tx(`Compile log unavailable: ${compileLogContext.reason}`, `编译日志不可用:${compileLogContext.reason}`),
|
|
2132
|
+
title: tx(`Compile log unavailable: ${Modules.CompileAdapter.localizeCompileReason(compileLogContext.reason, getLocale())}`, `编译日志不可用:${Modules.CompileAdapter.localizeCompileReason(compileLogContext.reason, getLocale())}`),
|
|
2047
2133
|
status: 'failed',
|
|
2048
2134
|
failure: contextFailure
|
|
2049
2135
|
});
|
|
2050
2136
|
}
|
|
2051
2137
|
}
|
|
2052
2138
|
|
|
2053
|
-
|
|
2139
|
+
let sensitiveFindings = await awaitRunStep(runSensitivePreflight({
|
|
2054
2140
|
task,
|
|
2055
2141
|
project,
|
|
2056
2142
|
rules: getGovernanceRulesForCurrentProject(),
|
|
@@ -2087,6 +2173,8 @@
|
|
|
2087
2173
|
compileLogContext,
|
|
2088
2174
|
customInstructions: submittedCustomInstructions,
|
|
2089
2175
|
skillLoadingSettings: submittedSkillLoadingSettings,
|
|
2176
|
+
enabledCodexOverleafSkillIds: submittedEnabledSkillIds,
|
|
2177
|
+
enabledCodexLocalSkillIds: submittedEnabledLocalSkillIds,
|
|
2090
2178
|
attachments: submittedAttachments,
|
|
2091
2179
|
skillInvocation: submittedSkillInvocation,
|
|
2092
2180
|
submittedMode
|
|
@@ -2108,6 +2196,25 @@
|
|
|
2108
2196
|
fileOverlays = null;
|
|
2109
2197
|
otWarmStart = false;
|
|
2110
2198
|
restrictToFocusFiles = staleRetry.restrictToFocusFiles;
|
|
2199
|
+
const retryFindings = await awaitRunStep(runSensitivePreflight({
|
|
2200
|
+
task, project, rules: getGovernanceRulesForCurrentProject(), useExistingMirror: false
|
|
2201
|
+
}));
|
|
2202
|
+
if (retryFindings.blocked) {
|
|
2203
|
+
await finalizeAuditRecord(runAuditDraft, {
|
|
2204
|
+
resultStatus: 'blocked_sensitive', sensitiveFindings: retryFindings.findings,
|
|
2205
|
+
blockedFiles: retryFindings.findings.map(finding => ({
|
|
2206
|
+
path: finding.path || finding.source || 'task', reason: finding.detectorId || 'sensitive'
|
|
2207
|
+
}))
|
|
2208
|
+
});
|
|
2209
|
+
await finishRunView(tx('Blocked: sensitive content review required', '已阻止:需要处理敏感内容'), 'failed');
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
if (retryFindings.findings.length) {
|
|
2213
|
+
sensitiveFindings = {
|
|
2214
|
+
blocked: false, findings: [...sensitiveFindings.findings, ...retryFindings.findings]
|
|
2215
|
+
};
|
|
2216
|
+
runAuditDraft = await updateAuditSensitiveFindings(runAuditDraft, sensitiveFindings.findings);
|
|
2217
|
+
}
|
|
2111
2218
|
response = await awaitRunStep(sendTrackedCodexRun(buildCodexRunParams({
|
|
2112
2219
|
task,
|
|
2113
2220
|
project,
|
|
@@ -2120,6 +2227,8 @@
|
|
|
2120
2227
|
compileLogContext,
|
|
2121
2228
|
customInstructions: submittedCustomInstructions,
|
|
2122
2229
|
skillLoadingSettings: submittedSkillLoadingSettings,
|
|
2230
|
+
enabledCodexOverleafSkillIds: submittedEnabledSkillIds,
|
|
2231
|
+
enabledCodexLocalSkillIds: submittedEnabledLocalSkillIds,
|
|
2123
2232
|
attachments: submittedAttachments,
|
|
2124
2233
|
skillInvocation: submittedSkillInvocation,
|
|
2125
2234
|
submittedMode
|
|
@@ -2151,14 +2260,17 @@
|
|
|
2151
2260
|
compileLogContext,
|
|
2152
2261
|
customInstructions: submittedCustomInstructions,
|
|
2153
2262
|
skillLoadingSettings: submittedSkillLoadingSettings,
|
|
2263
|
+
enabledCodexOverleafSkillIds: submittedEnabledSkillIds,
|
|
2264
|
+
enabledCodexLocalSkillIds: submittedEnabledLocalSkillIds,
|
|
2154
2265
|
attachments: submittedAttachments,
|
|
2155
2266
|
skillInvocation: submittedSkillInvocation,
|
|
2156
2267
|
submittedMode
|
|
2157
2268
|
}));
|
|
2158
2269
|
} else {
|
|
2159
|
-
appendRunEvent({ title: tx('Cancelled: user chose not to create a new thread.', '已取消:用户选择不新建线程。'), status: '
|
|
2160
|
-
|
|
2161
|
-
await
|
|
2270
|
+
appendRunEvent({ title: tx('Cancelled: user chose not to create a new thread.', '已取消:用户选择不新建线程。'), status: 'info',
|
|
2271
|
+
activity: { v: 1, kind: 'lifecycle', state: 'cancelled', target: 'cancelled' } });
|
|
2272
|
+
await finalizeAuditRecord(runAuditDraft, { resultStatus: 'cancelled' });
|
|
2273
|
+
await finishRunView(tx('Cancelled', '已取消'), 'cancelled');
|
|
2162
2274
|
return;
|
|
2163
2275
|
}
|
|
2164
2276
|
}
|
|
@@ -2166,11 +2278,16 @@
|
|
|
2166
2278
|
if (!response.ok) {
|
|
2167
2279
|
if (runCancellationRequested || isRunCancellationError(response.error)) {
|
|
2168
2280
|
appendRunCancelledReport();
|
|
2169
|
-
await finishRunView(tx('Cancelled', '
|
|
2281
|
+
await finishRunView(tx('Cancelled', '已取消'), 'cancelled');
|
|
2170
2282
|
void finalizeAuditRecord(runAuditDraft, { resultStatus: 'cancelled' });
|
|
2171
2283
|
return;
|
|
2172
2284
|
}
|
|
2173
|
-
|
|
2285
|
+
if (response.error?.code === 'codex_local_skill_unavailable' && localSkillRestorePoint) {
|
|
2286
|
+
restoreRejectedLocalSkillSubmission({ ...submittedWritingStyleScope, sessionId: submittedSessionId,
|
|
2287
|
+
task: submittedDraft, attachments: submittedAttachments, skillInvocation: submittedSkillInvocation,
|
|
2288
|
+
selectionContext: submittedPanelState.selectionContext }, localSkillRestorePoint);
|
|
2289
|
+
}
|
|
2290
|
+
const translated = translateRawError(response.error.message, { mode: submittedMode, locale: getLocale(), code: response.error?.code });
|
|
2174
2291
|
// Structured FailureReason (§9.7): split bridge-availability from
|
|
2175
2292
|
// Codex-side errors. `native_connection_failed` / `native_unavailable`
|
|
2176
2293
|
// / `native_update_required` indicate the bridge itself is missing,
|
|
@@ -2182,7 +2299,11 @@
|
|
|
2182
2299
|
// steps) existed but were never emitted, so everything collapsed into
|
|
2183
2300
|
// "no usable result" and pointed users at the wrong recovery.
|
|
2184
2301
|
const runErrorMessage = String(response.error?.message || '');
|
|
2185
|
-
const codexRunFailure = response.error?.code === '
|
|
2302
|
+
const codexRunFailure = response.error?.code === 'codex_local_skill_unavailable'
|
|
2303
|
+
? buildContentFailure('codex_local_skill_unavailable', { path: 'codex.run' }, { technicalMessage: runErrorMessage })
|
|
2304
|
+
: translated.failureCode === 'codex_local_skills_failed'
|
|
2305
|
+
? buildContentFailure('codex_local_skills_failed', { path: 'codex.run' }, { technicalMessage: runErrorMessage })
|
|
2306
|
+
: response.error?.code === 'native_execution_interrupted'
|
|
2186
2307
|
? buildContentFailure('native_request_failed', { path: 'codex.run' }, {
|
|
2187
2308
|
// The host IS installed — the request was interrupted mid-flight.
|
|
2188
2309
|
// Telling this user to reinstall would be a misdiagnosis.
|
|
@@ -2420,7 +2541,7 @@
|
|
|
2420
2541
|
} catch (error) {
|
|
2421
2542
|
if (runCancellationRequested || isRunCancellationError(error)) {
|
|
2422
2543
|
appendRunCancelledReport();
|
|
2423
|
-
await finishRunView(tx('Cancelled', '
|
|
2544
|
+
await finishRunView(tx('Cancelled', '已取消'), 'cancelled');
|
|
2424
2545
|
void finalizeAuditRecord(runAuditDraft, { resultStatus: 'cancelled' });
|
|
2425
2546
|
return;
|
|
2426
2547
|
}
|
|
@@ -2507,6 +2628,8 @@
|
|
|
2507
2628
|
submittedMode,
|
|
2508
2629
|
submittedCustomInstructions,
|
|
2509
2630
|
submittedSkillLoadingSettings,
|
|
2631
|
+
submittedEnabledLocalSkillIds,
|
|
2632
|
+
submittedEnabledSkillIds,
|
|
2510
2633
|
submittedAttachments,
|
|
2511
2634
|
submittedSkillInvocation,
|
|
2512
2635
|
executionSnapshot
|
|
@@ -2515,8 +2638,7 @@
|
|
|
2515
2638
|
title: tx('Starting the Codex skill installer.', '正在启动 Codex skill installer。'),
|
|
2516
2639
|
status: 'running'
|
|
2517
2640
|
});
|
|
2518
|
-
const
|
|
2519
|
-
const codexThreadId = activeSession?.codexThreadId || '';
|
|
2641
|
+
const codexThreadId = currentRunView?.codexThreadId || '';
|
|
2520
2642
|
const params = {
|
|
2521
2643
|
projectId: getCurrentProjectId(),
|
|
2522
2644
|
mode: submittedMode,
|
|
@@ -2524,27 +2646,29 @@
|
|
|
2524
2646
|
model: executionSnapshot.model,
|
|
2525
2647
|
reasoningEffort: executionSnapshot.reasoningEffort,
|
|
2526
2648
|
speedTier: executionSnapshot.speedTier,
|
|
2527
|
-
session:
|
|
2649
|
+
session: currentRunView?.sessionContext,
|
|
2650
|
+
clientRunId: currentRunView?.recordId || '',
|
|
2651
|
+
clientSessionId: runSessionId,
|
|
2652
|
+
accountScopeId: currentRunView?.runAccountScopeId || '',
|
|
2528
2653
|
threadId: codexThreadId || undefined,
|
|
2529
2654
|
customInstructions: submittedCustomInstructions || undefined,
|
|
2530
2655
|
loadCodexLocalSkills: submittedSkillLoadingSettings?.loadCodexLocalSkills !== false,
|
|
2531
2656
|
loadCodexOverleafSkills: submittedSkillLoadingSettings?.loadCodexOverleafSkills !== false,
|
|
2657
|
+
enabledCodexLocalSkillIds: submittedEnabledLocalSkillIds,
|
|
2658
|
+
enabledCodexOverleafSkillIds: submittedEnabledSkillIds,
|
|
2532
2659
|
attachments: submittedAttachments.length ? submittedAttachments : undefined,
|
|
2533
2660
|
skillInvocation: submittedSkillInvocation,
|
|
2534
2661
|
focusFiles: [],
|
|
2535
2662
|
skipMirrorSync: true
|
|
2536
2663
|
};
|
|
2537
2664
|
|
|
2538
|
-
const response = await
|
|
2539
|
-
method: 'codex.run',
|
|
2540
|
-
params
|
|
2541
|
-
});
|
|
2665
|
+
const response = await sendTrackedCodexRun(params);
|
|
2542
2666
|
|
|
2543
2667
|
if (!response.ok) {
|
|
2544
2668
|
if (runCancellationRequested || isRunCancellationError(response.error)) {
|
|
2545
2669
|
appendRunCancelledReport();
|
|
2546
2670
|
await finalizeAuditRecord(runAuditDraft, { resultStatus: 'cancelled' });
|
|
2547
|
-
await finishRunView(tx('Cancelled', '
|
|
2671
|
+
await finishRunView(tx('Cancelled', '已取消'), 'cancelled');
|
|
2548
2672
|
return;
|
|
2549
2673
|
}
|
|
2550
2674
|
const translated = translateRawError(response.error.message, { mode: submittedMode, locale: getLocale() });
|
|
@@ -2738,19 +2862,31 @@
|
|
|
2738
2862
|
if (currentRunView) {
|
|
2739
2863
|
return queueComposerInput();
|
|
2740
2864
|
}
|
|
2865
|
+
if (runSubmissionPending) return;
|
|
2866
|
+
runSubmissionPending = true;
|
|
2741
2867
|
runTask().catch(error => {
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2868
|
+
if (!currentRunView) {
|
|
2869
|
+
setRunning(false);
|
|
2870
|
+
nativeChannel.clearActiveRequest();
|
|
2871
|
+
stopRunElapsedTick();
|
|
2872
|
+
runCancellationController = null;
|
|
2873
|
+
}
|
|
2747
2874
|
console.error('[codex-overleaf] failed to start task', error);
|
|
2748
|
-
appendPlainLog(tx(`Could not start Codex task: ${error.message}`, `无法启动 Codex 任务:${error.message}`));
|
|
2749
|
-
});
|
|
2875
|
+
appendPlainLog(tx(`Could not start Codex task: ${localizeVisibleReason(error.message)}`, `无法启动 Codex 任务:${localizeVisibleReason(error.message)}`));
|
|
2876
|
+
}).finally(() => { runSubmissionPending = false; });
|
|
2750
2877
|
}
|
|
2751
2878
|
|
|
2752
2879
|
const runGuidanceController = Modules.RunGuidanceController.create({
|
|
2753
|
-
findRunRecord,
|
|
2880
|
+
findRunRecord, getCurrentRunView: () => getCurrentRunViewForRender(),
|
|
2881
|
+
getActiveSessionId: () => state?.activeSessionId,
|
|
2882
|
+
getScope: () => ({ accountScopeId: cachedAccountScopeId, projectId: getCurrentProjectId(), generation: spaRouteGeneration }),
|
|
2883
|
+
getSessionCreatedAt: sessionId => findSessionById(sessionId)?.createdAt,
|
|
2884
|
+
getRunRoot: target => target.sessionId === state?.activeSessionId
|
|
2885
|
+
? panel?.querySelector(`[data-run-id="${cssEscape(target.recordId)}"]`) : null,
|
|
2886
|
+
onRecordChanged: target => updateSessionById(target.sessionId, {}),
|
|
2887
|
+
onPresentationError: error => console.warn('[codex-overleaf] guidance presentation failed', error),
|
|
2888
|
+
RunGuidanceView: Modules.RunGuidanceView, tx, maxRunEvents: MAX_RUN_EVENTS,
|
|
2889
|
+
revealGuidance: row => runTimelineView.revealGuidance(row),
|
|
2754
2890
|
renderRunEvent, cssEscape, bumpUnreadIfDetached
|
|
2755
2891
|
});
|
|
2756
2892
|
|
|
@@ -2764,13 +2900,23 @@
|
|
|
2764
2900
|
readInputs: readPanelInputs,
|
|
2765
2901
|
getTask: () => panel?.querySelector('[data-task]')?.value || '',
|
|
2766
2902
|
hasAttachments: () => getComposerAttachmentsForRun().length > 0,
|
|
2767
|
-
capturePayload: () =>
|
|
2903
|
+
capturePayload: () => ({
|
|
2904
|
+
...RunExecutionSnapshot.toQueuePayload(captureCurrentExecutionSnapshot()),
|
|
2905
|
+
submissionContext: RunInputQueue.normalizeSubmissionContext({
|
|
2906
|
+
customInstructions: getCustomInstructionsForCurrentProject(),
|
|
2907
|
+
skillLoadingSettings: getSkillLoadingSettings(),
|
|
2908
|
+
skillInvocation: getComposerSkillInvocationForRun(),
|
|
2909
|
+
enabledCodexOverleafSkillIds: getEnabledCodexOverleafSkillIds(),
|
|
2910
|
+
enabledCodexLocalSkillIds: getEnabledCodexLocalSkillIds()
|
|
2911
|
+
})
|
|
2912
|
+
}),
|
|
2768
2913
|
prepareClaim: () => providerSettingsCoordinator.ensureLoaded(),
|
|
2769
2914
|
resolveExecutionSnapshot: snapshot => RunExecutionSnapshot.resolveForExecution(snapshot, {
|
|
2770
2915
|
catalog: providerSettingsCoordinator.getCatalog()
|
|
2771
2916
|
}),
|
|
2772
2917
|
clearTask: () => {
|
|
2773
2918
|
panel.querySelector('[data-task]').value = '';
|
|
2919
|
+
selectionContextView.clear();
|
|
2774
2920
|
updateSessionById(state.activeSessionId, { task: '' });
|
|
2775
2921
|
autosizeTaskTextarea();
|
|
2776
2922
|
syncComposerSendAvailability();
|
|
@@ -2782,10 +2928,18 @@
|
|
|
2782
2928
|
);
|
|
2783
2929
|
},
|
|
2784
2930
|
run: item => runTask({ queuedInput: item }),
|
|
2785
|
-
|
|
2931
|
+
getGuideScope: () => ({ projectId: getCurrentProjectId(), accountScopeId: cachedAccountScopeId, generation: spaRouteGeneration }),
|
|
2932
|
+
isGuideCancelled: () => runCancellationRequested || runCancellationController?.signal?.aborted === true,
|
|
2933
|
+
preflightGuide: ({ task, assertCurrent }) => runSensitivePreflight({
|
|
2934
|
+
task, project: { files: [] }, rules: getGovernanceRulesForCurrentProject(),
|
|
2935
|
+
guidance: true, useExistingMirror: false, assertCurrent
|
|
2936
|
+
}),
|
|
2937
|
+
sendSteer: (params, context) => nativeCompatibilityController.sendBackgroundNative({ method: 'codex.steer', params }, context),
|
|
2786
2938
|
getContainer: () => panel?.querySelector('[data-pending-inputs]'),
|
|
2787
2939
|
createView: options => PendingInputView.create(options),
|
|
2788
2940
|
appendEvent: (title, status) => appendRunEvent({ title, status }),
|
|
2941
|
+
previewGuidance: runGuidanceController.preview,
|
|
2942
|
+
clearGuidancePreview: runGuidanceController.clearPreview,
|
|
2789
2943
|
appendGuidance: runGuidanceController.upsert,
|
|
2790
2944
|
removeGuidance: runGuidanceController.remove,
|
|
2791
2945
|
toast: (message, status) => showPluginToast(message, { status }),
|
|
@@ -2801,6 +2955,9 @@
|
|
|
2801
2955
|
const requestedProviderId = submitted.providerId || state?.providerId || 'builtin';
|
|
2802
2956
|
const selection = providerSettingsCoordinator.getRunSelection(requestedProviderId);
|
|
2803
2957
|
const providerId = selection?.providerId || requestedProviderId;
|
|
2958
|
+
const selectedContext = Object.prototype.hasOwnProperty.call(submitted, 'selectionContext')
|
|
2959
|
+
? submitted.selectionContext : selectionContextView.capture();
|
|
2960
|
+
const selectedPaths = submitted.focusFiles ?? getActiveFocusFiles();
|
|
2804
2961
|
return RunExecutionSnapshot.capture({
|
|
2805
2962
|
mode: submitted.mode ?? state?.mode,
|
|
2806
2963
|
providerId,
|
|
@@ -2810,7 +2967,11 @@
|
|
|
2810
2967
|
speedTier: submitted.speedTier ?? state?.speedTier,
|
|
2811
2968
|
autoRecompile: submitted.autoRecompile ?? state?.autoRecompile !== false,
|
|
2812
2969
|
requireReviewing: submitted.requireReviewing ?? state?.requireReviewing === true,
|
|
2813
|
-
focusFiles:
|
|
2970
|
+
focusFiles: selectedContext?.mode === 'edit' && selectedPaths.length
|
|
2971
|
+
? [...new Set([...selectedPaths, selectedContext.path])] : selectedPaths,
|
|
2972
|
+
selectionContext: selectedContext,
|
|
2973
|
+
writingStyle: Object.prototype.hasOwnProperty.call(submitted, 'writingStyle')
|
|
2974
|
+
? submitted.writingStyle : writingStyleSettings.capture()
|
|
2814
2975
|
}, {
|
|
2815
2976
|
source: 'submitted',
|
|
2816
2977
|
requireProviderRevision: providerId !== 'builtin'
|
|
@@ -2818,6 +2979,18 @@
|
|
|
2818
2979
|
}
|
|
2819
2980
|
|
|
2820
2981
|
function queueComposerInput() {
|
|
2982
|
+
if (getSkillLoadingSettings().loadCodexLocalSkills !== false && !Array.isArray(getCodexLocalSkillsCatalog())) {
|
|
2983
|
+
primeCodexLocalSkillsCatalog({ required: true }).catch(() => {});
|
|
2984
|
+
showPluginToast(tx('Local Codex skills are still loading. Try submitting again when loading finishes.',
|
|
2985
|
+
'Codex 本地技能仍在加载,请在加载完成后重新提交。'), { status: 'info' });
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
const invocation = getComposerSkillInvocationForRun();
|
|
2989
|
+
if (invocation?.scope === 'codex-local' && !getEnabledCodexLocalSkillIds()?.includes(invocation.id)) {
|
|
2990
|
+
showPluginToast(tx('The selected local Codex skill is unavailable or disabled. Select an enabled skill and try again.',
|
|
2991
|
+
'所选 Codex 本地技能不可用或已关闭,请选择已启用的技能后重试。'), { status: 'failed' });
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2821
2994
|
return runInputCoordinator?.queueComposerInput();
|
|
2822
2995
|
}
|
|
2823
2996
|
|
|
@@ -2867,7 +3040,7 @@
|
|
|
2867
3040
|
const nativeResult = results[0];
|
|
2868
3041
|
const nativeResponse = nativeResult?.status === 'fulfilled' ? nativeResult.value : null;
|
|
2869
3042
|
if (nativeResponse && !nativeResponse.ok) {
|
|
2870
|
-
const message = nativeResponse?.error?.message || tx('native host did not respond', 'native host 没有响应');
|
|
3043
|
+
const message = localizeVisibleReason(nativeResponse?.error?.message || tx('native host did not respond', 'native host 没有响应'));
|
|
2871
3044
|
appendRunEvent({
|
|
2872
3045
|
title: tx(`Cancel request was not delivered: ${message}`, `中断请求没有送达:${message}`),
|
|
2873
3046
|
status: 'failed'
|
|
@@ -2909,25 +3082,8 @@
|
|
|
2909
3082
|
force: true
|
|
2910
3083
|
}
|
|
2911
3084
|
});
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
const released = result.lockReleased === true || result.cancelled === true;
|
|
2915
|
-
appendPlainLog(released
|
|
2916
|
-
? tx(
|
|
2917
|
-
`Force-released the stuck Codex run for project ${projectKey}. You can retry now.`,
|
|
2918
|
-
`已强制释放项目 ${projectKey} 的 Codex 占用,可以重试了。`
|
|
2919
|
-
)
|
|
2920
|
-
: tx(
|
|
2921
|
-
`No stuck run was found for project ${projectKey}.`,
|
|
2922
|
-
`没有发现项目 ${projectKey} 上的卡住任务。`
|
|
2923
|
-
));
|
|
2924
|
-
return response;
|
|
2925
|
-
}
|
|
2926
|
-
const message = response?.error?.message || tx('native host did not respond', 'native host 没有响应');
|
|
2927
|
-
appendPlainLog(tx(
|
|
2928
|
-
`Force-release request was not delivered: ${message}`,
|
|
2929
|
-
`强制释放请求没有送达:${message}`
|
|
2930
|
-
));
|
|
3085
|
+
appendPlainLog(Modules.RunRecoveryActions.formatForceCancelOutcome(response, tx));
|
|
3086
|
+
if (response?.ok !== true && response?.error?.message) appendPlainLog(localizeVisibleReason(response.error.message));
|
|
2931
3087
|
return response;
|
|
2932
3088
|
}
|
|
2933
3089
|
|
|
@@ -3031,6 +3187,12 @@
|
|
|
3031
3187
|
.filter(id => id && isCodexOverleafSkillEnabled(id));
|
|
3032
3188
|
}
|
|
3033
3189
|
|
|
3190
|
+
function getEnabledCodexLocalSkillIds() {
|
|
3191
|
+
if (getSkillLoadingSettings().loadCodexLocalSkills === false) return [];
|
|
3192
|
+
const skills = getCodexLocalSkillsCatalog();
|
|
3193
|
+
return Array.isArray(skills) ? skills.filter(skill => isCodexLocalSkillEnabled(skill.id, skill)).map(skill => skill.id) : undefined;
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3034
3196
|
function buildCodexRunParams({
|
|
3035
3197
|
task,
|
|
3036
3198
|
project,
|
|
@@ -3043,12 +3205,16 @@
|
|
|
3043
3205
|
compileLogContext,
|
|
3044
3206
|
customInstructions,
|
|
3045
3207
|
skillLoadingSettings,
|
|
3208
|
+
enabledCodexOverleafSkillIds,
|
|
3209
|
+
enabledCodexLocalSkillIds,
|
|
3046
3210
|
attachments,
|
|
3047
3211
|
skillInvocation,
|
|
3048
3212
|
submittedMode
|
|
3049
3213
|
} = {}) {
|
|
3050
3214
|
const executionSnapshot = currentRunView?.executionSnapshot || captureCurrentExecutionSnapshot();
|
|
3051
|
-
const submittedState = RunExecutionSnapshot.applyToState(
|
|
3215
|
+
const submittedState = RunExecutionSnapshot.applyToState({
|
|
3216
|
+
...state, session: currentRunView?.sessionContext || state.session
|
|
3217
|
+
}, executionSnapshot);
|
|
3052
3218
|
const params = runController.buildCodexRunParams({
|
|
3053
3219
|
currentProjectId: getCurrentProjectId(),
|
|
3054
3220
|
state: submittedState,
|
|
@@ -3068,7 +3234,9 @@
|
|
|
3068
3234
|
? getSkillLoadingSettings()
|
|
3069
3235
|
: { loadCodexLocalSkills: true, loadCodexOverleafSkills: true })
|
|
3070
3236
|
: skillLoadingSettings,
|
|
3071
|
-
enabledCodexOverleafSkillIds:
|
|
3237
|
+
enabledCodexOverleafSkillIds: enabledCodexOverleafSkillIds === undefined
|
|
3238
|
+
? getEnabledCodexOverleafSkillIds() : enabledCodexOverleafSkillIds,
|
|
3239
|
+
enabledCodexLocalSkillIds,
|
|
3072
3240
|
attachments,
|
|
3073
3241
|
skillInvocation,
|
|
3074
3242
|
compileLogContext,
|
|
@@ -3078,16 +3246,31 @@
|
|
|
3078
3246
|
...params,
|
|
3079
3247
|
clientRunId: currentRunView?.recordId || '',
|
|
3080
3248
|
clientSessionId: currentRunView?.sessionId || '',
|
|
3081
|
-
providerSelection: RunExecutionSnapshot.toProviderSelection(executionSnapshot)
|
|
3249
|
+
providerSelection: RunExecutionSnapshot.toProviderSelection(executionSnapshot),
|
|
3250
|
+
selectionContext: executionSnapshot.selectionContext || null,
|
|
3251
|
+
accountScopeId: currentRunView?.runAccountScopeId || cachedAccountScopeId || '',
|
|
3252
|
+
writingStyle: executionSnapshot.writingStyle || null
|
|
3082
3253
|
};
|
|
3083
3254
|
}
|
|
3084
3255
|
|
|
3085
3256
|
async function sendTrackedCodexRun(params) {
|
|
3086
|
-
const
|
|
3257
|
+
const owner = currentRunView;
|
|
3258
|
+
const signal = runCancellationController?.signal;
|
|
3259
|
+
const assertCurrent = () => {
|
|
3260
|
+
if (signal?.aborted || (owner && owner !== currentRunView)) throw createRunCancellationError();
|
|
3261
|
+
throwIfRunCancellationRequested();
|
|
3262
|
+
if (owner && (owner.runProjectId !== getCurrentProjectId()
|
|
3263
|
+
|| owner.runAccountScopeId !== cachedAccountScopeId)) {
|
|
3264
|
+
throw Object.assign(createRunCancellationError(), {
|
|
3265
|
+
message: 'The active project or account changed before dispatch.'
|
|
3266
|
+
});
|
|
3267
|
+
}
|
|
3268
|
+
};
|
|
3269
|
+
const compatibilityGate = await nativeCompatibilityController.ensureForMethod('codex.run', { assertCurrent });
|
|
3270
|
+
assertCurrent();
|
|
3087
3271
|
if (!compatibilityGate.ok) {
|
|
3088
3272
|
return compatibilityGate.response;
|
|
3089
3273
|
}
|
|
3090
|
-
throwIfRunCancellationRequested();
|
|
3091
3274
|
const tracked = nativeChannel.sendNativeTracked(nativeCompatibilityController.attachEvidence({
|
|
3092
3275
|
method: 'codex.run',
|
|
3093
3276
|
params
|
|
@@ -3118,6 +3301,12 @@
|
|
|
3118
3301
|
const projectKey = getCurrentProjectId();
|
|
3119
3302
|
const recovery = await activeTurnControl.recoverJournals({
|
|
3120
3303
|
projectKey,
|
|
3304
|
+
accountScopeId: cachedAccountScopeId,
|
|
3305
|
+
requireOwnerLost: Modules.StorageDb.sharedSessionsEnabled?.() === true,
|
|
3306
|
+
normalizeInterruptedRun: record => Object.assign(record, Modules.SessionState.normalizeRuns([record], {
|
|
3307
|
+
restoreRunningRuns: true,
|
|
3308
|
+
locale: getLocale()
|
|
3309
|
+
})[0]),
|
|
3121
3310
|
findSession: findSessionById,
|
|
3122
3311
|
getActiveSession: () => getActiveSession(state),
|
|
3123
3312
|
createInterruptedRun: journal => ({
|
|
@@ -3160,7 +3349,7 @@
|
|
|
3160
3349
|
maxEvents: MAX_RUN_EVENTS
|
|
3161
3350
|
});
|
|
3162
3351
|
if (recovery.changed) {
|
|
3163
|
-
await saveState();
|
|
3352
|
+
if (await saveState({ recoveryRuns: recovery.recoveredRuns }) !== true) return;
|
|
3164
3353
|
for (const requestId of recovery.acknowledgeIds) {
|
|
3165
3354
|
await activeTurnControl.acknowledge(requestId).catch(() => false);
|
|
3166
3355
|
}
|
|
@@ -3172,11 +3361,17 @@
|
|
|
3172
3361
|
if (!StorageDb || !AuditRecords) {
|
|
3173
3362
|
return null;
|
|
3174
3363
|
}
|
|
3364
|
+
// Audit follows the captured run owner even if the visible page changes.
|
|
3365
|
+
// Missing owner identity must never be filled with a later account.
|
|
3366
|
+
const accountScopeId = currentRunView ? currentRunView.runAccountScopeId : cachedAccountScopeId;
|
|
3367
|
+
const projectId = currentRunView ? currentRunView.runProjectId : getCurrentProjectId();
|
|
3368
|
+
if (!accountScopeId || !projectId) return null;
|
|
3175
3369
|
const executionSnapshot = input.executionSnapshot
|
|
3176
3370
|
|| currentRunView?.executionSnapshot
|
|
3177
3371
|
|| captureCurrentExecutionSnapshot();
|
|
3178
3372
|
const draft = AuditRecords.buildAuditDraftRecord({
|
|
3179
|
-
|
|
3373
|
+
accountScopeId,
|
|
3374
|
+
projectId,
|
|
3180
3375
|
sessionId: input.sessionId || '',
|
|
3181
3376
|
turnId: currentRunView?.recordId || '',
|
|
3182
3377
|
mode: input.mode || executionSnapshot.mode,
|
|
@@ -3211,8 +3406,10 @@
|
|
|
3211
3406
|
return draft;
|
|
3212
3407
|
}
|
|
3213
3408
|
const record = StorageDb.buildAuditLogRecord
|
|
3214
|
-
? StorageDb.buildAuditLogRecord({ ...draft, ...updates, id: draft.id,
|
|
3215
|
-
|
|
3409
|
+
? StorageDb.buildAuditLogRecord({ ...draft, ...updates, id: draft.id,
|
|
3410
|
+
accountScopeId: draft.accountScopeId, projectId: draft.projectId,
|
|
3411
|
+
completedAt: updates.completedAt || draft.completedAt || '' })
|
|
3412
|
+
: { ...draft, ...updates, id: draft.id, accountScopeId: draft.accountScopeId, projectId: draft.projectId };
|
|
3216
3413
|
return StorageDb.putRecord('auditLogs', record).then(() => record).catch(() => draft);
|
|
3217
3414
|
}
|
|
3218
3415
|
|
|
@@ -3242,121 +3439,10 @@
|
|
|
3242
3439
|
}
|
|
3243
3440
|
|
|
3244
3441
|
async function runSensitivePreflight(options) {
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
return { blocked: false, findings: [] };
|
|
3250
|
-
}
|
|
3251
|
-
if (!SensitiveScan?.scanSensitiveInputs && !useExistingMirror) {
|
|
3252
|
-
return { blocked: false, findings: [] };
|
|
3253
|
-
}
|
|
3254
|
-
let findings = SensitiveScan?.scanSensitiveInputs
|
|
3255
|
-
? SensitiveScan.scanSensitiveInputs({
|
|
3256
|
-
task,
|
|
3257
|
-
files: (project?.files || []).filter(isTextSnapshotFile)
|
|
3258
|
-
})
|
|
3259
|
-
: [];
|
|
3260
|
-
if (useExistingMirror) {
|
|
3261
|
-
findings = dedupeSensitiveFindings([
|
|
3262
|
-
...findings,
|
|
3263
|
-
...await scanNativeMirrorSensitiveFindings()
|
|
3264
|
-
]);
|
|
3265
|
-
}
|
|
3266
|
-
if (!findings.length) {
|
|
3267
|
-
return { blocked: false, findings: [] };
|
|
3268
|
-
}
|
|
3269
|
-
appendRunEvent({
|
|
3270
|
-
title: tx(
|
|
3271
|
-
`Sensitive content check found ${findings.length} item(s).`,
|
|
3272
|
-
`敏感内容检查发现 ${findings.length} 项。`
|
|
3273
|
-
),
|
|
3274
|
-
status: 'failed',
|
|
3275
|
-
detail: findings.slice(0, 8).map(finding => ({
|
|
3276
|
-
[tr('detailFile')]: finding.path || finding.source || 'task',
|
|
3277
|
-
[tr('detailReason')]: finding.detectorId,
|
|
3278
|
-
[tx('Preview', '预览')]: finding.preview || '[REDACTED]'
|
|
3279
|
-
}))
|
|
3280
|
-
});
|
|
3281
|
-
if (normalizedRules.sensitiveConfirmAllowed === true) {
|
|
3282
|
-
const approved = await showPluginConfirm({
|
|
3283
|
-
title: tr('sensitiveConfirmTitle'),
|
|
3284
|
-
message: formatSensitiveConfirmMessage(findings),
|
|
3285
|
-
confirmLabel: tr('sensitiveConfirmRun'),
|
|
3286
|
-
cancelLabel: tr('confirmDefaultCancel'),
|
|
3287
|
-
destructive: true
|
|
3288
|
-
});
|
|
3289
|
-
return { blocked: !approved, findings };
|
|
3290
|
-
}
|
|
3291
|
-
appendCompletionReport({
|
|
3292
|
-
conclusion: tx(
|
|
3293
|
-
'This run was blocked before Codex received project context because sensitive content was detected.',
|
|
3294
|
-
'检测到敏感内容,本轮已在向 Codex 发送项目上下文前阻止。'
|
|
3295
|
-
),
|
|
3296
|
-
status: 'blocked',
|
|
3297
|
-
operations: [],
|
|
3298
|
-
applyResults: [],
|
|
3299
|
-
nextStep: tx(
|
|
3300
|
-
'Remove the sensitive content, narrow @context, or enable explicit sensitive confirmation in Project Settings.',
|
|
3301
|
-
'请移除敏感内容、缩小 @context,或在项目设置里允许敏感内容显式确认。'
|
|
3302
|
-
)
|
|
3303
|
-
});
|
|
3304
|
-
return { blocked: true, findings };
|
|
3305
|
-
}
|
|
3306
|
-
|
|
3307
|
-
function formatSensitiveConfirmMessage(findings = []) {
|
|
3308
|
-
const visible = findings.slice(0, 8).map(finding => {
|
|
3309
|
-
const path = finding.path || finding.source || 'task';
|
|
3310
|
-
return `${path}: ${finding.detectorId || 'sensitive'} (${finding.preview || '[REDACTED]'})`;
|
|
3311
|
-
});
|
|
3312
|
-
return [
|
|
3313
|
-
tr('sensitiveConfirmMessage'),
|
|
3314
|
-
'',
|
|
3315
|
-
...visible,
|
|
3316
|
-
findings.length > visible.length ? tx(`${findings.length - visible.length} more finding(s).`, `另有 ${findings.length - visible.length} 项。`) : ''
|
|
3317
|
-
].filter(Boolean).join('\n');
|
|
3318
|
-
}
|
|
3319
|
-
|
|
3320
|
-
async function scanNativeMirrorSensitiveFindings() {
|
|
3321
|
-
try {
|
|
3322
|
-
const response = await sendBackgroundNative({
|
|
3323
|
-
method: 'mirror.scanSensitive',
|
|
3324
|
-
params: { projectId: getCurrentProjectId() }
|
|
3325
|
-
});
|
|
3326
|
-
if (response?.ok) {
|
|
3327
|
-
return Array.isArray(response.result?.findings) ? response.result.findings : [];
|
|
3328
|
-
}
|
|
3329
|
-
return [{
|
|
3330
|
-
detectorId: 'mirror-sensitive-scan-unavailable',
|
|
3331
|
-
source: 'local-mirror',
|
|
3332
|
-
preview: response?.error?.message || 'Could not scan the local mirror before Codex runs.'
|
|
3333
|
-
}];
|
|
3334
|
-
} catch (error) {
|
|
3335
|
-
return [{
|
|
3336
|
-
detectorId: 'mirror-sensitive-scan-unavailable',
|
|
3337
|
-
source: 'local-mirror',
|
|
3338
|
-
preview: error?.message || 'Could not scan the local mirror before Codex runs.'
|
|
3339
|
-
}];
|
|
3340
|
-
}
|
|
3341
|
-
}
|
|
3342
|
-
|
|
3343
|
-
function dedupeSensitiveFindings(findings = []) {
|
|
3344
|
-
const seen = new Set();
|
|
3345
|
-
const deduped = [];
|
|
3346
|
-
for (const finding of findings || []) {
|
|
3347
|
-
const key = [
|
|
3348
|
-
finding?.detectorId || '',
|
|
3349
|
-
finding?.source || '',
|
|
3350
|
-
finding?.path || '',
|
|
3351
|
-
finding?.preview || ''
|
|
3352
|
-
].join('\u0000');
|
|
3353
|
-
if (seen.has(key)) {
|
|
3354
|
-
continue;
|
|
3355
|
-
}
|
|
3356
|
-
seen.add(key);
|
|
3357
|
-
deduped.push(finding);
|
|
3358
|
-
}
|
|
3359
|
-
return deduped;
|
|
3442
|
+
return Modules.SensitivePreflight.create({
|
|
3443
|
+
SensitiveScan, normalizeGovernanceRules, isTextSnapshotFile, sendBackgroundNative,
|
|
3444
|
+
getCurrentProjectId, appendRunEvent, appendCompletionReport, showPluginConfirm, tr, tx
|
|
3445
|
+
}).run(options);
|
|
3360
3446
|
}
|
|
3361
3447
|
|
|
3362
3448
|
function appendRunCancelledReport() {
|
|
@@ -3364,11 +3450,12 @@
|
|
|
3364
3450
|
.some(item => item?.status === 'queued');
|
|
3365
3451
|
appendRunEvent({
|
|
3366
3452
|
title: tx('Current Codex task was cancelled.', '已中断当前 Codex 任务。'),
|
|
3367
|
-
status: '
|
|
3453
|
+
status: 'info',
|
|
3454
|
+
activity: { v: 1, kind: 'lifecycle', state: 'cancelled', target: 'cancelled' }
|
|
3368
3455
|
});
|
|
3369
3456
|
appendCompletionReport({
|
|
3370
3457
|
conclusion: tx('This run was cancelled. It did not continue syncing or writing to Overleaf.', '这轮已中断,没有继续同步或写入 Overleaf。'),
|
|
3371
|
-
status: '
|
|
3458
|
+
status: 'cancelled',
|
|
3372
3459
|
operations: [],
|
|
3373
3460
|
applyResults: [],
|
|
3374
3461
|
nextStep: hasQueuedFollowUp
|
|
@@ -3407,6 +3494,7 @@
|
|
|
3407
3494
|
: !hasInput;
|
|
3408
3495
|
runButton.title = label;
|
|
3409
3496
|
runButton.setAttribute('aria-label', label);
|
|
3497
|
+
if (currentRunView?.writebackRetry && hasInput) runButton.disabled = true;
|
|
3410
3498
|
}
|
|
3411
3499
|
|
|
3412
3500
|
function handleTaskInputKeydown(event) {
|
|
@@ -3421,6 +3509,7 @@
|
|
|
3421
3509
|
}
|
|
3422
3510
|
|
|
3423
3511
|
function handleTaskInput() {
|
|
3512
|
+
composerDraftRevision += 1;
|
|
3424
3513
|
autosizeTaskTextarea();
|
|
3425
3514
|
syncComposerSendAvailability();
|
|
3426
3515
|
renderPendingInputs();
|
|
@@ -3443,10 +3532,8 @@
|
|
|
3443
3532
|
subtitle: tr('slashInstallSkillSubtitle')
|
|
3444
3533
|
}
|
|
3445
3534
|
];
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
}
|
|
3449
|
-
for (const skill of slashCodexOverleafSkills) {
|
|
3535
|
+
const settings = getSkillLoadingSettings();
|
|
3536
|
+
for (const skill of settings.loadCodexOverleafSkills === false ? [] : slashCodexOverleafSkills) {
|
|
3450
3537
|
const id = String(skill?.id || '').trim();
|
|
3451
3538
|
if (!isSafeSkillId(id)) {
|
|
3452
3539
|
continue;
|
|
@@ -3462,13 +3549,28 @@
|
|
|
3462
3549
|
scope: 'codex-overleaf',
|
|
3463
3550
|
skillId: id,
|
|
3464
3551
|
title,
|
|
3465
|
-
subtitle: tr('
|
|
3552
|
+
subtitle: tr('slashUseOverleafSkillSubtitle')
|
|
3466
3553
|
});
|
|
3467
3554
|
}
|
|
3555
|
+
const localSkills = settings.loadCodexLocalSkills === false ? [] : (getCodexLocalSkillsCatalog() || []);
|
|
3556
|
+
for (const skill of localSkills) {
|
|
3557
|
+
if (!isSafeSkillId(skill.id) || !isCodexLocalSkillEnabled(skill.id, skill)) continue;
|
|
3558
|
+
const sameName = localSkills.filter(other => String(other.title || other.name || other.id) === String(skill.title || skill.name || skill.id)).length > 1;
|
|
3559
|
+
commands.push({ id: `local-skill:${skill.id}`, kind: 'codex-local-skill', scope: 'codex-local', skillId: skill.id,
|
|
3560
|
+
title: String(skill.title || skill.name || skill.id).trim().slice(0, 80),
|
|
3561
|
+
searchName: String(skill.name || ''),
|
|
3562
|
+
subtitle: tr('slashUseLocalSkillSubtitle') + (skill.sourceKind === 'plugin' && skill.sourceLabel ? ' · ' + skill.sourceLabel : '')
|
|
3563
|
+
+ (sameName ? ' · ' + (skill.sourceHint || skill.id.slice(-8)) : '') });
|
|
3564
|
+
}
|
|
3468
3565
|
return commands;
|
|
3469
3566
|
}
|
|
3470
3567
|
|
|
3471
3568
|
async function refreshCodexOverleafSkillsForSlashMenu() {
|
|
3569
|
+
if (getSlashTrigger() && getSkillLoadingSettings().loadCodexLocalSkills !== false) {
|
|
3570
|
+
primeCodexLocalSkillsCatalog().catch(() => {
|
|
3571
|
+
showPluginToast(tx('Could not load local Codex skills. Try again.', '无法加载 Codex 本地技能,请重试。'), { status: 'failed' });
|
|
3572
|
+
});
|
|
3573
|
+
}
|
|
3472
3574
|
if (slashCodexOverleafSkillsLoaded || slashCodexOverleafSkillsLoading) {
|
|
3473
3575
|
return;
|
|
3474
3576
|
}
|
|
@@ -3502,7 +3604,8 @@
|
|
|
3502
3604
|
const commands = getSlashCommands().filter(command => {
|
|
3503
3605
|
return !query
|
|
3504
3606
|
|| command.title.toLowerCase().includes(query)
|
|
3505
|
-
|| command.id.toLowerCase().includes(query)
|
|
3607
|
+
|| command.id.toLowerCase().includes(query)
|
|
3608
|
+
|| String(command.searchName || '').toLowerCase().includes(query);
|
|
3506
3609
|
});
|
|
3507
3610
|
renderSlashMenu(commands);
|
|
3508
3611
|
return;
|
|
@@ -3717,6 +3820,17 @@
|
|
|
3717
3820
|
renderedSlashCommands = new Map(commands.map(command => [command.id, command]));
|
|
3718
3821
|
menu.dataset.activeIndex = '0';
|
|
3719
3822
|
menu.hidden = false;
|
|
3823
|
+
menu.scrollTop = 0;
|
|
3824
|
+
updateSlashMenuLayout();
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
function updateSlashMenuLayout() {
|
|
3828
|
+
const menu = panel?.querySelector('[data-slash-menu]');
|
|
3829
|
+
const composer = panel?.querySelector('[data-composer-form]');
|
|
3830
|
+
if (!menu || menu.hidden || !composer) return;
|
|
3831
|
+
const visibleTop = Math.max(0, root.visualViewport?.offsetTop || 0, panel.getBoundingClientRect().top);
|
|
3832
|
+
const available = Math.max(0, Math.floor(composer.getBoundingClientRect().top - visibleTop - 12));
|
|
3833
|
+
menu.style.setProperty('--codex-slash-menu-space', `${available}px`);
|
|
3720
3834
|
}
|
|
3721
3835
|
|
|
3722
3836
|
function handleSlashMenuClick(event) {
|
|
@@ -3775,6 +3889,18 @@
|
|
|
3775
3889
|
buttons.forEach((button, buttonIndex) => {
|
|
3776
3890
|
button.dataset.active = buttonIndex === index ? 'true' : 'false';
|
|
3777
3891
|
});
|
|
3892
|
+
const active = buttons[index];
|
|
3893
|
+
if (!active || !menu.clientHeight) return;
|
|
3894
|
+
const viewport = menu.getBoundingClientRect();
|
|
3895
|
+
const item = active.getBoundingClientRect();
|
|
3896
|
+
const top = viewport.top + menu.clientTop;
|
|
3897
|
+
const bottom = top + menu.clientHeight;
|
|
3898
|
+
// Scroll only this popup; scrollIntoView may move the chat or editor too.
|
|
3899
|
+
if (item.height > menu.clientHeight || item.top < top) {
|
|
3900
|
+
menu.scrollTop += item.top - top;
|
|
3901
|
+
} else if (item.bottom > bottom) {
|
|
3902
|
+
menu.scrollTop += item.bottom - bottom;
|
|
3903
|
+
}
|
|
3778
3904
|
}
|
|
3779
3905
|
|
|
3780
3906
|
function selectSlashCommand(commandId) {
|
|
@@ -3796,7 +3922,7 @@
|
|
|
3796
3922
|
activateSkillInstallerComposerContext();
|
|
3797
3923
|
return;
|
|
3798
3924
|
}
|
|
3799
|
-
if (command.kind === 'codex-overleaf-skill') {
|
|
3925
|
+
if (command.kind === 'codex-overleaf-skill' || command.kind === 'codex-local-skill') {
|
|
3800
3926
|
activateCodexOverleafSkillComposerContext(command);
|
|
3801
3927
|
}
|
|
3802
3928
|
}
|
|
@@ -3823,6 +3949,7 @@
|
|
|
3823
3949
|
}
|
|
3824
3950
|
|
|
3825
3951
|
function activateSkillInstallerComposerContext() {
|
|
3952
|
+
composerDraftRevision += 1;
|
|
3826
3953
|
composerSkillInvocation = {
|
|
3827
3954
|
id: 'skill-installer',
|
|
3828
3955
|
title: tr('skillInstallerComposerLabel')
|
|
@@ -3833,6 +3960,7 @@
|
|
|
3833
3960
|
}
|
|
3834
3961
|
|
|
3835
3962
|
function activateCodexOverleafSkillComposerContext(command) {
|
|
3963
|
+
composerDraftRevision += 1;
|
|
3836
3964
|
const id = String(command?.skillId || '').trim();
|
|
3837
3965
|
if (!isSafeSkillId(id)) {
|
|
3838
3966
|
return;
|
|
@@ -3840,7 +3968,7 @@
|
|
|
3840
3968
|
composerSkillInvocation = {
|
|
3841
3969
|
id,
|
|
3842
3970
|
title: String(command?.title || id).trim().slice(0, 80) || id,
|
|
3843
|
-
scope: 'codex-overleaf'
|
|
3971
|
+
scope: command.scope === 'codex-local' ? 'codex-local' : 'codex-overleaf'
|
|
3844
3972
|
};
|
|
3845
3973
|
renderComposerSkillInvocation();
|
|
3846
3974
|
const input = panel?.querySelector('[data-task]');
|
|
@@ -3848,6 +3976,7 @@
|
|
|
3848
3976
|
}
|
|
3849
3977
|
|
|
3850
3978
|
function clearComposerSkillInvocation() {
|
|
3979
|
+
composerDraftRevision += 1;
|
|
3851
3980
|
composerSkillInvocation = null;
|
|
3852
3981
|
renderComposerSkillInvocation();
|
|
3853
3982
|
panel?.querySelector('[data-task]')?.focus?.();
|
|
@@ -3858,7 +3987,7 @@
|
|
|
3858
3987
|
if (!normalized) {
|
|
3859
3988
|
return null;
|
|
3860
3989
|
}
|
|
3861
|
-
if (normalized.id === 'skill-installer') {
|
|
3990
|
+
if (normalized.id === 'skill-installer' && !normalized.scope) {
|
|
3862
3991
|
return {
|
|
3863
3992
|
id: 'skill-installer',
|
|
3864
3993
|
title: tr('skillInstallerComposerLabel')
|
|
@@ -3873,13 +4002,9 @@
|
|
|
3873
4002
|
return null;
|
|
3874
4003
|
}
|
|
3875
4004
|
const title = String(value?.title || id).trim().slice(0, 80) || id;
|
|
3876
|
-
if (id === 'skill-installer') {
|
|
3877
|
-
|
|
3878
|
-
}
|
|
3879
|
-
if (value?.scope !== 'codex-overleaf') {
|
|
3880
|
-
return null;
|
|
3881
|
-
}
|
|
3882
|
-
return { id, title, scope: 'codex-overleaf' };
|
|
4005
|
+
if (id === 'skill-installer' && (!value?.scope || value.scope === 'builtin')) return { id, title };
|
|
4006
|
+
if (!['codex-overleaf', 'codex-local'].includes(value?.scope)) return null;
|
|
4007
|
+
return { id, title, scope: value.scope };
|
|
3883
4008
|
}
|
|
3884
4009
|
|
|
3885
4010
|
function isSafeSkillId(id) {
|
|
@@ -3900,7 +4025,7 @@
|
|
|
3900
4025
|
}
|
|
3901
4026
|
const clear = context.querySelector('[data-composer-skill-clear]');
|
|
3902
4027
|
if (clear) {
|
|
3903
|
-
const clearLabel = active?.id === 'skill-installer'
|
|
4028
|
+
const clearLabel = active?.id === 'skill-installer' && !active.scope
|
|
3904
4029
|
? tr('skillInstallerComposerClear')
|
|
3905
4030
|
: tr('composerSkillClear');
|
|
3906
4031
|
clear.title = clearLabel;
|
|
@@ -3941,72 +4066,6 @@
|
|
|
3941
4066
|
return writebackController.buildSyncApplyOperations(syncChanges, project);
|
|
3942
4067
|
}
|
|
3943
4068
|
|
|
3944
|
-
function partitionUnsafeProjectPathOperations(operations = []) {
|
|
3945
|
-
const safe = [];
|
|
3946
|
-
const skipped = [];
|
|
3947
|
-
for (const operation of operations || []) {
|
|
3948
|
-
const normalized = normalizeOperationProjectPaths(operation);
|
|
3949
|
-
const invalid = getInvalidOperationProjectPath(normalized);
|
|
3950
|
-
if (invalid) {
|
|
3951
|
-
skipped.push({
|
|
3952
|
-
operation: normalized,
|
|
3953
|
-
result: {
|
|
3954
|
-
ok: false,
|
|
3955
|
-
code: 'invalid_project_path',
|
|
3956
|
-
reason: tx(`Invalid ${invalid}. Codex did not write this file.`, `路径无效:${invalid}。Codex 没有写入这个文件。`)
|
|
3957
|
-
}
|
|
3958
|
-
});
|
|
3959
|
-
continue;
|
|
3960
|
-
}
|
|
3961
|
-
safe.push(normalized);
|
|
3962
|
-
}
|
|
3963
|
-
return { safe, skipped };
|
|
3964
|
-
}
|
|
3965
|
-
|
|
3966
|
-
function normalizeOperationProjectPaths(operation = {}) {
|
|
3967
|
-
if (!operation || typeof operation !== 'object') {
|
|
3968
|
-
return operation;
|
|
3969
|
-
}
|
|
3970
|
-
const normalized = { ...operation };
|
|
3971
|
-
if (typeof operation.path === 'string') {
|
|
3972
|
-
normalized.path = normalizeSafeProjectPath(operation.path);
|
|
3973
|
-
if (!normalized.path) {
|
|
3974
|
-
normalized.invalidProjectPath = true;
|
|
3975
|
-
}
|
|
3976
|
-
}
|
|
3977
|
-
if (typeof operation.to === 'string') {
|
|
3978
|
-
normalized.to = normalizeSafeProjectPath(operation.to);
|
|
3979
|
-
if (!normalized.to) {
|
|
3980
|
-
normalized.invalidProjectDestinationPath = true;
|
|
3981
|
-
}
|
|
3982
|
-
}
|
|
3983
|
-
if (typeof operation.destinationPath === 'string') {
|
|
3984
|
-
normalized.destinationPath = normalizeSafeProjectPath(operation.destinationPath);
|
|
3985
|
-
if (!normalized.destinationPath) {
|
|
3986
|
-
normalized.invalidProjectDestinationPath = true;
|
|
3987
|
-
}
|
|
3988
|
-
}
|
|
3989
|
-
return normalized;
|
|
3990
|
-
}
|
|
3991
|
-
|
|
3992
|
-
function getInvalidOperationProjectPath(operation = {}) {
|
|
3993
|
-
if (operation.invalidProjectPath || (requiresOperationPath(operation) && !operation.path)) {
|
|
3994
|
-
return 'operation path';
|
|
3995
|
-
}
|
|
3996
|
-
if (operation.invalidProjectDestinationPath || (requiresOperationDestinationPath(operation) && !(operation.to || operation.destinationPath))) {
|
|
3997
|
-
return 'operation destination path';
|
|
3998
|
-
}
|
|
3999
|
-
return '';
|
|
4000
|
-
}
|
|
4001
|
-
|
|
4002
|
-
function requiresOperationPath(operation = {}) {
|
|
4003
|
-
return ['edit', 'create', 'delete', 'rename', 'move', 'binary-create', 'overwrite-binary'].includes(operation.type);
|
|
4004
|
-
}
|
|
4005
|
-
|
|
4006
|
-
function requiresOperationDestinationPath(operation = {}) {
|
|
4007
|
-
return operation.type === 'rename' || operation.type === 'move';
|
|
4008
|
-
}
|
|
4009
|
-
|
|
4010
4069
|
function normalizeSafeProjectPath(value) {
|
|
4011
4070
|
if (Modules.ProjectFiles?.normalizeSafeProjectPath) {
|
|
4012
4071
|
return Modules.ProjectFiles.normalizeSafeProjectPath(value);
|
|
@@ -4018,157 +4077,9 @@
|
|
|
4018
4077
|
.replace(/^\/+/, '');
|
|
4019
4078
|
}
|
|
4020
4079
|
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
}
|
|
4025
|
-
return GovernanceRules.evaluateGovernedOperations(operations, getGovernanceRulesForCurrentProject());
|
|
4026
|
-
}
|
|
4027
|
-
|
|
4028
|
-
function buildGovernanceSkippedApplyResult(blockedItems = []) {
|
|
4029
|
-
return {
|
|
4030
|
-
ok: blockedItems.length === 0,
|
|
4031
|
-
applied: [],
|
|
4032
|
-
skipped: (blockedItems || []).map(item => ({
|
|
4033
|
-
operation: item.operation,
|
|
4034
|
-
result: {
|
|
4035
|
-
ok: false,
|
|
4036
|
-
code: 'governance_blocked',
|
|
4037
|
-
reason: formatGovernanceBlockedReason(item),
|
|
4038
|
-
reasonKey: item.reason || 'governance_blocked'
|
|
4039
|
-
}
|
|
4040
|
-
}))
|
|
4041
|
-
};
|
|
4042
|
-
}
|
|
4043
|
-
|
|
4044
|
-
function formatGovernanceBlockedReason(item = {}) {
|
|
4045
|
-
if (item.reason === 'readonly') {
|
|
4046
|
-
return tx(
|
|
4047
|
-
'Project governance marked this path read-only, so Codex did not write it.',
|
|
4048
|
-
'项目治理规则将此路径标记为只读,因此 Codex 没有写入。'
|
|
4049
|
-
);
|
|
4050
|
-
}
|
|
4051
|
-
if (item.reason === 'writable_allowlist') {
|
|
4052
|
-
return tx(
|
|
4053
|
-
'Project governance allows writes only to configured writable patterns, and this path is outside that allowlist.',
|
|
4054
|
-
'项目治理规则只允许写入配置的可写路径,此路径不在允许范围内。'
|
|
4055
|
-
);
|
|
4056
|
-
}
|
|
4057
|
-
return tx('Project governance blocked this write.', '项目治理规则阻止了此写入。');
|
|
4058
|
-
}
|
|
4059
|
-
|
|
4060
|
-
function filterSyncChangesByOperations(syncChanges = [], operations = []) {
|
|
4061
|
-
const allowedPaths = new Set((operations || []).map(operation => operation.path).filter(Boolean));
|
|
4062
|
-
return (syncChanges || []).filter(change => allowedPaths.has(change?.path));
|
|
4063
|
-
}
|
|
4064
|
-
|
|
4065
|
-
function mergeApplyResultSkipped(result = {}, skipped = []) {
|
|
4066
|
-
if (!skipped.length) {
|
|
4067
|
-
return result;
|
|
4068
|
-
}
|
|
4069
|
-
return {
|
|
4070
|
-
...(result || {}),
|
|
4071
|
-
ok: false,
|
|
4072
|
-
applied: Array.isArray(result?.applied) ? result.applied : [],
|
|
4073
|
-
skipped: [
|
|
4074
|
-
...getSkippedEntries(result),
|
|
4075
|
-
...skipped
|
|
4076
|
-
]
|
|
4077
|
-
};
|
|
4078
|
-
}
|
|
4079
|
-
|
|
4080
|
-
async function confirmBinaryOperations(operations = []) {
|
|
4081
|
-
const binaryOperations = (operations || []).filter(operation => operation.type === 'binary-create' || operation.type === 'overwrite-binary');
|
|
4082
|
-
if (!binaryOperations.length) {
|
|
4083
|
-
return { operations, skipped: [] };
|
|
4084
|
-
}
|
|
4085
|
-
const approved = await showPluginConfirm({
|
|
4086
|
-
title: tr('binaryAssetConfirmTitle'),
|
|
4087
|
-
message: tr('binaryAssetConfirmMessage', { files: formatOperationFiles(binaryOperations) }),
|
|
4088
|
-
confirmLabel: tr('binaryAssetConfirm'),
|
|
4089
|
-
cancelLabel: tr('binaryAssetCancel'),
|
|
4090
|
-
destructive: true
|
|
4091
|
-
});
|
|
4092
|
-
if (approved) {
|
|
4093
|
-
return { operations, skipped: [] };
|
|
4094
|
-
}
|
|
4095
|
-
return {
|
|
4096
|
-
operations: operations.filter(operation => operation.type !== 'binary-create' && operation.type !== 'overwrite-binary'),
|
|
4097
|
-
skipped: binaryOperations.map(operation => ({
|
|
4098
|
-
operation,
|
|
4099
|
-
result: {
|
|
4100
|
-
ok: false,
|
|
4101
|
-
code: 'binary_confirmation_rejected',
|
|
4102
|
-
reason: tx('Binary asset writeback requires explicit confirmation and was skipped.', '二进制资源写回需要显式确认,已跳过。')
|
|
4103
|
-
}
|
|
4104
|
-
}))
|
|
4105
|
-
};
|
|
4106
|
-
}
|
|
4107
|
-
|
|
4108
|
-
function buildAuditDiffSummary(operations = []) {
|
|
4109
|
-
const changedFiles = new Set();
|
|
4110
|
-
let binaryFilesChanged = 0;
|
|
4111
|
-
for (const operation of operations || []) {
|
|
4112
|
-
if (operation?.path) {
|
|
4113
|
-
changedFiles.add(operation.path);
|
|
4114
|
-
}
|
|
4115
|
-
if (operation?.type === 'binary-create' || operation?.type === 'overwrite-binary') {
|
|
4116
|
-
binaryFilesChanged++;
|
|
4117
|
-
}
|
|
4118
|
-
}
|
|
4119
|
-
return {
|
|
4120
|
-
filesChanged: changedFiles.size,
|
|
4121
|
-
additions: 0,
|
|
4122
|
-
deletions: 0,
|
|
4123
|
-
binaryFilesChanged
|
|
4124
|
-
};
|
|
4125
|
-
}
|
|
4126
|
-
|
|
4127
|
-
function buildAuditSummaryFromApply({ operations = [], applyResults = [], blockedFiles = [], resultStatus = 'completed', saveVerification = null } = {}) {
|
|
4128
|
-
const appliedFiles = [];
|
|
4129
|
-
const skippedFiles = [];
|
|
4130
|
-
for (const result of applyResults || []) {
|
|
4131
|
-
for (const item of getAppliedEntries(result)) {
|
|
4132
|
-
appliedFiles.push(summarizeOperationForAudit(item.operation, item.result, 'applied'));
|
|
4133
|
-
}
|
|
4134
|
-
for (const item of getSkippedEntries(result)) {
|
|
4135
|
-
skippedFiles.push(summarizeOperationForAudit(item.operation, item.result, 'skipped'));
|
|
4136
|
-
}
|
|
4137
|
-
}
|
|
4138
|
-
return {
|
|
4139
|
-
changedFiles: (operations || []).map(operation => summarizeOperationForAudit(operation, {}, 'changed')),
|
|
4140
|
-
['diffSummary']: buildAuditDiffSummary(operations),
|
|
4141
|
-
blockedFiles,
|
|
4142
|
-
appliedFiles,
|
|
4143
|
-
skippedFiles,
|
|
4144
|
-
resultStatus,
|
|
4145
|
-
saveVerification
|
|
4146
|
-
};
|
|
4147
|
-
}
|
|
4148
|
-
|
|
4149
|
-
// Tolerates `operation` and `result` being null in addition to undefined.
|
|
4150
|
-
// Default-parameter values fire only for `undefined`, but the v1.3.8
|
|
4151
|
-
// write-guard (pageBridge.runWriteGuard / writebackRouter.checkWritebackRunProjectId)
|
|
4152
|
-
// emits batch-level skips with `operation: null` — there is no specific
|
|
4153
|
-
// op to attribute the block to. Without this normalization the audit pass
|
|
4154
|
-
// crashed with "Cannot read properties of null (reading 'path')", the
|
|
4155
|
-
// outer-catch swallowed the partial-sync conclusion, and the user saw the
|
|
4156
|
-
// misleading "local Codex returned no usable result" fallback.
|
|
4157
|
-
function summarizeOperationForAudit(operation, result, status = '') {
|
|
4158
|
-
const op = operation || {};
|
|
4159
|
-
const res = result || {};
|
|
4160
|
-
return {
|
|
4161
|
-
path: op.path || op.from || op.to || '',
|
|
4162
|
-
destinationPath: op.destinationPath || op.to || '',
|
|
4163
|
-
type: op.type || '',
|
|
4164
|
-
reason: res.reasonKey || res.code || res.reason || op.reasonKey || op.reason || '',
|
|
4165
|
-
status,
|
|
4166
|
-
size: op.size
|
|
4167
|
-
};
|
|
4168
|
-
}
|
|
4169
|
-
|
|
4170
|
-
function getSyncChangePatches(change = {}) {
|
|
4171
|
-
return writebackController.getSyncChangePatches(change);
|
|
4080
|
+
|
|
4081
|
+
function getSyncChangePatches(change = {}) {
|
|
4082
|
+
return writebackController.getSyncChangePatches(change);
|
|
4172
4083
|
}
|
|
4173
4084
|
|
|
4174
4085
|
function normalizeTextPatches(patches) {
|
|
@@ -4276,6 +4187,17 @@
|
|
|
4276
4187
|
// saveState. Returning null puts the record in degraded mode and excludes
|
|
4277
4188
|
// it from cross-project queries (spec §5.2 storage rules).
|
|
4278
4189
|
window.codexOverleafDeriveAccountScopeId = () => cachedAccountScopeId;
|
|
4190
|
+
var sharedSessionViewSync = Modules.SharedSessionViewSync.install({
|
|
4191
|
+
Bridge: Modules.SharedSessionBridge, StorageDb: Modules.StorageDb,
|
|
4192
|
+
getScope: () => ({ accountScopeId: cachedAccountScopeId, projectId: getCurrentProjectId(), generation: spaRouteGeneration }),
|
|
4193
|
+
getState: () => state, setState: next => { state = next; },
|
|
4194
|
+
isBusy: () => Boolean(readLiveRunViewForSaveStateGuard() || document.querySelector('dialog[open]')),
|
|
4195
|
+
normalize: normalizePanelState, render: applyStateToPanel,
|
|
4196
|
+
notice: error => appendStorageNoticeOnce('shared-history-refresh', tx(
|
|
4197
|
+
`Could not refresh shared history: ${error.message}`,
|
|
4198
|
+
`共享会话历史刷新失败:${error.message}`
|
|
4199
|
+
))
|
|
4200
|
+
});
|
|
4279
4201
|
|
|
4280
4202
|
// -----------------------------------------------------------------------
|
|
4281
4203
|
// SPA route change lifecycle (spec §5.7).
|
|
@@ -4354,7 +4276,7 @@
|
|
|
4354
4276
|
chromeApi: chrome, getState: () => state, setState: next => { state = next; }, getPanel: () => panel,
|
|
4355
4277
|
applyTheme: applyPanelTheme, applyLocale: () => { applyLocaleToPanel(); refreshDashboardSoon(); },
|
|
4356
4278
|
onPreloadChange: enabled => contextTrayController.scheduleContextPrefetch({ enabled }),
|
|
4357
|
-
onSkillsChange: () => { renderLocalSkillList(); updateSkillsEntrySummary(); },
|
|
4279
|
+
onSkillsChange: () => { renderLocalSkillList(); updateSkillsEntrySummary(); updateSlashMenuForTaskInput(); },
|
|
4358
4280
|
onError: error => showPluginToast(tx(
|
|
4359
4281
|
`Global settings could not be saved or loaded: ${error.message}. Please retry.`,
|
|
4360
4282
|
`全局设置未能保存或读取:${error.message}。请重试。`), { status: 'failed', sticky: true })
|
|
@@ -4369,7 +4291,8 @@
|
|
|
4369
4291
|
try {
|
|
4370
4292
|
storageKey = getProjectStorageKey(LEGACY_STORAGE_KEY, window.location.href);
|
|
4371
4293
|
const reloaded = await loadStoredState();
|
|
4372
|
-
state =
|
|
4294
|
+
state = normalizeLoadedPanelState(reloaded);
|
|
4295
|
+
await getRunReviewController().recoverInterruptedUndoStatuses();
|
|
4373
4296
|
applyStateToPanel();
|
|
4374
4297
|
} catch (_error) {
|
|
4375
4298
|
// Reload failures fall back to the in-memory state; the next saveState
|
|
@@ -4431,6 +4354,7 @@
|
|
|
4431
4354
|
function leaveActiveProject(newId) {
|
|
4432
4355
|
const prevId = activeProjectId;
|
|
4433
4356
|
activeProjectId = newId; // null for non-project URLs (spec §5.7.1)
|
|
4357
|
+
getComposerDraftPersistence().syncScope();
|
|
4434
4358
|
if (!prevId || prevId === newId) {
|
|
4435
4359
|
return;
|
|
4436
4360
|
}
|
|
@@ -4471,6 +4395,7 @@
|
|
|
4471
4395
|
// project derivation: account menu is global chrome and should be
|
|
4472
4396
|
// readable on per-project URLs too).
|
|
4473
4397
|
await refreshAccountScopeId();
|
|
4398
|
+
getComposerDraftPersistence().syncScope();
|
|
4474
4399
|
if (generation !== spaRouteGeneration || pathname !== window.location.pathname) {
|
|
4475
4400
|
return;
|
|
4476
4401
|
}
|
|
@@ -4699,7 +4624,7 @@
|
|
|
4699
4624
|
const applied = await applyTaskOperations(project, operations);
|
|
4700
4625
|
applyResults.push(applied);
|
|
4701
4626
|
appendApplyResult(applied);
|
|
4702
|
-
recordUndoFromApply(project, applied);
|
|
4627
|
+
await recordUndoFromApply(project, applied);
|
|
4703
4628
|
const approved = await showPluginConfirm({
|
|
4704
4629
|
title: tx('Confirm delete plan?', '确认删除计划?'),
|
|
4705
4630
|
message: formatDeletePlan(result.deletePlan || []),
|
|
@@ -4713,7 +4638,7 @@
|
|
|
4713
4638
|
const deleteApplied = await applyTaskOperations(project, pendingOperations, { allowHighRisk: true });
|
|
4714
4639
|
applyResults.push(deleteApplied);
|
|
4715
4640
|
appendApplyResult(deleteApplied);
|
|
4716
|
-
recordUndoFromApply(project, deleteApplied);
|
|
4641
|
+
await recordUndoFromApply(project, deleteApplied);
|
|
4717
4642
|
appendLog(tx('Deleted files after confirmation.', '已按确认删除文件。'));
|
|
4718
4643
|
const summaryLine = appendChangeSummary({
|
|
4719
4644
|
notes,
|
|
@@ -4766,7 +4691,7 @@
|
|
|
4766
4691
|
appendOperationsPreview(operations, tx('Preparing to write', '准备写入'));
|
|
4767
4692
|
const applied = await applyTaskOperations(project, operations);
|
|
4768
4693
|
appendApplyResult(applied);
|
|
4769
|
-
recordUndoFromApply(project, applied);
|
|
4694
|
+
await recordUndoFromApply(project, applied);
|
|
4770
4695
|
appendLog(mode === 'auto' ? tx('Auto write task completed.', '自动写入任务完成。') : tx('Task completed.', '任务完成。'));
|
|
4771
4696
|
const summaryLine = appendChangeSummary({
|
|
4772
4697
|
notes,
|
|
@@ -4791,6 +4716,7 @@
|
|
|
4791
4716
|
}
|
|
4792
4717
|
|
|
4793
4718
|
async function applyTaskOperations(project, operations, options = {}) {
|
|
4719
|
+
const target = currentRunView;
|
|
4794
4720
|
const runRequireReviewing = typeof options.requireReviewing === 'boolean'
|
|
4795
4721
|
? options.requireReviewing
|
|
4796
4722
|
: state.requireReviewing === true;
|
|
@@ -4800,7 +4726,13 @@
|
|
|
4800
4726
|
title: tr('undoNoWritesTitle'),
|
|
4801
4727
|
status: 'completed'
|
|
4802
4728
|
});
|
|
4803
|
-
}
|
|
4729
|
+
}
|
|
4730
|
+
const reviewing = await ensureReviewingBeforeWrite(partitioned.safe, { requireReviewing: runRequireReviewing });
|
|
4731
|
+
if (!reviewing.ok) {
|
|
4732
|
+
return buildReviewingBlockedApplyResult(partitioned.safe, reviewing, partitioned.skipped);
|
|
4733
|
+
}
|
|
4734
|
+
if (partitioned.safe.length) await stageWritebackRecovery(partitioned.safe, target, runRequireReviewing, project);
|
|
4735
|
+
if (partitioned.safe.length) {
|
|
4804
4736
|
appendRunEvent({
|
|
4805
4737
|
title: tx('Saved a recoverable version before this run writes.', '已保存本轮写入前的可恢复版本。'),
|
|
4806
4738
|
status: 'completed',
|
|
@@ -4809,10 +4741,7 @@
|
|
|
4809
4741
|
}
|
|
4810
4742
|
});
|
|
4811
4743
|
}
|
|
4812
|
-
|
|
4813
|
-
if (!reviewing.ok) {
|
|
4814
|
-
return buildReviewingBlockedApplyResult(partitioned.safe, reviewing, partitioned.skipped);
|
|
4815
|
-
}
|
|
4744
|
+
|
|
4816
4745
|
const applied = partitioned.safe.length
|
|
4817
4746
|
? await callPageBridge('applyOperations', {
|
|
4818
4747
|
operations: partitioned.safe,
|
|
@@ -4820,7 +4749,7 @@
|
|
|
4820
4749
|
requireReviewing: runRequireReviewing,
|
|
4821
4750
|
requireEditing: !runRequireReviewing,
|
|
4822
4751
|
// Welcome-panel + write-guard:
|
|
4823
|
-
runProjectId:
|
|
4752
|
+
runProjectId: target?.runProjectId || ''
|
|
4824
4753
|
})
|
|
4825
4754
|
: { ok: true, applied: [], skipped: [] };
|
|
4826
4755
|
|
|
@@ -5018,18 +4947,33 @@
|
|
|
5018
4947
|
button.setAttribute('aria-busy', loading ? 'true' : 'false');
|
|
5019
4948
|
}
|
|
5020
4949
|
|
|
5021
|
-
function
|
|
4950
|
+
function syncProbeStatus(mode = state?.mode) {
|
|
4951
|
+
const status = panel?.querySelector('[data-probe-status]');
|
|
4952
|
+
if (!status) return;
|
|
4953
|
+
if (status.dataset.refreshing === 'true') {
|
|
4954
|
+
status.textContent = tr('refreshProbeLoading');
|
|
4955
|
+
return;
|
|
4956
|
+
}
|
|
4957
|
+
if (probeStatusSnapshot?.projectId !== getCurrentProjectId()) return;
|
|
4958
|
+
const { failed, probe, userInitiated } = probeStatusSnapshot;
|
|
4959
|
+
const text = failed ? tr('refreshProbeFailed') : formatProbeStatusBar(probe, mode);
|
|
4960
|
+
status.textContent = userInitiated && !failed ? tr('refreshProbeDone', { status: text }) : text;
|
|
4961
|
+
status.dataset.ok = !failed && isProbeReadyForCurrentMode(probe, mode) ? 'true' : 'false';
|
|
4962
|
+
if (!failed) updateExistingProbeNotice(probe, mode);
|
|
4963
|
+
}
|
|
4964
|
+
|
|
4965
|
+
function formatProbeStatusBar(probe, mode = state?.mode) {
|
|
5022
4966
|
const readiness = getProbeRunReadiness(probe);
|
|
5023
4967
|
const reviewingOk = readiness.reviewingOk;
|
|
5024
4968
|
const editorWritable = readiness.editorWritable;
|
|
5025
4969
|
|
|
5026
|
-
if (
|
|
4970
|
+
if (mode === 'ask') {
|
|
5027
4971
|
return appendOtStatusToProbeStatus(`${formatModeLabel('ask')} · ${readiness.contextLabel}`);
|
|
5028
4972
|
}
|
|
5029
4973
|
if (reviewingOk) {
|
|
5030
4974
|
const status = editorWritable
|
|
5031
4975
|
? `${tr('canRun')} · ${readiness.contextLabel}`
|
|
5032
|
-
: `${formatModeLabel(
|
|
4976
|
+
: `${formatModeLabel(mode)} · ${tr('validatingEditor')}`;
|
|
5033
4977
|
return appendOtStatusToProbeStatus(status);
|
|
5034
4978
|
}
|
|
5035
4979
|
if (readiness.contextReady) {
|
|
@@ -5059,9 +5003,9 @@
|
|
|
5059
5003
|
status.textContent = appendOtStatusToProbeStatus(base);
|
|
5060
5004
|
}
|
|
5061
5005
|
|
|
5062
|
-
function isProbeReadyForCurrentMode(probe) {
|
|
5006
|
+
function isProbeReadyForCurrentMode(probe, mode = state?.mode) {
|
|
5063
5007
|
const readiness = getProbeRunReadiness(probe);
|
|
5064
|
-
return
|
|
5008
|
+
return mode === 'ask' || readiness.reviewingOk;
|
|
5065
5009
|
}
|
|
5066
5010
|
|
|
5067
5011
|
function getProbeRunReadiness(probe) {
|
|
@@ -5126,22 +5070,22 @@
|
|
|
5126
5070
|
appendLog(message);
|
|
5127
5071
|
}
|
|
5128
5072
|
|
|
5129
|
-
function updateExistingProbeNotice(probe) {
|
|
5073
|
+
function updateExistingProbeNotice(probe, mode = state?.mode) {
|
|
5130
5074
|
if (currentRunView || !panel?.querySelector('[data-probe-notice]')) {
|
|
5131
5075
|
return;
|
|
5132
5076
|
}
|
|
5133
|
-
const { ready, message } = formatProbeUserNotice(probe);
|
|
5077
|
+
const { ready, message } = formatProbeUserNotice(probe, mode);
|
|
5134
5078
|
updateProbeNotice(ready ? '' : message);
|
|
5135
5079
|
}
|
|
5136
5080
|
|
|
5137
|
-
function formatProbeUserNotice(probe) {
|
|
5081
|
+
function formatProbeUserNotice(probe, mode = state?.mode) {
|
|
5138
5082
|
const readiness = getProbeRunReadiness(probe);
|
|
5139
5083
|
const reviewingOk = readiness.reviewingOk;
|
|
5140
5084
|
const editorOk = readiness.editorOk;
|
|
5141
5085
|
const manualOverride = probe.reviewing?.status === 'manual-override';
|
|
5142
5086
|
const editorWriteBlocked = probe.capabilities?.editor?.write === false;
|
|
5143
5087
|
|
|
5144
|
-
if (
|
|
5088
|
+
if (mode === 'ask') {
|
|
5145
5089
|
return {
|
|
5146
5090
|
ready: true,
|
|
5147
5091
|
message: tx(
|
|
@@ -5155,8 +5099,8 @@
|
|
|
5155
5099
|
return {
|
|
5156
5100
|
ready: true,
|
|
5157
5101
|
message: tx(
|
|
5158
|
-
`Ready: ${formatModeLabel(
|
|
5159
|
-
`可以运行:已选择“${formatModeLabel(
|
|
5102
|
+
`Ready: ${formatModeLabel(mode)} is selected. This page has not exposed a writable editor yet; Codex will reopen and verify the target file when writing. If writeback fails, reload Overleaf and retry.`,
|
|
5103
|
+
`可以运行:已选择“${formatModeLabel(mode)}”。当前页面暂时没有暴露可写编辑器,写入时会重新打开目标文件并验证;如果写回失败,再刷新 Overleaf 页面后重试。`
|
|
5160
5104
|
)
|
|
5161
5105
|
};
|
|
5162
5106
|
}
|
|
@@ -5209,7 +5153,7 @@
|
|
|
5209
5153
|
focusFiles: getActiveFocusFiles()
|
|
5210
5154
|
});
|
|
5211
5155
|
const sanitizedProject = sanitizeRunProjectSnapshot(project);
|
|
5212
|
-
if (sanitizedProject?.capabilities?.fullProjectSnapshot) {
|
|
5156
|
+
if (sanitizedProject?.capabilities?.fullProjectSnapshot || sanitizedProject?.capabilities?.pathIdentityComplete === false) {
|
|
5213
5157
|
return sanitizedProject;
|
|
5214
5158
|
}
|
|
5215
5159
|
const pageStateProject = await callPageBridge('getProjectSnapshot', {
|
|
@@ -5230,25 +5174,37 @@
|
|
|
5230
5174
|
}
|
|
5231
5175
|
|
|
5232
5176
|
function sanitizeRunProjectSnapshot(project) {
|
|
5233
|
-
if (!project || typeof project !== 'object')
|
|
5234
|
-
|
|
5235
|
-
}
|
|
5236
|
-
const activePath = normalizeSnapshotPath(project.activePath || '');
|
|
5177
|
+
if (!project || typeof project !== 'object') return project;
|
|
5178
|
+
const normalize = Modules.ProjectFiles.normalizeSafeProjectPath;
|
|
5237
5179
|
const files = Array.isArray(project.files) ? project.files : [];
|
|
5238
|
-
const
|
|
5239
|
-
const
|
|
5240
|
-
|
|
5241
|
-
|
|
5180
|
+
const counts = new Map();
|
|
5181
|
+
for (const file of files) {
|
|
5182
|
+
const path = normalize(file?.path);
|
|
5183
|
+
if (path) counts.set(path, (counts.get(path) || 0) + 1);
|
|
5242
5184
|
}
|
|
5185
|
+
const skipped = [];
|
|
5186
|
+
const safeFiles = files.filter(file => {
|
|
5187
|
+
const path = normalize(file?.path);
|
|
5188
|
+
if (path && counts.get(path) === 1) return true;
|
|
5189
|
+
skipped.push({ path: typeof file?.path === 'string' ? file.path : '',
|
|
5190
|
+
reason: path ? 'snapshot_path_duplicate' : 'snapshot_path_invalid' });
|
|
5191
|
+
return false;
|
|
5192
|
+
});
|
|
5193
|
+
const activePath = normalize(project.activePath || '');
|
|
5194
|
+
const activePathKnown = activePath && safeFiles.some(file => file.path === activePath);
|
|
5195
|
+
const rejectActive = Boolean(project.activePath && !activePathKnown);
|
|
5196
|
+
if (!skipped.length && !rejectActive) return project;
|
|
5243
5197
|
return {
|
|
5244
5198
|
...project,
|
|
5245
|
-
activePath: '',
|
|
5246
|
-
files:
|
|
5199
|
+
activePath: rejectActive ? '' : project.activePath,
|
|
5200
|
+
files: safeFiles.map(file => rejectActive && file?.active ? { ...file, active: false } : file),
|
|
5247
5201
|
capabilities: {
|
|
5248
5202
|
...(project.capabilities || {}),
|
|
5203
|
+
...(skipped.length ? { fullProjectSnapshot: false, contentComplete: false, pathIdentityComplete: false,
|
|
5204
|
+
skipped: [...(project.capabilities?.skipped || []), ...skipped] } : {}),
|
|
5249
5205
|
diagnostics: {
|
|
5250
5206
|
...(project.capabilities?.diagnostics || {}),
|
|
5251
|
-
rejectedActivePath: project.activePath
|
|
5207
|
+
...(rejectActive ? { rejectedActivePath: project.activePath } : {})
|
|
5252
5208
|
}
|
|
5253
5209
|
}
|
|
5254
5210
|
};
|
|
@@ -5334,11 +5290,8 @@
|
|
|
5334
5290
|
}
|
|
5335
5291
|
|
|
5336
5292
|
function normalizeSnapshotPath(path) {
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
.replace(/\\/g, '/')
|
|
5340
|
-
.trim()
|
|
5341
|
-
.replace(/^\/+/, '');
|
|
5293
|
+
// Snapshot/overlay/focus arrays already carry canonical names from file chips.
|
|
5294
|
+
return Modules.ProjectFiles.normalizeSafeProjectPath(path);
|
|
5342
5295
|
}
|
|
5343
5296
|
|
|
5344
5297
|
function mergeProjectWithSyncChangeBaseFiles(project = {}, syncChanges = []) {
|
|
@@ -5489,6 +5442,9 @@
|
|
|
5489
5442
|
}
|
|
5490
5443
|
|
|
5491
5444
|
async function callPageBridge(method, params) {
|
|
5445
|
+
if (method === 'applyOperations' && params?.reviewingPolicy === 'no-trace-undo') {
|
|
5446
|
+
return Modules.AssetTransferBroker.create({ callPageBridge: (name, input) => pageBridgeClient.call(name, input), sendBackgroundNative }).applyOperations(params);
|
|
5447
|
+
}
|
|
5492
5448
|
return pageBridgeClient.call(method, params);
|
|
5493
5449
|
}
|
|
5494
5450
|
|
|
@@ -5547,8 +5503,10 @@
|
|
|
5547
5503
|
legacyKey,
|
|
5548
5504
|
accountScopeId
|
|
5549
5505
|
);
|
|
5506
|
+
const composerAttachments = await loadComposerAttachmentsForProject(accountScopeId, projectId);
|
|
5550
5507
|
|
|
5551
5508
|
return {
|
|
5509
|
+
composerAttachments,
|
|
5552
5510
|
model: prefs.model || '', providerId: prefs.providerId || 'builtin',
|
|
5553
5511
|
reasoningEffort: prefs.reasoningEffort || '',
|
|
5554
5512
|
speedTier: prefs.speedTier || '',
|
|
@@ -5560,6 +5518,7 @@
|
|
|
5560
5518
|
loadCodexLocalSkills: prefs.loadCodexLocalSkills !== false,
|
|
5561
5519
|
loadCodexOverleafSkills: prefs.loadCodexOverleafSkills !== false,
|
|
5562
5520
|
codexOverleafSkillEnabled: prefs.codexOverleafSkillEnabled || {},
|
|
5521
|
+
codexLocalSkillEnabled: prefs.codexLocalSkillEnabled || {},
|
|
5563
5522
|
experimentalOtByProject: prefs.experimentalOtByProject || {},
|
|
5564
5523
|
customInstructionsByProject: prefs.customInstructionsByProject || {},
|
|
5565
5524
|
governanceRulesByProject: normalizeGovernanceRulesByProject(prefs.governanceRulesByProject),
|
|
@@ -5569,9 +5528,11 @@
|
|
|
5569
5528
|
accountScopeId: session.accountScopeId || accountScopeId,
|
|
5570
5529
|
title: session.title || '',
|
|
5571
5530
|
titleSource: session.titleSource || 'auto',
|
|
5572
|
-
focusFiles: session.focusFiles || [],
|
|
5531
|
+
focusFiles: session.focusFiles || [], selectionContext: session.selectionContext?.projectId === projectId ? session.selectionContext : null,
|
|
5573
5532
|
projectReferenceFiles: session.projectReferenceFiles || [],
|
|
5574
5533
|
codexThreadId: session.codexThreadId || '',
|
|
5534
|
+
forkedFromSessionId: session.forkedFromSessionId || '',
|
|
5535
|
+
forkedFromRunId: session.forkedFromRunId || '',
|
|
5575
5536
|
createdAt: session.createdAt,
|
|
5576
5537
|
updatedAt: session.updatedAt,
|
|
5577
5538
|
runs: Array.isArray(session.runs) ? session.runs : [],
|
|
@@ -5612,6 +5573,7 @@
|
|
|
5612
5573
|
loadCodexLocalSkills: fallback.loadCodexLocalSkills !== false,
|
|
5613
5574
|
loadCodexOverleafSkills: fallback.loadCodexOverleafSkills !== false,
|
|
5614
5575
|
codexOverleafSkillEnabled: fallback.codexOverleafSkillEnabled || {},
|
|
5576
|
+
codexLocalSkillEnabled: fallback.codexLocalSkillEnabled || {},
|
|
5615
5577
|
experimentalOtByProject: fallback.experimentalOtByProject || {},
|
|
5616
5578
|
customInstructionsByProject: fallback.customInstructionsByProject || {},
|
|
5617
5579
|
governanceRulesByProject: normalizeGovernanceRulesByProject(fallback.governanceRulesByProject),
|
|
@@ -5640,33 +5602,21 @@
|
|
|
5640
5602
|
}
|
|
5641
5603
|
|
|
5642
5604
|
async function saveState(options) {
|
|
5605
|
+
const endSharedSave = typeof sharedSessionViewSync !== 'undefined'
|
|
5606
|
+
? sharedSessionViewSync?.beginSave() : null;
|
|
5643
5607
|
try {
|
|
5644
5608
|
const StorageDb = Modules.StorageDb;
|
|
5645
5609
|
const Migration = Modules.StorageMigration;
|
|
5646
|
-
//
|
|
5647
|
-
//
|
|
5648
|
-
//
|
|
5649
|
-
// is in flight on a different project (mid-run SPA navigation), saving
|
|
5650
|
-
// under that URL-derived id would pollute another project's Recent-
|
|
5651
|
-
// projects entry and surface bogus disabled rows. Two safety lanes:
|
|
5652
|
-
// 1. `options.projectIdOverride` lets a caller that legitimately
|
|
5653
|
-
// needs to persist to a non-active project supply the projectId
|
|
5654
|
-
// explicitly (the only such caller today is the post-navigation
|
|
5655
|
-
// settlement path in `persistPostNavigationRunStatus`, which goes
|
|
5656
|
-
// directly through StorageDb — but the override is in place so
|
|
5657
|
-
// future callers do not have to monkey-patch around saveState).
|
|
5658
|
-
// 2. When no override is set AND a navigation-divergent run is in
|
|
5659
|
-
// flight (currentRunView.runProjectId !== URL-derived id), log a
|
|
5660
|
-
// warning and skip persistence. The settlement path owns that
|
|
5661
|
-
// run's persistence; routing through saveState would write to the
|
|
5662
|
-
// WRONG projectId record.
|
|
5610
|
+
// Settlement owns a run after navigation away from its project. Ordinary
|
|
5611
|
+
// view saves must not write that run into the current route's project.
|
|
5612
|
+
// An explicit override is reserved for callers that own a detached scope.
|
|
5663
5613
|
const projectIdOverride = options && typeof options.projectIdOverride === 'string'
|
|
5664
5614
|
? options.projectIdOverride
|
|
5665
5615
|
: '';
|
|
5666
5616
|
const urlProjectId = getCurrentProjectId();
|
|
5667
5617
|
const projectId = projectIdOverride || urlProjectId;
|
|
5668
5618
|
if (!projectId) {
|
|
5669
|
-
if (options?.reviewRunIds?.length) throw new Error('
|
|
5619
|
+
if (options?.requireDurable || options?.reviewRunIds?.length) throw new Error('Persistence lost its project.'); return;
|
|
5670
5620
|
}
|
|
5671
5621
|
// Read currentRunView through a defensive accessor so historical test
|
|
5672
5622
|
// harnesses that inline `saveState` without re-declaring the module-
|
|
@@ -5678,13 +5628,14 @@
|
|
|
5678
5628
|
'Skipped saveState: run is in flight on a different project than the current URL; settlement owns persistence.',
|
|
5679
5629
|
'已跳过 saveState:本次运行所属项目与当前 URL 不一致,写回由 settlement 负责。'
|
|
5680
5630
|
));
|
|
5681
|
-
if (options?.reviewRunIds?.length) throw new Error('
|
|
5631
|
+
if (options?.requireDurable || options?.reviewRunIds?.length) throw new Error('Persistence project changed.'); return;
|
|
5682
5632
|
}
|
|
5683
5633
|
const compactState = prepareStateForStorage(state, { onAggressive: notifyAggressiveCompactionOnce });
|
|
5684
5634
|
compactState.autoRecompile = state.autoRecompile;
|
|
5685
5635
|
compactState.loadCodexLocalSkills = state.loadCodexLocalSkills !== false;
|
|
5686
5636
|
compactState.loadCodexOverleafSkills = state.loadCodexOverleafSkills !== false;
|
|
5687
5637
|
compactState.codexOverleafSkillEnabled = getCodexOverleafSkillEnabled();
|
|
5638
|
+
compactState.codexLocalSkillEnabled = getCodexLocalSkillEnabled();
|
|
5688
5639
|
compactState.experimentalOtByProject = state.experimentalOtByProject;
|
|
5689
5640
|
compactState.customInstructionsByProject = state.customInstructionsByProject;
|
|
5690
5641
|
compactState.governanceRulesByProject = state.governanceRulesByProject;
|
|
@@ -5695,11 +5646,14 @@
|
|
|
5695
5646
|
? structuredClone(compactState)
|
|
5696
5647
|
: JSON.parse(JSON.stringify(compactState));
|
|
5697
5648
|
|
|
5698
|
-
const
|
|
5649
|
+
const retentionScope = { accountScopeId: typeof cachedAccountScopeId === 'string' ? cachedAccountScopeId : '', projectId, generation: typeof spaRouteGeneration === 'number' ? spaRouteGeneration : null };
|
|
5650
|
+
let written;
|
|
5651
|
+
const persistScopedState = async persistenceContext => {
|
|
5652
|
+
written = await Modules.ScopedPersistenceCoordinator.persistPanelState({
|
|
5699
5653
|
state: stateSnapshot,
|
|
5700
5654
|
compactState: compactStateSnapshot,
|
|
5701
5655
|
projectId,
|
|
5702
|
-
deletedSessionIds: options?.deletedSessionIds, reviewRunIds: options?.reviewRunIds,
|
|
5656
|
+
deletedSessionIds: options?.deletedSessionIds, reviewRunIds: options?.reviewRunIds, recoveryRuns: options?.recoveryRuns,
|
|
5703
5657
|
Migration,
|
|
5704
5658
|
StorageDb,
|
|
5705
5659
|
SessionPersistence: Modules.SessionPersistence,
|
|
@@ -5707,7 +5661,9 @@
|
|
|
5707
5661
|
normalizeGovernanceRulesByProject,
|
|
5708
5662
|
normalizeCustomInstructionsByProject,
|
|
5709
5663
|
persistenceContext
|
|
5710
|
-
|
|
5664
|
+
});
|
|
5665
|
+
await getComposerDraftPersistence().save({ accountScopeId: persistenceContext.scope.accountScopeId, projectId });
|
|
5666
|
+
};
|
|
5711
5667
|
|
|
5712
5668
|
const persistenceCoordinator = getScopedPersistenceCoordinator();
|
|
5713
5669
|
if (!persistenceCoordinator) {
|
|
@@ -5727,7 +5683,7 @@
|
|
|
5727
5683
|
persistScopedState
|
|
5728
5684
|
);
|
|
5729
5685
|
if (!committed.ok && (
|
|
5730
|
-
options?.reviewRunIds?.length || options?.queueMutation
|
|
5686
|
+
options?.requireDurable || options?.reviewRunIds?.length || options?.recoveryRuns?.length || options?.queueMutation
|
|
5731
5687
|
|| options?.queueMutations?.length
|
|
5732
5688
|
|| committed.reason !== 'stale_view'
|
|
5733
5689
|
)) {
|
|
@@ -5735,8 +5691,23 @@
|
|
|
5735
5691
|
code: committed.reason
|
|
5736
5692
|
});
|
|
5737
5693
|
}
|
|
5694
|
+
if (committed.ok) {
|
|
5695
|
+
const retentionUpdates = StorageDb.projectRetentionUpdates?.({ liveState: state, capturedState: stateSnapshot, written,
|
|
5696
|
+
capturedScope: retentionScope, currentScope: { accountScopeId: typeof cachedAccountScopeId === 'string' ? cachedAccountScopeId : '',
|
|
5697
|
+
projectId: getCurrentProjectId(), generation: typeof spaRouteGeneration === 'number' ? spaRouteGeneration : null } }) || [];
|
|
5698
|
+
for (const update of retentionUpdates) for (const run of [...(state.runs || []), ...(state.sessions || []).flatMap(session => session.runs || [])]) {
|
|
5699
|
+
if (run.id !== update.runId) continue;
|
|
5700
|
+
if (update.retention) run.recoveryRetention = update.retention; else delete run.recoveryRetention;
|
|
5701
|
+
}
|
|
5702
|
+
if (retentionUpdates.some(update => update.retention)) appendStorageNoticeOnce('undo-retention', tx(
|
|
5703
|
+
'Some recovery evidence was not retained durably. After reloading, check Overleaf history or Review.',
|
|
5704
|
+
'部分恢复材料未持久保留;刷新后请核对 Overleaf 历史或审阅。'));
|
|
5705
|
+
}
|
|
5706
|
+
if (options?.requireDurable || options?.recoveryRuns?.length) return true;
|
|
5738
5707
|
}
|
|
5739
5708
|
} catch (error) {
|
|
5709
|
+
if (options?.requireDurable) throw error;
|
|
5710
|
+
if (error?.code === 'recovery_scope_changed') return;
|
|
5740
5711
|
if (
|
|
5741
5712
|
error?.code === 'account_scope_unavailable' &&
|
|
5742
5713
|
!options?.reviewRunIds?.length && !options?.queueMutation &&
|
|
@@ -5764,19 +5735,21 @@
|
|
|
5764
5735
|
emitStorageQuotaFailure(error, fallbackError);
|
|
5765
5736
|
}
|
|
5766
5737
|
if (typeof appendStorageNoticeOnce === 'function') {
|
|
5767
|
-
appendStorageNoticeOnce('save-failed', tx(`Failed to save session state: ${error.message}`, `保存会话状态失败:${error.message}`));
|
|
5738
|
+
appendStorageNoticeOnce('save-failed', tx(`Failed to save session state: ${localizeVisibleReason(error.message)}`, `保存会话状态失败:${localizeVisibleReason(error.message)}`));
|
|
5768
5739
|
} else {
|
|
5769
5740
|
throw error;
|
|
5770
5741
|
}
|
|
5742
|
+
} finally {
|
|
5743
|
+
endSharedSave?.();
|
|
5771
5744
|
}
|
|
5772
5745
|
}
|
|
5773
5746
|
|
|
5774
|
-
|
|
5775
5747
|
function prepareCompactFallbackState(inputState) {
|
|
5776
5748
|
const compact = prepareStateForStorage(inputState);
|
|
5777
5749
|
return {
|
|
5778
5750
|
...compact,
|
|
5779
5751
|
__codexOverleafCompactFallback: true,
|
|
5752
|
+
composerAttachments: [],
|
|
5780
5753
|
task: '',
|
|
5781
5754
|
session: null,
|
|
5782
5755
|
sessions: [],
|
|
@@ -5869,7 +5842,7 @@
|
|
|
5869
5842
|
// here (e.g. fallback chrome.storage.local.set rethrew).
|
|
5870
5843
|
emitStorageQuotaFailure(error, null);
|
|
5871
5844
|
}
|
|
5872
|
-
if (persistenceOptions?.reviewRunIds?.length) throw error;
|
|
5845
|
+
if (persistenceOptions?.requireDurable || persistenceOptions?.reviewRunIds?.length) throw error;
|
|
5873
5846
|
appendPlainLog(tx(`Failed to save session state: ${formatStateSaveError(error)}`, `保存会话状态失败:${formatStateSaveError(error)}`));
|
|
5874
5847
|
})
|
|
5875
5848
|
.finally(() => {
|
|
@@ -5945,7 +5918,7 @@
|
|
|
5945
5918
|
if (isStorageQuotaError(error)) {
|
|
5946
5919
|
return tx('Local session history is too large. Delete some old tasks and retry.', '本地会话记录太大,请删除一些旧任务后重试。');
|
|
5947
5920
|
}
|
|
5948
|
-
return error?.message || String(error);
|
|
5921
|
+
return localizeVisibleReason(error?.message || String(error));
|
|
5949
5922
|
}
|
|
5950
5923
|
|
|
5951
5924
|
async function persistPanelInputs(event) {
|
|
@@ -6004,6 +5977,7 @@
|
|
|
6004
5977
|
button.dataset.active = active ? 'true' : 'false';
|
|
6005
5978
|
button.setAttribute('aria-pressed', active ? 'true' : 'false');
|
|
6006
5979
|
});
|
|
5980
|
+
syncProbeStatus(currentMode);
|
|
6007
5981
|
}
|
|
6008
5982
|
|
|
6009
5983
|
function readPanelInputs() {
|
|
@@ -6056,18 +6030,59 @@
|
|
|
6056
6030
|
return speedSelect?.value || state?.speedTier || 'standard';
|
|
6057
6031
|
}
|
|
6058
6032
|
|
|
6059
|
-
|
|
6060
|
-
let composerAttachmentsRestoredOnce = false;
|
|
6033
|
+
let composerDraftPersistence = null;
|
|
6061
6034
|
let lastRunActivityBumpMs = 0;
|
|
6062
6035
|
|
|
6036
|
+
function getComposerDraftPersistence() {
|
|
6037
|
+
if (!composerDraftPersistence) composerDraftPersistence = Modules.ComposerAttachmentDraft.createController({
|
|
6038
|
+
getScope: () => ({ accountScopeId: cachedAccountScopeId, projectId: getCurrentProjectId() }),
|
|
6039
|
+
getAttachments: () => composerAttachmentController.serializePersistableAttachments(),
|
|
6040
|
+
setAttachments: value => { state.composerAttachments = value; },
|
|
6041
|
+
clear: () => composerAttachmentController.clear({ notify: false }),
|
|
6042
|
+
restore: value => composerAttachmentController.restorePersistedAttachments(value),
|
|
6043
|
+
read: scope => Modules.StorageDb.readComposerAttachmentDraft(scope),
|
|
6044
|
+
write: (scope, value) => Modules.StorageDb.writeComposerAttachmentDraft(scope, value),
|
|
6045
|
+
scheduleSave: () => saveStateSoon()
|
|
6046
|
+
});
|
|
6047
|
+
return composerDraftPersistence;
|
|
6048
|
+
}
|
|
6049
|
+
|
|
6050
|
+
async function loadComposerAttachmentsForProject(accountScopeId, projectId) {
|
|
6051
|
+
if (!accountScopeId || !projectId) return [];
|
|
6052
|
+
try {
|
|
6053
|
+
return await getComposerDraftPersistence().load({ accountScopeId, projectId });
|
|
6054
|
+
} catch (error) {
|
|
6055
|
+
if (error?.code === 'stale_hydration' || error?.code === 'account_scope_unavailable') throw error;
|
|
6056
|
+
appendPlainLog(tx('Saved attachments could not be restored. Reattach the files before sending.',
|
|
6057
|
+
'已保存的附件无法恢复,请在发送前重新添加文件。'));
|
|
6058
|
+
return [];
|
|
6059
|
+
}
|
|
6060
|
+
}
|
|
6061
|
+
|
|
6063
6062
|
function persistComposerAttachmentsToState() {
|
|
6064
6063
|
if (typeof composerAttachmentController?.serializePersistableAttachments === 'function') {
|
|
6065
6064
|
state.composerAttachments = composerAttachmentController.serializePersistableAttachments();
|
|
6066
6065
|
}
|
|
6067
6066
|
}
|
|
6068
6067
|
|
|
6069
|
-
function clearTaskComposer({ keepAttachments = false } = {}) {
|
|
6068
|
+
function clearTaskComposer({ keepAttachments = false, submission = null } = {}) {
|
|
6070
6069
|
const taskInput = panel?.querySelector('[data-task]');
|
|
6070
|
+
if (submission) {
|
|
6071
|
+
if (submission.projectId !== getCurrentProjectId()
|
|
6072
|
+
|| submission.accountScopeId !== cachedAccountScopeId) return false;
|
|
6073
|
+
if (findSessionById(submission.sessionId)?.task !== submission.task) return false;
|
|
6074
|
+
if (state.activeSessionId !== submission.sessionId) {
|
|
6075
|
+
updateSessionById(submission.sessionId, { task: '' });
|
|
6076
|
+
saveStateSoon();
|
|
6077
|
+
return false;
|
|
6078
|
+
}
|
|
6079
|
+
if (composerAttachmentController.getAttachmentByteUsage?.().pending > 0
|
|
6080
|
+
|| selectionContextView.getRevision?.() !== submission.selectionRevision) return false;
|
|
6081
|
+
if (!runController.isSubmittedComposerUnchanged(submission, {
|
|
6082
|
+
task: taskInput?.value, attachments: getComposerAttachmentsForRun(),
|
|
6083
|
+
skillInvocation: getComposerSkillInvocationForRun(), selectionContext: selectionContextView.capture()
|
|
6084
|
+
})) return false;
|
|
6085
|
+
}
|
|
6071
6086
|
if (taskInput) {
|
|
6072
6087
|
taskInput.value = '';
|
|
6073
6088
|
}
|
|
@@ -6077,11 +6092,38 @@
|
|
|
6077
6092
|
}
|
|
6078
6093
|
composerSkillInvocation = null;
|
|
6079
6094
|
renderComposerSkillInvocation();
|
|
6095
|
+
selectionContextView.clear();
|
|
6080
6096
|
state = updateActiveSession(state, { task: '' });
|
|
6081
6097
|
saveStateSoon();
|
|
6098
|
+
return true;
|
|
6099
|
+
}
|
|
6100
|
+
|
|
6101
|
+
function restoreRejectedLocalSkillSubmission(submission, checkpoint) {
|
|
6102
|
+
const input = panel?.querySelector('[data-task]');
|
|
6103
|
+
if (!checkpoint || !input || submission.accountScopeId !== cachedAccountScopeId
|
|
6104
|
+
|| submission.projectId !== getCurrentProjectId() || submission.sessionId !== state.activeSessionId
|
|
6105
|
+
|| !findSessionById(submission.sessionId) || input.value || state.task
|
|
6106
|
+
|| composerDraftRevision !== checkpoint.draftRevision
|
|
6107
|
+
|| getComposerAttachmentsForRun().length || composerAttachmentController.getAttachmentByteUsage?.().pending
|
|
6108
|
+
|| composerAttachmentController.getRevision?.() !== checkpoint.attachmentRevision
|
|
6109
|
+
|| selectionContextView.getRevision?.() !== checkpoint.selectionRevision
|
|
6110
|
+
|| selectionContextView.capture() || getComposerSkillInvocationForRun()) return false;
|
|
6111
|
+
if (!composerAttachmentController.restoreSubmittedAttachments?.(submission.attachments, checkpoint.attachmentRevision)) return false;
|
|
6112
|
+
composerDraftRevision += 1;
|
|
6113
|
+
composerSkillInvocation = submission.skillInvocation;
|
|
6114
|
+
input.value = submission.task;
|
|
6115
|
+
state = updateActiveSession(state, { task: submission.task, selectionContext: submission.selectionContext });
|
|
6116
|
+
renderComposerSkillInvocation();
|
|
6117
|
+
selectionContextView.render();
|
|
6118
|
+
autosizeTaskTextarea();
|
|
6119
|
+
syncComposerSendAvailability();
|
|
6120
|
+
saveStateSoon();
|
|
6121
|
+
return true;
|
|
6082
6122
|
}
|
|
6083
6123
|
|
|
6084
6124
|
function applyStateToPanel(options = {}) {
|
|
6125
|
+
selectionContextView.render();
|
|
6126
|
+
writingStyleSettings.sync();
|
|
6085
6127
|
applyPanelTheme(getThemePreference());
|
|
6086
6128
|
if ((state.providerId || 'builtin') === 'builtin' && !modelSelectHasOption(state.model)) {
|
|
6087
6129
|
renderModelOptions(getModelCatalog().FALLBACK_MODELS, state.model);
|
|
@@ -6118,23 +6160,14 @@
|
|
|
6118
6160
|
applySessionLabel();
|
|
6119
6161
|
renderSessionList();
|
|
6120
6162
|
renderRunHistory(options);
|
|
6163
|
+
renderPendingInputs();
|
|
6121
6164
|
getTrackedChangeCaptureController().schedule();
|
|
6122
6165
|
renderContextSelection();
|
|
6123
6166
|
renderContextSummary();
|
|
6124
|
-
|
|
6125
|
-
// restored into the (empty) tray ONCE per page load, before the render
|
|
6126
|
-
// below. Re-running the gate on later renders would resurrect files the
|
|
6127
|
-
// user explicitly removed (fleet P2).
|
|
6128
|
-
if (!composerAttachmentsRestoredOnce
|
|
6129
|
-
&& Array.isArray(state.composerAttachments) && state.composerAttachments.length
|
|
6130
|
-
&& !composerAttachmentController.getAttachmentsForRun?.().length
|
|
6131
|
-
&& typeof composerAttachmentController.restorePersistedAttachments === 'function') {
|
|
6132
|
-
composerAttachmentController.restorePersistedAttachments(state.composerAttachments);
|
|
6133
|
-
}
|
|
6134
|
-
composerAttachmentsRestoredOnce = true;
|
|
6167
|
+
getComposerDraftPersistence().hydrate(state.composerAttachments);
|
|
6135
6168
|
renderComposerAttachments();
|
|
6136
6169
|
renderComposerSkillInvocation();
|
|
6137
|
-
applyLocaleToPanel();
|
|
6170
|
+
applyLocaleToPanel({ reprojectDynamic: false });
|
|
6138
6171
|
if (!panel.querySelector('[data-context-tray]')?.hidden) {
|
|
6139
6172
|
renderContextFiles();
|
|
6140
6173
|
}
|
|
@@ -6159,21 +6192,45 @@
|
|
|
6159
6192
|
}
|
|
6160
6193
|
panel.dataset.running = running ? 'true' : 'false';
|
|
6161
6194
|
panel.dataset.cancelling = running && runCancellationRequested ? 'true' : 'false';
|
|
6195
|
+
panel.querySelectorAll('[data-run-id]').forEach(root => {
|
|
6196
|
+
const record = findRunRecord(root.dataset.runId);
|
|
6197
|
+
if (record) configureEditButton(root, record, { running });
|
|
6198
|
+
if (record) configureWritebackButton(root, record, { running });
|
|
6199
|
+
});
|
|
6162
6200
|
renderPendingInputs();
|
|
6163
6201
|
}
|
|
6164
6202
|
|
|
6165
|
-
function prepareRetryReplacement(run) {
|
|
6203
|
+
function prepareRetryReplacement(run, task) {
|
|
6166
6204
|
const sessionId = state?.activeSessionId || '';
|
|
6167
6205
|
const source = findRunRecord(run?.id, sessionId);
|
|
6168
|
-
const eligible = source &&
|
|
6206
|
+
const eligible = source && !source.forkSnapshot
|
|
6207
|
+
&& (!source.runProjectId || source.runProjectId === getCurrentProjectId())
|
|
6208
|
+
&& runController.isRetryReplacementEligible(
|
|
6169
6209
|
source,
|
|
6170
6210
|
WritebackSettlement.projectRunSettlement(source)
|
|
6171
6211
|
);
|
|
6172
|
-
|
|
6212
|
+
const returnToDraft = eligible && !currentRunView
|
|
6213
|
+
&& ['cancelled', 'rejected'].includes(source.status);
|
|
6214
|
+
const draft = { task: typeof task === 'string' ? task : String(run?.task || '') };
|
|
6215
|
+
if (returnToDraft) {
|
|
6216
|
+
const snapshot = source.executionSnapshot || {};
|
|
6217
|
+
const mode = snapshot.mode || source.mode;
|
|
6218
|
+
draft.runs = (state.runs || []).filter(item => item.id !== source.id);
|
|
6219
|
+
draft.mode = ['ask', 'auto'].includes(mode) ? mode : state.mode;
|
|
6220
|
+
draft.focusFiles = Array.isArray(snapshot.focusFiles) ? snapshot.focusFiles : state.focusFiles;
|
|
6221
|
+
draft.selectionContext = snapshot.selectionContext || null;
|
|
6222
|
+
}
|
|
6223
|
+
// Persist the composer through its session owner, not only the top-level alias.
|
|
6224
|
+
state = updateActiveSession(state, draft);
|
|
6225
|
+
pendingRetryReplacement = eligible && !returnToDraft ? { sessionId, runId: source.id } : null;
|
|
6226
|
+
if (returnToDraft) applyStateToPanel({ preserveScroll: true });
|
|
6173
6227
|
return Boolean(eligible);
|
|
6174
6228
|
}
|
|
6175
6229
|
|
|
6176
6230
|
function startRunView({
|
|
6231
|
+
sessionId = state.activeSessionId,
|
|
6232
|
+
sessionContext = runController.captureSessionContext(state),
|
|
6233
|
+
codexThreadId = '',
|
|
6177
6234
|
task,
|
|
6178
6235
|
mode,
|
|
6179
6236
|
model,
|
|
@@ -6214,41 +6271,39 @@
|
|
|
6214
6271
|
executionSnapshot: executionSnapshot
|
|
6215
6272
|
? RunExecutionSnapshot.cloneSnapshot(executionSnapshot)
|
|
6216
6273
|
: undefined,
|
|
6217
|
-
//
|
|
6218
|
-
// Immutable per-run capture of the project this run was submitted
|
|
6219
|
-
// against. Every writeback / accept / undo dispatch attaches this id
|
|
6220
|
-
// to its page-bridge params so the page-side guard can verify the run
|
|
6221
|
-
// is still acting on the same project before mutating the editor. No
|
|
6222
|
-
// language-level enforcement — the field is treated as immutable by
|
|
6223
|
-
// convention and never reassigned anywhere downstream.
|
|
6274
|
+
// Writeback/Accept/Undo carry this immutable project identity to Page.
|
|
6224
6275
|
runProjectId: getCurrentProjectId() || '',
|
|
6225
6276
|
runAccountScopeId: cachedAccountScopeId || ''
|
|
6226
6277
|
};
|
|
6227
|
-
const active =
|
|
6278
|
+
const active = findSessionById(sessionId);
|
|
6228
6279
|
const titlePatch = active?.titleSource !== 'manual'
|
|
6229
6280
|
? {
|
|
6230
6281
|
title: deriveSessionTitle([], task),
|
|
6231
6282
|
titleSource: 'auto'
|
|
6232
6283
|
}
|
|
6233
6284
|
: {};
|
|
6234
|
-
|
|
6235
|
-
runs: runController.replaceRunForRetry(
|
|
6285
|
+
updateSessionById(sessionId, {
|
|
6286
|
+
runs: runController.replaceRunForRetry(active?.runs, replaceRunId, record),
|
|
6236
6287
|
...titlePatch
|
|
6237
6288
|
});
|
|
6238
6289
|
saveStateSoon(replaceRunId ? 0 : 120);
|
|
6239
6290
|
|
|
6240
6291
|
const log = panel.querySelector('[data-log]');
|
|
6241
|
-
removeEmptyRunsMessage(log);
|
|
6242
|
-
if (replaceRunId) log.querySelector(`[data-run-id="${cssEscape(replaceRunId)}"]`)?.remove();
|
|
6243
6292
|
const root = renderRunCard(record);
|
|
6244
|
-
|
|
6245
|
-
|
|
6293
|
+
if (state.activeSessionId === sessionId) {
|
|
6294
|
+
removeEmptyRunsMessage(log);
|
|
6295
|
+
if (replaceRunId) log.querySelector(`[data-run-id="${cssEscape(replaceRunId)}"]`)?.remove();
|
|
6296
|
+
log.append(root);
|
|
6297
|
+
scrollLogToBottom({ force: true });
|
|
6298
|
+
}
|
|
6246
6299
|
startRunElapsedTick();
|
|
6247
6300
|
renderSessionList();
|
|
6248
6301
|
applySessionLabel();
|
|
6249
6302
|
|
|
6250
6303
|
return {
|
|
6251
|
-
sessionId
|
|
6304
|
+
sessionId,
|
|
6305
|
+
sessionContext,
|
|
6306
|
+
codexThreadId,
|
|
6252
6307
|
recordId: record.id,
|
|
6253
6308
|
// Mirror the immutable runProjectId on the view so writeback /
|
|
6254
6309
|
// accept / undo dispatches don't need to re-look up the record.
|
|
@@ -6346,6 +6401,17 @@
|
|
|
6346
6401
|
return;
|
|
6347
6402
|
}
|
|
6348
6403
|
|
|
6404
|
+
const owner = currentRunView && findRunRecord(currentRunView.recordId, currentRunView.sessionId);
|
|
6405
|
+
if (owner && Modules.SubagentActivityModel.ingest(owner, event, {
|
|
6406
|
+
locale: getLocale(), sanitize: sanitizeAssistantVisibleText, nativeEventSeq: journalSeq
|
|
6407
|
+
})) {
|
|
6408
|
+
owner.nativeEventSeq = Math.max(Number(owner.nativeEventSeq || 0), Number(journalSeq || 0));
|
|
6409
|
+
scheduleRunStateSave('stream');
|
|
6410
|
+
const visible = getCurrentRunViewForRender();
|
|
6411
|
+
if (visible) runTimelineView.refreshActivitySummary?.(visible);
|
|
6412
|
+
return;
|
|
6413
|
+
}
|
|
6414
|
+
|
|
6349
6415
|
const activity = mapAgentEventToActivity(event, { locale: getLocale() });
|
|
6350
6416
|
if (!activity?.visible || activity.kind === 'technical') {
|
|
6351
6417
|
return;
|
|
@@ -6415,9 +6481,11 @@
|
|
|
6415
6481
|
kind: input.kind || 'activity',
|
|
6416
6482
|
subagent: input.subagent === true ? true : undefined,
|
|
6417
6483
|
technicalDetail: sanitizeAssistantVisibleValue(input.technicalDetail),
|
|
6484
|
+
activity: Modules.RunActivityModel.capture(input, sanitizeAssistantVisibleText),
|
|
6418
6485
|
guidanceId: sanitizeAssistantVisibleText(input.guidanceId),
|
|
6419
6486
|
streamKey: sanitizeAssistantVisibleText(input.streamKey),
|
|
6420
6487
|
streamRole: sanitizeAssistantVisibleText(input.streamRole),
|
|
6488
|
+
streamPhase: sanitizeAssistantVisibleText(input.streamPhase),
|
|
6421
6489
|
appendText: typeof input.appendText === 'string' ? sanitizeAssistantVisibleText(input.appendText) : input.appendText,
|
|
6422
6490
|
replaceText: input.replaceText,
|
|
6423
6491
|
// Structured FailureReason (§7) attached to the event when the caller
|
|
@@ -6531,6 +6599,7 @@
|
|
|
6531
6599
|
}
|
|
6532
6600
|
|
|
6533
6601
|
function appendRunEventToView(view, event) {
|
|
6602
|
+
const projected = event.kind !== 'stream' && runTimelineView.refreshActivitySummary?.(view, event);
|
|
6534
6603
|
if (event.kind === 'report') {
|
|
6535
6604
|
view.report.hidden = false;
|
|
6536
6605
|
const record = view?.recordId ? findRunRecord(view.recordId, view.sessionId) : null;
|
|
@@ -6541,6 +6610,7 @@
|
|
|
6541
6610
|
if (event.kind === 'technical') {
|
|
6542
6611
|
return;
|
|
6543
6612
|
}
|
|
6613
|
+
if (projected) { bumpUnreadIfDetached(); return; }
|
|
6544
6614
|
if (runGuidanceController.appendToView(event, view)) return;
|
|
6545
6615
|
if (event.kind === 'stream') {
|
|
6546
6616
|
// Stream deltas update an existing line in place — they must not inflate
|
|
@@ -6548,7 +6618,7 @@
|
|
|
6548
6618
|
upsertStreamEvent(view, event);
|
|
6549
6619
|
return;
|
|
6550
6620
|
}
|
|
6551
|
-
|
|
6621
|
+
runTimelineView.appendActivityDetail(view, renderRunEvent(event), event);
|
|
6552
6622
|
bumpUnreadIfDetached();
|
|
6553
6623
|
}
|
|
6554
6624
|
|
|
@@ -6559,6 +6629,7 @@
|
|
|
6559
6629
|
if (existing) {
|
|
6560
6630
|
const nextTitle = sanitizeAssistantVisibleText(event.title);
|
|
6561
6631
|
const nextRole = sanitizeAssistantVisibleText(event.streamRole) || existing.streamRole;
|
|
6632
|
+
if (event.streamPhase) existing.streamPhase = event.streamPhase;
|
|
6562
6633
|
const combinedTitle = event.replaceText
|
|
6563
6634
|
? nextTitle
|
|
6564
6635
|
: sanitizeAssistantVisibleText(appendStreamText(existing.title, nextTitle));
|
|
@@ -6618,10 +6689,6 @@
|
|
|
6618
6689
|
}
|
|
6619
6690
|
|
|
6620
6691
|
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
6692
|
function applyPanelWidth(width, options = {}) {
|
|
6626
6693
|
const nextWidth = PanelRenderer.setWidth(panelRendererInstance, width, { notify: false });
|
|
6627
6694
|
if (state) {
|
|
@@ -6633,10 +6700,6 @@
|
|
|
6633
6700
|
return nextWidth;
|
|
6634
6701
|
}
|
|
6635
6702
|
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
6703
|
function getCurrentProjectMathSources() {
|
|
6641
6704
|
const sources = [];
|
|
6642
6705
|
const seen = new Set();
|
|
@@ -6799,277 +6862,34 @@
|
|
|
6799
6862
|
);
|
|
6800
6863
|
}
|
|
6801
6864
|
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
if (!
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
await undoRunTrackedChanges(runId, run);
|
|
6817
|
-
return;
|
|
6818
|
-
}
|
|
6819
|
-
|
|
6820
|
-
const undoRestore = buildNoTraceUndoRestore(run);
|
|
6821
|
-
const undoOperations = undoRestore.operations;
|
|
6822
|
-
const unsafeFullFileUndo = findUnsafeFullFileUndoOperation(undoOperations, {
|
|
6823
|
-
allowSnapshotRestore: undoRestore.snapshotRestore
|
|
6824
|
-
});
|
|
6825
|
-
if (unsafeFullFileUndo) {
|
|
6826
|
-
appendRunRecordEvent(runId, {
|
|
6827
|
-
title: tr('undoUnsafeFullFileTitle'),
|
|
6828
|
-
status: 'failed',
|
|
6829
|
-
detail: {
|
|
6830
|
-
[tr('detailFile')]: unsafeFullFileUndo.path,
|
|
6831
|
-
[tr('detailReason')]: tr('undoUnsafeFullFileReason')
|
|
6832
|
-
}
|
|
6833
|
-
});
|
|
6834
|
-
return;
|
|
6835
|
-
}
|
|
6836
|
-
|
|
6837
|
-
// v1.8.0 C3: multi-file undos confirm through a per-file selection —
|
|
6838
|
-
// undoing a subset leaves undoStatus 'partial' and the button usable;
|
|
6839
|
-
// re-undoing an already-restored file is skipped by the base check.
|
|
6840
|
-
const undoPaths = Array.from(new Set(undoOperations.map(operation => operation?.path).filter(Boolean)));
|
|
6841
|
-
let selectedOperations = undoOperations;
|
|
6842
|
-
if (undoPaths.length > 1 && typeof showUndoFileSelection === 'function') {
|
|
6843
|
-
const selectedPaths = await showUndoFileSelection({
|
|
6844
|
-
title: tr('undoFileSelectTitle'),
|
|
6845
|
-
task: truncateRunTitle(run.task),
|
|
6846
|
-
confirmLabel: tr('undoFileSelectConfirm'),
|
|
6847
|
-
cancelLabel: tr('confirmDefaultCancel'),
|
|
6848
|
-
selectAllLabel: tr('undoFileSelectAll'),
|
|
6849
|
-
paths: undoPaths
|
|
6850
|
-
});
|
|
6851
|
-
if (!selectedPaths) {
|
|
6852
|
-
return;
|
|
6853
|
-
}
|
|
6854
|
-
const selectedSet = new Set(selectedPaths);
|
|
6855
|
-
selectedOperations = undoOperations.filter(operation => selectedSet.has(operation?.path));
|
|
6856
|
-
} else {
|
|
6857
|
-
const approved = await showPluginConfirm({
|
|
6858
|
-
title: tr('undoNoTraceTitle'),
|
|
6859
|
-
message: [
|
|
6860
|
-
truncateRunTitle(run.task),
|
|
6861
|
-
'',
|
|
6862
|
-
tr('undoNoTraceMessage', { files: formatOperationFiles(undoOperations) })
|
|
6863
|
-
].join('\n'),
|
|
6864
|
-
confirmLabel: tr('undoConfirm'),
|
|
6865
|
-
cancelLabel: tr('confirmDefaultCancel'),
|
|
6866
|
-
destructive: true
|
|
6867
|
-
});
|
|
6868
|
-
if (!approved) {
|
|
6869
|
-
return;
|
|
6870
|
-
}
|
|
6871
|
-
}
|
|
6872
|
-
|
|
6873
|
-
setRunUndoStatus(runId, 'running');
|
|
6874
|
-
appendRunRecordEvent(runId, {
|
|
6875
|
-
title: tr('undoNoTraceStarted'),
|
|
6876
|
-
status: 'running',
|
|
6877
|
-
detail: { [tr('detailWillUndo')]: formatOperationFiles(selectedOperations) }
|
|
6865
|
+
let runReviewController = null;
|
|
6866
|
+
function getRunReviewController() {
|
|
6867
|
+
if (!runReviewController) runReviewController = Modules.RunReviewController.create({
|
|
6868
|
+
Modules, WritebackSettlement, trackedChangeInFlight, findRunRecord,
|
|
6869
|
+
getRunUndoCount, isTrackedChangeLifecycleRun, getPendingMirrorRefresh, hasTrackedEditorUndo, buildNoTraceUndoRestore,
|
|
6870
|
+
findUnsafeFullFileUndoOperation, showUndoFileSelection, showPluginConfirm, tr,
|
|
6871
|
+
truncateRunTitle, formatOperationFiles, formatTrackedChangeFiles, formatTrackedUndoFiles,
|
|
6872
|
+
getPageBridgeTimeoutMs, callPageBridge, setRunUndoStatus, appendRunRecordEvent, getRunProjectIdForWriteback,
|
|
6873
|
+
isUndoResultEffectivelyApplied, writebackOrchestrator, appendUndoReviewingPolicyEvent, formatOperationType,
|
|
6874
|
+
formatApplyResultReason, buildTrackedUndoPostFiles, getTrackedChangeCaptureController, buildContentFailure,
|
|
6875
|
+
applyTrackedChangeSettlement, refreshRunCardControls, formatBridgeResultReason, advanceRunUndoProgress,
|
|
6876
|
+
saveStateSoon, flushQueuedSaveState,
|
|
6877
|
+
getState: () => state, getCurrentProjectId, getAccountScopeId: () => cachedAccountScopeId,
|
|
6878
|
+
getRouteGeneration: () => spaRouteGeneration, tx, notify: message => showPluginToast(message, { status: 'warning', sticky: true })
|
|
6878
6879
|
});
|
|
6880
|
+
return runReviewController;
|
|
6881
|
+
}
|
|
6879
6882
|
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
baseFiles: run.undoBaseFiles || [],
|
|
6883
|
-
reviewingPolicy: 'no-trace-undo',
|
|
6884
|
-
// Welcome-panel + write-guard:
|
|
6885
|
-
// route the undo through the same project-ID guard. The undo is
|
|
6886
|
-
// bound to the run's original project, not the editor's active one.
|
|
6887
|
-
runProjectId: getRunProjectIdForWriteback(run)
|
|
6888
|
-
});
|
|
6889
|
-
const undoApplied = isUndoResultEffectivelyApplied(run, result);
|
|
6890
|
-
writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
|
|
6891
|
-
appendUndoReviewingPolicyEvent(runId, result.reviewingPolicy);
|
|
6892
|
-
appendRunRecordEvent(runId, {
|
|
6893
|
-
title: tr('undoResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
|
|
6894
|
-
status: undoApplied ? 'completed' : result.skipped?.length ? 'failed' : 'completed',
|
|
6895
|
-
detail: {
|
|
6896
|
-
[tr('detailUndone')]: (result.applied || []).map(item => ({
|
|
6897
|
-
[tr('detailAction')]: formatOperationType(item.operation?.type),
|
|
6898
|
-
[tr('detailFile')]: item.operation?.path
|
|
6899
|
-
})),
|
|
6900
|
-
[tr('detailSkipped')]: (result.skipped || []).map(item => ({
|
|
6901
|
-
[tr('detailAction')]: formatOperationType(item.operation?.type),
|
|
6902
|
-
[tr('detailFile')]: item.operation?.path,
|
|
6903
|
-
[tr('detailReason')]: formatApplyResultReason(item)
|
|
6904
|
-
}))
|
|
6905
|
-
}
|
|
6906
|
-
});
|
|
6907
|
-
// A subset undo never closes the ledger: unselected files can still
|
|
6908
|
-
// be undone later, so the run stays 'partial' and the button usable.
|
|
6909
|
-
const fullSelection = selectedOperations.length === undoOperations.length;
|
|
6910
|
-
applyLegacyUndoSettlement(
|
|
6911
|
-
runId,
|
|
6912
|
-
undoApplied && fullSelection ? 'applied' : 'partial',
|
|
6913
|
-
result
|
|
6914
|
-
);
|
|
6883
|
+
async function undoRun(runId) {
|
|
6884
|
+
return getRunReviewController().undoRun(runId);
|
|
6915
6885
|
}
|
|
6916
6886
|
|
|
6917
6887
|
async function undoRunTrackedChanges(runId, run) {
|
|
6918
|
-
|
|
6919
|
-
const approved = await showPluginConfirm({
|
|
6920
|
-
title: trackedUndo ? tr('undoTrackedTitle') : tr('undoNativeTitle'),
|
|
6921
|
-
message: [
|
|
6922
|
-
truncateRunTitle(run.task),
|
|
6923
|
-
'',
|
|
6924
|
-
trackedUndo
|
|
6925
|
-
? tr('undoTrackedMessage', { files: formatTrackedChangeFiles(run.undoTrackedChanges) })
|
|
6926
|
-
: tr('undoNativeMessage', { files: formatTrackedUndoFiles(run) })
|
|
6927
|
-
].join('\n'),
|
|
6928
|
-
confirmLabel: trackedUndo ? tr('undoTrackedConfirm') : tr('undoConfirm'),
|
|
6929
|
-
cancelLabel: tr('confirmDefaultCancel'),
|
|
6930
|
-
destructive: true
|
|
6931
|
-
});
|
|
6932
|
-
if (!approved) {
|
|
6933
|
-
return;
|
|
6934
|
-
}
|
|
6935
|
-
|
|
6936
|
-
// Tracked-change-lifecycle runs (those holding tracked-change refs) reach a
|
|
6937
|
-
// decisive terminal `rejected` once the reject returns. The UI-local
|
|
6938
|
-
// in-flight lock disables the button; it is never persisted.
|
|
6939
|
-
const lifecycleReject = trackedUndo && isTrackedChangeLifecycleRun(run);
|
|
6940
|
-
if (lifecycleReject) {
|
|
6941
|
-
trackedChangeInFlight.set(runId, 'reject');
|
|
6942
|
-
refreshRunCardControls(runId);
|
|
6943
|
-
} else {
|
|
6944
|
-
setRunUndoStatus(runId, 'running');
|
|
6945
|
-
}
|
|
6946
|
-
appendRunRecordEvent(runId, {
|
|
6947
|
-
title: trackedUndo ? tr('undoTrackedStarted') : tr('undoNativeStarted'),
|
|
6948
|
-
status: 'running',
|
|
6949
|
-
detail: { [tr('detailWillUndo')]: trackedUndo ? formatTrackedChangeFiles(run.undoTrackedChanges) : formatTrackedUndoFiles(run) }
|
|
6950
|
-
});
|
|
6951
|
-
|
|
6952
|
-
try {
|
|
6953
|
-
const trackedUndoPostFilesAtDispatch = buildTrackedUndoPostFiles(run);
|
|
6954
|
-
const result = await callPageBridge('rejectTrackedChanges', {
|
|
6955
|
-
trackedChanges: run.undoTrackedChanges || [],
|
|
6956
|
-
expectedFiles: run.undoExpectedFiles || [],
|
|
6957
|
-
postFiles: trackedUndoPostFilesAtDispatch,
|
|
6958
|
-
untrackedUndo: run.executionSnapshot
|
|
6959
|
-
? run.executionSnapshot.requireReviewing === false && ['submitted', 'legacy-captured'].includes(run.executionSnapshot.source)
|
|
6960
|
-
: getTrackedChangeCaptureController().hasLegacyUntrackedCheckpoint(run),
|
|
6961
|
-
// Welcome-panel + write-guard:
|
|
6962
|
-
// bind the reject to the run's original project. If the user has
|
|
6963
|
-
// navigated away the page-side guard refuses with
|
|
6964
|
-
// `aborted_project_changed` and the document is left untouched.
|
|
6965
|
-
runProjectId: getRunProjectIdForWriteback(run)
|
|
6966
|
-
});
|
|
6967
|
-
writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
|
|
6968
|
-
appendRunRecordEvent(runId, {
|
|
6969
|
-
title: trackedUndo
|
|
6970
|
-
? tr('undoTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 })
|
|
6971
|
-
: tr('undoNativeResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
|
|
6972
|
-
status: result.skipped?.length ? 'failed' : 'completed',
|
|
6973
|
-
detail: {
|
|
6974
|
-
[trackedUndo ? tr('detailRejected') : tr('detailUndone')]: (result.applied || []).map(item => ({
|
|
6975
|
-
[tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
|
|
6976
|
-
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
|
|
6977
|
-
})),
|
|
6978
|
-
[tr('detailSkipped')]: (result.skipped || []).map(item => ({
|
|
6979
|
-
[tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
|
|
6980
|
-
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || '',
|
|
6981
|
-
[tr('detailReason')]: formatBridgeResultReason(item.result, item.trackedChange?.path)
|
|
6982
|
-
}))
|
|
6983
|
-
}
|
|
6984
|
-
});
|
|
6985
|
-
if (lifecycleReject) {
|
|
6986
|
-
WritebackSettlement.attachUndoNotVerifiedFailure(run, result, {
|
|
6987
|
-
buildFailure: buildContentFailure,
|
|
6988
|
-
postFiles: trackedUndoPostFilesAtDispatch
|
|
6989
|
-
});
|
|
6990
|
-
await applyTrackedChangeSettlement(runId, 'reject', result);
|
|
6991
|
-
return;
|
|
6992
|
-
}
|
|
6993
|
-
applyLegacyUndoSettlement(runId, result.skipped?.length ? 'partial' : 'applied', result);
|
|
6994
|
-
} finally {
|
|
6995
|
-
if (lifecycleReject) {
|
|
6996
|
-
trackedChangeInFlight.delete(runId);
|
|
6997
|
-
refreshRunCardControls(runId);
|
|
6998
|
-
}
|
|
6999
|
-
}
|
|
6888
|
+
return getRunReviewController().undoRunTrackedChanges(runId, run);
|
|
7000
6889
|
}
|
|
7001
6890
|
|
|
7002
6891
|
async function acceptRun(runId) {
|
|
7003
|
-
|
|
7004
|
-
if (!run || !isTrackedChangeLifecycleRun(run)) {
|
|
7005
|
-
return;
|
|
7006
|
-
}
|
|
7007
|
-
const status = run.trackedChangeStatus || '';
|
|
7008
|
-
// pending and needs_review are both actionable per §7: needs_review means
|
|
7009
|
-
// the prior attempt could not prove a clean post-action state, and the
|
|
7010
|
-
// user is supposed to be able to retry after inspecting Overleaf.
|
|
7011
|
-
if (status !== 'pending' && status !== 'needs_review') {
|
|
7012
|
-
return;
|
|
7013
|
-
}
|
|
7014
|
-
if (!Array.isArray(run.undoTrackedChanges) || !run.undoTrackedChanges.length) {
|
|
7015
|
-
return;
|
|
7016
|
-
}
|
|
7017
|
-
if (trackedChangeInFlight.get(runId)) {
|
|
7018
|
-
return;
|
|
7019
|
-
}
|
|
7020
|
-
|
|
7021
|
-
trackedChangeInFlight.set(runId, 'accept');
|
|
7022
|
-
refreshRunCardControls(runId);
|
|
7023
|
-
appendRunRecordEvent(runId, {
|
|
7024
|
-
title: tr('runAcceptTrackedStarted'),
|
|
7025
|
-
status: 'running',
|
|
7026
|
-
detail: { [tr('detailAccepted')]: formatTrackedChangeFiles(run.undoTrackedChanges) }
|
|
7027
|
-
});
|
|
7028
|
-
|
|
7029
|
-
try {
|
|
7030
|
-
const result = await callPageBridge('acceptTrackedChanges', {
|
|
7031
|
-
trackedChanges: run.undoTrackedChanges || [],
|
|
7032
|
-
expectedFiles: run.undoExpectedFiles || [],
|
|
7033
|
-
postFiles: buildTrackedUndoPostFiles(run),
|
|
7034
|
-
appliedOperations: Array.isArray(run.appliedOperations) ? run.appliedOperations : [],
|
|
7035
|
-
runProjectId: getRunProjectIdForWriteback(run)
|
|
7036
|
-
});
|
|
7037
|
-
appendAcceptDiagnosticEvents(runId, Array.isArray(result.diagnostics) ? result.diagnostics : []);
|
|
7038
|
-
appendRunRecordEvent(runId, {
|
|
7039
|
-
title: tr('runAcceptTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
|
|
7040
|
-
status: result.skipped?.length ? 'failed' : 'completed',
|
|
7041
|
-
detail: {
|
|
7042
|
-
[tr('detailAccepted')]: (result.applied || []).map(item => ({
|
|
7043
|
-
[tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
|
|
7044
|
-
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
|
|
7045
|
-
})),
|
|
7046
|
-
[tr('detailSkipped')]: (result.skipped || []).map(item => ({
|
|
7047
|
-
[tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
|
|
7048
|
-
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || '',
|
|
7049
|
-
[tr('detailReason')]: formatBridgeResultReason(item.result, item.trackedChange?.path)
|
|
7050
|
-
}))
|
|
7051
|
-
}
|
|
7052
|
-
});
|
|
7053
|
-
if (result.ok === false) {
|
|
7054
|
-
appendRunRecordEvent(runId, {
|
|
7055
|
-
title: tr('runAcceptTrackedFailed'),
|
|
7056
|
-
status: 'failed',
|
|
7057
|
-
detail: {
|
|
7058
|
-
[tr('detailReason')]: tr('runAcceptTrackedFailedReason')
|
|
7059
|
-
}
|
|
7060
|
-
});
|
|
7061
|
-
if (!WritebackSettlement.isSuccessfulTrackedChangeSettlement(result)) {
|
|
7062
|
-
refreshRunCardControls(runId);
|
|
7063
|
-
}
|
|
7064
|
-
}
|
|
7065
|
-
WritebackSettlement.attachAcceptNotVerifiedFailure(run, result, {
|
|
7066
|
-
buildFailure: buildContentFailure
|
|
7067
|
-
});
|
|
7068
|
-
await applyTrackedChangeSettlement(runId, 'accept', result);
|
|
7069
|
-
} finally {
|
|
7070
|
-
trackedChangeInFlight.delete(runId);
|
|
7071
|
-
refreshRunCardControls(runId);
|
|
7072
|
-
}
|
|
6892
|
+
return getRunReviewController().acceptRun(runId);
|
|
7073
6893
|
}
|
|
7074
6894
|
|
|
7075
6895
|
function getRunProjectIdForWriteback(run) {
|
|
@@ -7078,32 +6898,6 @@
|
|
|
7078
6898
|
: '';
|
|
7079
6899
|
}
|
|
7080
6900
|
|
|
7081
|
-
// Translates the page bridge's Accept All `diagnostics` array into one
|
|
7082
|
-
// run-card event row per step. Each entry is `{ step, info }`; the step
|
|
7083
|
-
// names map 1:1 to the runAcceptTrackedStep* i18n keys. We render `info`
|
|
7084
|
-
// as the event detail so the user can copy-paste the full trace without
|
|
7085
|
-
// any extra page-state query.
|
|
7086
|
-
function appendAcceptDiagnosticEvents(runId, diagnostics) {
|
|
7087
|
-
for (const entry of diagnostics || []) {
|
|
7088
|
-
const step = entry && entry.step;
|
|
7089
|
-
const info = (entry && entry.info) || {};
|
|
7090
|
-
const titleKey = WritebackSettlement.ACCEPT_DIAGNOSTIC_TITLE_KEYS[step];
|
|
7091
|
-
if (!titleKey) {
|
|
7092
|
-
continue;
|
|
7093
|
-
}
|
|
7094
|
-
const title = titleKey === 'runAcceptTrackedStepReplayStart' || titleKey === 'runAcceptTrackedStepReplayDone'
|
|
7095
|
-
? tr(titleKey, { path: info.path || tr('unknownFile') })
|
|
7096
|
-
: tr(titleKey);
|
|
7097
|
-
const status = WritebackSettlement.acceptDiagnosticStatus(step, info);
|
|
7098
|
-
appendRunRecordEvent(runId, {
|
|
7099
|
-
title,
|
|
7100
|
-
kind: 'activity',
|
|
7101
|
-
status,
|
|
7102
|
-
detail: info
|
|
7103
|
-
});
|
|
7104
|
-
}
|
|
7105
|
-
}
|
|
7106
|
-
|
|
7107
6901
|
function buildContentFailure(code, operation, overrides) {
|
|
7108
6902
|
return WritebackSettlement.buildContentFailure(
|
|
7109
6903
|
code,
|
|
@@ -7113,35 +6907,206 @@
|
|
|
7113
6907
|
);
|
|
7114
6908
|
}
|
|
7115
6909
|
|
|
7116
|
-
async function applyTrackedChangeSettlement(runId, kind, result) {
|
|
7117
|
-
const
|
|
6910
|
+
async function applyTrackedChangeSettlement(runId, kind, result, options = {}) {
|
|
6911
|
+
const record = findRunRecord(runId);
|
|
6912
|
+
const run = kind === 'reject' ? advanceRunUndoProgress(record, result) : record;
|
|
7118
6913
|
const settlement = WritebackSettlement.settleTrackedChangeLifecycle({
|
|
7119
6914
|
kind,
|
|
7120
6915
|
run,
|
|
7121
6916
|
result,
|
|
7122
6917
|
failureReasons: FailureReasons
|
|
7123
6918
|
});
|
|
7124
|
-
if (settlement.decision === 'blocked'
|
|
6919
|
+
if (settlement.decision === 'blocked'
|
|
6920
|
+
&& !(kind === 'reject' && (run !== record || record?.undoOperations?.some(Modules.UndoOperations.isFileUndoOperation)))) {
|
|
7125
6921
|
return;
|
|
7126
6922
|
}
|
|
7127
6923
|
await Modules.ScopedPersistenceCoordinator.applyReviewTransition({
|
|
7128
6924
|
getState: () => state, runId, transition: WritebackSettlement.applySettlementTransition(run, settlement),
|
|
7129
|
-
persist: flushQueuedSaveState
|
|
6925
|
+
persist: flushQueuedSaveState, assertCurrent: options.assertCurrent
|
|
7130
6926
|
});
|
|
7131
6927
|
}
|
|
7132
6928
|
|
|
6929
|
+
|
|
6930
|
+
function advanceRunUndoProgress(run, result) {
|
|
6931
|
+
getDirectoryCleanupRecovery().capture({ recordId: run.id, sessionId: state.activeSessionId,
|
|
6932
|
+
runProjectId: run.runProjectId, runAccountScopeId: run.runAccountScopeId }, result);
|
|
6933
|
+
const postFiles = buildTrackedUndoPostFiles(run);
|
|
6934
|
+
return Modules.UndoOperations.advanceUndoCheckpoint(run, result, file =>
|
|
6935
|
+
WritebackSettlement.isUndoVerifiedContentMatching({ undoExpectedFiles: [file] }, result, { postFiles })
|
|
6936
|
+
);
|
|
6937
|
+
}
|
|
7133
6938
|
function getRunUndoCount(run) {
|
|
7134
6939
|
if (!run) {
|
|
7135
6940
|
return 0;
|
|
7136
6941
|
}
|
|
7137
6942
|
const trackedEditorUndoCount = hasTrackedEditorUndo(run) ? 1 : 0;
|
|
7138
6943
|
if (trackedEditorUndoCount) {
|
|
7139
|
-
return trackedEditorUndoCount;
|
|
6944
|
+
return trackedEditorUndoCount + Modules.UndoOperations.getFileUndoOperations(run).length;
|
|
7140
6945
|
}
|
|
7141
|
-
return (run.undoOperations
|
|
6946
|
+
return Modules.UndoOperations.prepareRunUndo(run).undoOperations.length
|
|
7142
6947
|
+ (run.undoTrackedChanges?.length || 0);
|
|
7143
6948
|
}
|
|
7144
6949
|
|
|
6950
|
+
function recoveryOwner(target) {
|
|
6951
|
+
if (!target.runProjectId || target.runProjectId !== getCurrentProjectId() || !cachedAccountScopeId) {
|
|
6952
|
+
throw new Error('Writeback recovery lost its project or account identity.');
|
|
6953
|
+
}
|
|
6954
|
+
return { projectId: target.runProjectId, accountScopeId: cachedAccountScopeId,
|
|
6955
|
+
sessionId: target.sessionId, runId: target.recordId, createdAt: new Date().toISOString() };
|
|
6956
|
+
}
|
|
6957
|
+
|
|
6958
|
+
let directoryCleanupRecovery = null;
|
|
6959
|
+
function getDirectoryCleanupRecovery() {
|
|
6960
|
+
return directoryCleanupRecovery || (directoryCleanupRecovery = Modules.DirectoryCleanupRecovery.create({
|
|
6961
|
+
getProjectId: getCurrentProjectId, getAccountScopeId: () => cachedAccountScopeId,
|
|
6962
|
+
getRouteGeneration: () => spaRouteGeneration, findRunRecord, refresh: refreshRunCardControls,
|
|
6963
|
+
onOmitted: (runId, code) => appendRunRecordEvent(runId, { kind: 'technical', status: 'warning',
|
|
6964
|
+
title: tx('Some folder observations exceeded the storage limit; existing observations were kept.',
|
|
6965
|
+
'部分文件夹观测记录超过存储上限,已有记录已保留。'), detail: { code } })
|
|
6966
|
+
}));
|
|
6967
|
+
}
|
|
6968
|
+
async function stageWritebackRecovery(operations, target, requireReviewing) {
|
|
6969
|
+
const owner = recoveryOwner(target), generation = spaRouteGeneration, view = currentRunView;
|
|
6970
|
+
const assertCurrent = () => {
|
|
6971
|
+
const record = findRunRecord(target.recordId, target.sessionId);
|
|
6972
|
+
if (owner.projectId !== getCurrentProjectId() || owner.accountScopeId !== cachedAccountScopeId
|
|
6973
|
+
|| generation !== spaRouteGeneration || view !== currentRunView || !view
|
|
6974
|
+
|| view.recordId !== owner.runId || view.sessionId !== owner.sessionId
|
|
6975
|
+
|| (target.runAccountScopeId && target.runAccountScopeId !== owner.accountScopeId)
|
|
6976
|
+
|| !record || record.forkSnapshot || record.runProjectId !== owner.projectId) {
|
|
6977
|
+
throw new Error('The project, account or writeback run changed before dispatch.');
|
|
6978
|
+
}
|
|
6979
|
+
return record;
|
|
6980
|
+
};
|
|
6981
|
+
const record = assertCurrent();
|
|
6982
|
+
const creates = operations.filter(op => op.type === 'create' && typeof op.content === 'string');
|
|
6983
|
+
record.retryRequireReviewing = requireReviewing === true;
|
|
6984
|
+
if (!creates.length) return;
|
|
6985
|
+
const candidate = Modules.WritebackIntent.normalize({ ...owner, id: crypto.randomUUID(),
|
|
6986
|
+
operations: creates, baseFiles: [], requireReviewing });
|
|
6987
|
+
const safe = Modules.SessionState.pickWritebackRecovery({ ...record, retryWriteback: candidate }).retryWriteback;
|
|
6988
|
+
if (safe) record.retryWriteback = safe; else delete record.retryWriteback;
|
|
6989
|
+
await flushQueuedSaveState();
|
|
6990
|
+
assertCurrent();
|
|
6991
|
+
}
|
|
6992
|
+
|
|
6993
|
+
async function settleWritebackRecovery(applied, project, verification, target) {
|
|
6994
|
+
const record = findRunRecord(target.recordId, target.sessionId);
|
|
6995
|
+
if (!record) return;
|
|
6996
|
+
const owner = recoveryOwner(target);
|
|
6997
|
+
const skippedPaths = new Set((applied.skipped || []).map(entry => entry.operation?.path));
|
|
6998
|
+
const creates = (record.retryWriteback?.operations || []).filter(op => skippedPaths.has(op.path));
|
|
6999
|
+
// Untouched edits (never opened / rejected) and deletes whose server check failed are safe to replay.
|
|
7000
|
+
const edits = (applied.skipped || []).filter(({ operation: op, result }) => result?.failure?.changedDocument === false && ((op?.type === 'edit'
|
|
7001
|
+
&& (result.failure.stage === 'navigation' || result.failure.code === 'write_operation_failed')) || (op?.type === 'delete' && result.failure.code === 'source_zip_unavailable'))).map(entry => entry.operation);
|
|
7002
|
+
const base = { ...(record.retryWriteback || { ...owner, id: crypto.randomUUID(), requireReviewing: record.retryRequireReviewing === true }),
|
|
7003
|
+
baseFiles: project?.files || [], requestId: applied.receiptUnconfirmed ? applied.requestId || '' : '' };
|
|
7004
|
+
// An oversized edit pre-image must not cost the create retries (the intent has a 300 KiB ceiling).
|
|
7005
|
+
const safeRetry = [[...creates, ...edits], creates].filter(ops => ops.length).map(ops => Modules.SessionState
|
|
7006
|
+
.pickWritebackRecovery({ ...record, retryWriteback: Modules.WritebackIntent.normalize({ ...base, operations: ops }) }).retryWriteback).find(Boolean);
|
|
7007
|
+
if (safeRetry) record.retryWriteback = safeRetry; else delete record.retryWriteback;
|
|
7008
|
+
delete record.retryRequireReviewing;
|
|
7009
|
+
const next = await writebackController.buildSaveCheck(applied, project, owner);
|
|
7010
|
+
const files = new Map((record.saveCheck?.files || []).map(file => [file.path, file]));
|
|
7011
|
+
for (const file of next?.files || []) {
|
|
7012
|
+
if (verification.state === 'verified_saved') files.delete(file.path);
|
|
7013
|
+
else files.set(file.path, file);
|
|
7014
|
+
}
|
|
7015
|
+
if (files.size) record.saveCheck = Modules.WritebackIntent.normalizeSaveCheck({ ...owner, files: [...files.values()] });
|
|
7016
|
+
else {
|
|
7017
|
+
delete record.saveCheck;
|
|
7018
|
+
if (verification.state === 'verified_saved') record.saveConfirmedAt = new Date().toISOString();
|
|
7019
|
+
}
|
|
7020
|
+
await flushQueuedSaveState();
|
|
7021
|
+
}
|
|
7022
|
+
|
|
7023
|
+
async function retryRunWriteback(runId) {
|
|
7024
|
+
if (currentRunView || trackedChangeInFlight.size) return;
|
|
7025
|
+
const session = getActiveSession(state);
|
|
7026
|
+
const record = (session?.runs || []).find(run => run.id === runId);
|
|
7027
|
+
const intent = Modules.WritebackIntent.normalize(record?.retryWriteback);
|
|
7028
|
+
const check = Modules.WritebackIntent.normalizeSaveCheck(record?.saveCheck);
|
|
7029
|
+
const owner = intent || check;
|
|
7030
|
+
if (!record || record.forkSnapshot || !owner || owner.projectId !== getCurrentProjectId()
|
|
7031
|
+
|| owner.accountScopeId !== cachedAccountScopeId || owner.sessionId !== session.id
|
|
7032
|
+
|| owner.runId !== record.id || record.undoStatus === 'applied' || record.trackedChangeStatus === 'rejected') return;
|
|
7033
|
+
const originalStatus = record.status;
|
|
7034
|
+
const retryAutoRecompile = typeof record.executionSnapshot?.autoRecompile === 'boolean'
|
|
7035
|
+
? record.executionSnapshot.autoRecompile : undefined;
|
|
7036
|
+
// State is renormalized during the awaits below (probes, session sync), which
|
|
7037
|
+
// replaces run objects; always write through the current copy.
|
|
7038
|
+
const live = () => findRunRecord(runId, session.id) || record;
|
|
7039
|
+
let finished = false;
|
|
7040
|
+
runCancellationRequested = false;
|
|
7041
|
+
runCancellationController = new AbortController();
|
|
7042
|
+
record.status = 'running';
|
|
7043
|
+
record.retryingWriteback = true;
|
|
7044
|
+
renderRunHistory({ preserveScroll: true });
|
|
7045
|
+
const root = panel.querySelector(`[data-run-id="${cssEscape(runId)}"]`);
|
|
7046
|
+
currentRunView = { root, recordId: runId, sessionId: session.id, runProjectId: owner.projectId,
|
|
7047
|
+
runAccountScopeId: owner.accountScopeId, executionSnapshot: record.executionSnapshot,
|
|
7048
|
+
startedAt: Date.now(), writebackRetry: true, events: root?.querySelector('[data-run-events]'),
|
|
7049
|
+
report: root?.querySelector('[data-run-report]'), status: root?.querySelector('[data-run-status]'),
|
|
7050
|
+
runProcess: root?.querySelector('[data-run-process]'), processLabel: root?.querySelector('[data-run-process-summary]') };
|
|
7051
|
+
setRunning(true);
|
|
7052
|
+
const assertOwner = () => {
|
|
7053
|
+
if (runCancellationRequested || currentRunView?.recordId !== runId) {
|
|
7054
|
+
throw Object.assign(new Error('Save checking was cancelled.'), { code: 'codex_cancelled' });
|
|
7055
|
+
}
|
|
7056
|
+
if (owner.projectId !== getCurrentProjectId() || owner.accountScopeId !== cachedAccountScopeId) {
|
|
7057
|
+
throw Object.assign(new Error('The project or account changed.'), { code: 'aborted_project_changed' });
|
|
7058
|
+
}
|
|
7059
|
+
};
|
|
7060
|
+
try {
|
|
7061
|
+
assertOwner();
|
|
7062
|
+
if (intent?.requestId) {
|
|
7063
|
+
const receipt = await callPageBridge('getWritebackReceipt', { requestId: intent.requestId, runProjectId: owner.projectId });
|
|
7064
|
+
if (receipt?.state === 'running') throw new Error(tx('The previous upload is still finishing. Try again shortly.', '上一轮上传仍在收尾,请稍后再试。'));
|
|
7065
|
+
}
|
|
7066
|
+
if (intent?.operations.length) {
|
|
7067
|
+
// Edits replay against the run's original base files, so the stale guard still refuses changed documents.
|
|
7068
|
+
const result = await applySyncChangesToOverleaf(intent.operations.map(op => op.type === 'edit'
|
|
7069
|
+
? { type: 'write', path: op.path, patches: op.patches, content: op.replaceAll } : op.type === 'delete' ? { type: 'delete', path: op.path } : { ...op, type: 'create' }),
|
|
7070
|
+
{ id: owner.projectId, files: intent.baseFiles }, { mode: 'auto', requireReviewing: intent.requireReviewing,
|
|
7071
|
+
autoRecompile: retryAutoRecompile,
|
|
7072
|
+
retryCreates: true, assistantMessage: tx('Retried the remaining file transfers for this task.', '已重试本轮未完成的文件同步。') });
|
|
7073
|
+
assertOwner();
|
|
7074
|
+
if (result.settlement?.facts) live().settlement = WritebackSettlement.mergeRetrySettlement(live().settlement, result.settlement.facts); // Keep files outside the retry.
|
|
7075
|
+
await finishRunView(result.hasSkippedOperations ? tx('Sync incomplete', '同步未完成') : tx('Synced', '已同步'),
|
|
7076
|
+
result.hasSkippedOperations ? 'failed' : 'completed');
|
|
7077
|
+
finished = true;
|
|
7078
|
+
} else if (check) {
|
|
7079
|
+
const result = await writebackController.confirmSaveCheck(check, { assertCurrent: assertOwner,
|
|
7080
|
+
readSnapshot: params => callPageBridge('getProjectSnapshot', { ...params, runProjectId: owner.projectId }) });
|
|
7081
|
+
assertOwner();
|
|
7082
|
+
const current = live();
|
|
7083
|
+
if (result.ok) {
|
|
7084
|
+
delete current.saveCheck;
|
|
7085
|
+
current.saveConfirmedAt = new Date().toISOString();
|
|
7086
|
+
if (current.settlement) current.settlement.evidence = { ...current.settlement.evidence, saved: 'verified' };
|
|
7087
|
+
}
|
|
7088
|
+
showPluginToast(result.ok ? tx('Saved content confirmed.', '已确认文件保存。')
|
|
7089
|
+
: tx('Saved content is still unconfirmed. No file was uploaded again.', '保存状态仍待确认,本次没有重复上传文件。'));
|
|
7090
|
+
}
|
|
7091
|
+
await flushQueuedSaveState();
|
|
7092
|
+
} catch (error) {
|
|
7093
|
+
showPluginToast(error.message || String(error));
|
|
7094
|
+
} finally {
|
|
7095
|
+
// Only finishRunView settles a new terminal status; every other exit
|
|
7096
|
+
// returns the card to the status it had before the retry.
|
|
7097
|
+
for (const copy of new Set([record, live()])) {
|
|
7098
|
+
delete copy.retryingWriteback;
|
|
7099
|
+
if (!finished && copy.status === 'running') copy.status = originalStatus;
|
|
7100
|
+
}
|
|
7101
|
+
if (currentRunView?.recordId === runId) currentRunView = null;
|
|
7102
|
+
runCancellationRequested = false;
|
|
7103
|
+
runCancellationController = null;
|
|
7104
|
+
setRunning(false);
|
|
7105
|
+
saveStateSoon();
|
|
7106
|
+
renderRunHistory({ preserveScroll: true });
|
|
7107
|
+
}
|
|
7108
|
+
}
|
|
7109
|
+
|
|
7145
7110
|
function appendUndoReviewingPolicyEvent(runId, reviewingPolicy) {
|
|
7146
7111
|
if (!reviewingPolicy || reviewingPolicy.policy !== 'no-trace-undo') {
|
|
7147
7112
|
return;
|
|
@@ -7224,8 +7189,10 @@
|
|
|
7224
7189
|
});
|
|
7225
7190
|
}
|
|
7226
7191
|
|
|
7227
|
-
function recordUndoFromApply(project, applyResult) {
|
|
7228
|
-
const
|
|
7192
|
+
async function recordUndoFromApply(project, applyResult) {
|
|
7193
|
+
const directoryRecovery = Modules.DirectoryCleanupRecovery ? getDirectoryCleanupRecovery() : null;
|
|
7194
|
+
directoryRecovery?.capture(currentRunView, applyResult);
|
|
7195
|
+
const appliedEntries = getAppliedEntries(applyResult).filter(entry => entry.result?.idempotent !== true);
|
|
7229
7196
|
if (!currentRunView?.recordId || !appliedEntries.length) {
|
|
7230
7197
|
return;
|
|
7231
7198
|
}
|
|
@@ -7244,14 +7211,15 @@
|
|
|
7244
7211
|
...appliedOperations
|
|
7245
7212
|
];
|
|
7246
7213
|
record.appliedOperations = combinedAppliedOperations;
|
|
7214
|
+
const createdCheckpoint = buildCreatedFileUndoCheckpoint(project, combinedAppliedOperations, record);
|
|
7247
7215
|
|
|
7248
7216
|
if ((currentRunView?.executionSnapshot?.requireReviewing ?? state.requireReviewing) === true) {
|
|
7249
7217
|
const combinedTrackedChanges = normalizeApplyTrackedChanges([
|
|
7250
7218
|
...(Array.isArray(record.undoTrackedChanges) ? record.undoTrackedChanges : []),
|
|
7251
7219
|
...trackedChanges
|
|
7252
7220
|
]);
|
|
7253
|
-
record.undoOperations =
|
|
7254
|
-
record.undoBaseFiles =
|
|
7221
|
+
record.undoOperations = createdCheckpoint.undoOperations;
|
|
7222
|
+
record.undoBaseFiles = createdCheckpoint.undoBaseFiles;
|
|
7255
7223
|
record.undoTrackedChanges = combinedTrackedChanges;
|
|
7256
7224
|
record.undoExpectedFiles = selectExpectedFilesForTrackedUndo(
|
|
7257
7225
|
project,
|
|
@@ -7259,6 +7227,7 @@
|
|
|
7259
7227
|
combinedTrackedChanges,
|
|
7260
7228
|
record.undoExpectedFiles
|
|
7261
7229
|
);
|
|
7230
|
+
record.undoOperations = Modules.UndoOperations.getFileUndoOperations(record);
|
|
7262
7231
|
record.undoStatus = '';
|
|
7263
7232
|
record.partialWriteback = skippedEntries.length > 0;
|
|
7264
7233
|
// Recording tracked-change refs enters the run into the tracked-change
|
|
@@ -7286,6 +7255,11 @@
|
|
|
7286
7255
|
[tr('detailMethod')]: tr('undoCheckpointNativeMethod')
|
|
7287
7256
|
}
|
|
7288
7257
|
});
|
|
7258
|
+
} else if (record.undoOperations.length) {
|
|
7259
|
+
appendRunEvent({
|
|
7260
|
+
title: tr('undoCheckpointPlain', { count: record.undoOperations.length }),
|
|
7261
|
+
status: 'completed'
|
|
7262
|
+
});
|
|
7289
7263
|
} else {
|
|
7290
7264
|
appendRunEvent({
|
|
7291
7265
|
title: tr('undoCheckpointMissing'),
|
|
@@ -7300,6 +7274,15 @@
|
|
|
7300
7274
|
}
|
|
7301
7275
|
|
|
7302
7276
|
const checkpoint = buildUndoCheckpoint(project, combinedAppliedOperations);
|
|
7277
|
+
const createdPaths = new Set(createdCheckpoint.undoOperations.map(operation => operation.path));
|
|
7278
|
+
checkpoint.undoOperations = [
|
|
7279
|
+
...checkpoint.undoOperations.filter(operation => !createdPaths.has(operation.path)),
|
|
7280
|
+
...createdCheckpoint.undoOperations
|
|
7281
|
+
];
|
|
7282
|
+
checkpoint.undoBaseFiles = [
|
|
7283
|
+
...checkpoint.undoBaseFiles.filter(file => !createdPaths.has(file.path)),
|
|
7284
|
+
...createdCheckpoint.undoBaseFiles
|
|
7285
|
+
];
|
|
7303
7286
|
if (!checkpoint.undoOperations.length) {
|
|
7304
7287
|
return;
|
|
7305
7288
|
}
|
|
@@ -7374,8 +7357,10 @@
|
|
|
7374
7357
|
timestamp: event.timestamp || new Date().toISOString(),
|
|
7375
7358
|
kind: event.kind || 'activity',
|
|
7376
7359
|
technicalDetail: event.technicalDetail,
|
|
7360
|
+
activity: Modules.RunActivityModel.capture(event, sanitizeAssistantVisibleText),
|
|
7377
7361
|
guidanceId: sanitizeAssistantVisibleText(event.guidanceId)
|
|
7378
7362
|
};
|
|
7363
|
+
if (event.failure) normalized.failure = sanitizeAssistantVisibleValue(event.failure);
|
|
7379
7364
|
record.events = [...(record.events || []), normalized].slice(-MAX_RUN_EVENTS);
|
|
7380
7365
|
saveStateSoon();
|
|
7381
7366
|
|
|
@@ -7400,29 +7385,24 @@
|
|
|
7400
7385
|
refreshRunCardControls(runId);
|
|
7401
7386
|
}
|
|
7402
7387
|
|
|
7403
|
-
function applyLegacyUndoSettlement(runId, undoStatus, result) {
|
|
7404
|
-
|
|
7405
|
-
if (!run) return;
|
|
7406
|
-
Object.assign(run, WritebackSettlement.applySettlementTransition(
|
|
7407
|
-
run,
|
|
7408
|
-
WritebackSettlement.settleLegacyUndo({
|
|
7409
|
-
run,
|
|
7410
|
-
status: undoStatus,
|
|
7411
|
-
result
|
|
7412
|
-
})
|
|
7413
|
-
));
|
|
7414
|
-
saveStateSoon();
|
|
7415
|
-
refreshRunCardControls(runId);
|
|
7388
|
+
async function applyLegacyUndoSettlement(runId, undoStatus, result) {
|
|
7389
|
+
return getRunReviewController().applyLegacyUndoSettlement(runId, undoStatus, result);
|
|
7416
7390
|
}
|
|
7417
7391
|
|
|
7418
7392
|
function refreshRunCardControls(runId) {
|
|
7419
|
-
const run = findRunRecord(runId);
|
|
7420
|
-
const root = panel?.querySelector(`[data-run-id="${cssEscape(runId)}"]`);
|
|
7393
|
+
const run = findRunRecord(runId), root = panel?.querySelector(`[data-run-id="${cssEscape(runId)}"]`);
|
|
7421
7394
|
if (!run || !root) {
|
|
7422
7395
|
return;
|
|
7423
7396
|
}
|
|
7397
|
+
if (runTimelineView.refreshResolvedUndo?.(runId)) return;
|
|
7424
7398
|
configureAcceptButton(root, run);
|
|
7425
7399
|
configureUndoButton(root, run);
|
|
7400
|
+
const reportEvent = (run.events || []).filter(event => event.kind === 'report').pop();
|
|
7401
|
+
const report = root.querySelector('[data-run-report]');
|
|
7402
|
+
if (reportEvent && report) {
|
|
7403
|
+
report.replaceChildren(renderCompletionReport(reportEvent, run));
|
|
7404
|
+
report.hidden = false;
|
|
7405
|
+
}
|
|
7426
7406
|
}
|
|
7427
7407
|
|
|
7428
7408
|
function findRunRecord(runId, sessionId = '') {
|
|
@@ -7543,6 +7523,11 @@
|
|
|
7543
7523
|
// v1.8.0 C1: a change-history row jumps to the run it describes —
|
|
7544
7524
|
// switch to its session if needed, then scroll + flash the run card.
|
|
7545
7525
|
async function jumpToHistoryRun(record = {}) {
|
|
7526
|
+
if (!cachedAccountScopeId || record.accountScopeId !== cachedAccountScopeId
|
|
7527
|
+
|| record.projectId !== getCurrentProjectId()) {
|
|
7528
|
+
showPluginToast(tx('The project or account changed. Reopen file history and try again.', '项目或账号已变化,请重新打开文件修改记录。'));
|
|
7529
|
+
return;
|
|
7530
|
+
}
|
|
7546
7531
|
closeCustomInstructionsSettings();
|
|
7547
7532
|
if (record.sessionId && record.sessionId !== state.activeSessionId) {
|
|
7548
7533
|
const target = (state.sessions || []).find(session => session.id === record.sessionId);
|
|
@@ -7621,7 +7606,7 @@
|
|
|
7621
7606
|
if (primaryNext) return primaryNext;
|
|
7622
7607
|
return tx('Expand the write result, review the skipped reasons, then retry after fixing them.', '请展开写入结果查看跳过原因,处理后可以重试。');
|
|
7623
7608
|
}
|
|
7624
|
-
if (input.status === 'rejected') {
|
|
7609
|
+
if (input.status === 'cancelled' || input.status === 'rejected') {
|
|
7625
7610
|
return tx('Adjust the task description and run again.', '可以调整任务描述后重新运行。');
|
|
7626
7611
|
}
|
|
7627
7612
|
if (input.status === 'blocked' || input.status === 'failed') {
|