codex-overleaf-link 2.5.0 → 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 +38 -26
- package/README.zh-CN.md +39 -27
- package/extension/bootstrap/background.js +52 -9
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/bootstrap/popup.html +1 -0
- package/extension/bootstrap/popupFallback.js +55 -0
- package/extension/bootstrap/updateStatus.js +1 -0
- package/extension/src/background.js +206 -53
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +74 -14
- package/extension/src/backgroundUpdateCoordinator.js +118 -21
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +8 -2
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +93 -45
- package/extension/src/content/composerPanel.js +18 -0
- package/extension/src/content/contentRuntime.js +661 -676
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +30 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +6879 -2786
- package/extension/src/content/generated/content.bundle.meta.json +11 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +6 -0
- package/extension/src/content/localSkillsPanel.js +102 -12
- package/extension/src/content/markdownText.js +5 -6
- package/extension/src/content/modelPicker.js +7 -4
- 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 +15 -2
- package/extension/src/content/panelMaintenance.js +40 -12
- package/extension/src/content/panelRenderer.js +6 -0
- 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 +46 -8
- package/extension/src/content/providerSettingsDialog.js +71 -13
- package/extension/src/content/recentProjects.js +11 -5
- package/extension/src/content/runActivitySummary.js +40 -6
- package/extension/src/content/runCompletionMeta.js +344 -0
- package/extension/src/content/runController.js +37 -14
- package/extension/src/content/runFailureNotice.js +21 -12
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runQueueScheduler.js +148 -42
- package/extension/src/content/runRecoveryActions.js +146 -0
- package/extension/src/content/runResultActions.js +8 -125
- package/extension/src/content/runReviewController.js +574 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +146 -153
- package/extension/src/content/scopedPersistencePanelState.js +51 -6
- package/extension/src/content/selectionContextView.js +1 -1
- 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/settingsPanel.js +7 -3
- package/extension/src/content/settingsWorkbench.js +5 -0
- package/extension/src/content/sharedSessionViewSync.js +45 -8
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +60 -8
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +82 -73
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/ownedDirectoryCleanup.js +94 -0
- package/extension/src/page/referenceProjects.js +1 -0
- package/extension/src/page/reviewContext.js +113 -0
- package/extension/src/page/saveState.js +6 -4
- package/extension/src/page/snapshotRouter.js +84 -39
- package/extension/src/page/textFileCreator.js +105 -25
- package/extension/src/page/trackedChangeCapture.js +12 -2
- package/extension/src/page/trackedChangeOwnership.js +19 -1
- package/extension/src/page/trackedChangeReplay.js +41 -21
- package/extension/src/page/trackedChangesLifecycle.js +217 -199
- package/extension/src/page/treeOperations.js +7 -10
- package/extension/src/page/updateSafetyProbe.js +41 -0
- package/extension/src/page/writebackReceiptJournal.js +2 -0
- package/extension/src/page/writebackRouter.js +80 -79
- package/extension/src/pageBridge.js +19 -3
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +23 -0
- package/extension/src/shared/auditRecords.js +2 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +79 -57
- package/extension/src/shared/composerAttachmentDraft.js +151 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +309 -3
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +298 -2
- package/extension/src/shared/managedUpdateProjection.js +2 -2
- package/extension/src/shared/nativeRequestIdentity.js +1 -0
- package/extension/src/shared/pageRpcContract.js +5 -4
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +72 -27
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +37 -14
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +118 -13
- package/extension/src/shared/storageDb.js +58 -26
- package/extension/src/shared/storageMigration.js +71 -111
- package/extension/src/shared/storageRunActions.js +209 -26
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +136 -13
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +1 -1
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +38 -38
- package/extension/styles/panel.css +19 -5
- package/native-host/src/anthropicBridgeRequest.js +4 -2
- 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/codexPromptAssembly.js +31 -12
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +110 -183
- package/native-host/src/codexSessionTiming.js +18 -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 +110 -35
- 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/nativeHelperLifecycle.js +21 -0
- package/native-host/src/nativeHostRegistration.js +33 -0
- package/native-host/src/nativeRunLifecycle.js +71 -0
- package/native-host/src/processStopBarrier.js +42 -7
- package/native-host/src/projectPathPlatform.js +30 -0
- package/native-host/src/providerHttp.js +80 -0
- package/native-host/src/providerProfile.js +4 -2
- package/native-host/src/providerReasoning.js +2 -7
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/providerStore.js +72 -14
- package/native-host/src/responsesPassthroughBridge.js +266 -52
- package/native-host/src/runtimeInstaller.js +21 -1
- package/native-host/src/selectionScope.js +36 -8
- package/native-host/src/subagentAdmission.js +26 -0
- package/native-host/src/subagentBroker.js +79 -66
- package/native-host/src/subagentTelemetry.js +19 -10
- package/native-host/src/subagentWorkspacePath.js +5 -2
- package/native-host/src/taskRunnerRuntime.js +77 -54
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +297 -39
- package/native-host/src/writingStyleRuntime.js +57 -29
- 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()
|
|
@@ -207,6 +214,8 @@
|
|
|
207
214
|
closeSlashMenu: () => closeSlashMenu(),
|
|
208
215
|
getCurrentProjectId: () => getCurrentProjectId(),
|
|
209
216
|
getLocalSkillsPanel: () => localSkillsPanel,
|
|
217
|
+
getCodexLocalSkills: () => getCodexLocalSkillsCatalog(),
|
|
218
|
+
onSkillsChange: () => updateSlashMenuForTaskInput(),
|
|
210
219
|
getLocale: () => getLocale(),
|
|
211
220
|
getPanel: () => panel,
|
|
212
221
|
getPanelRendererInstance: () => panelRendererInstance,
|
|
@@ -229,6 +238,9 @@
|
|
|
229
238
|
closeCustomInstructionsSettings,
|
|
230
239
|
closeSkillsView,
|
|
231
240
|
getCodexOverleafSkillEnabled,
|
|
241
|
+
getCodexLocalSkillEnabled,
|
|
242
|
+
isCodexLocalSkillEnabled,
|
|
243
|
+
setCodexLocalSkillEnabled,
|
|
232
244
|
getCustomInstructionsForCurrentProject,
|
|
233
245
|
getGovernanceRulesForCurrentProject,
|
|
234
246
|
getSkillLoadingSettings,
|
|
@@ -335,6 +347,7 @@
|
|
|
335
347
|
otWarmMirrorController,
|
|
336
348
|
getActiveFocusFiles,
|
|
337
349
|
getCurrentProjectId,
|
|
350
|
+
getScope: () => ({ accountScopeId: cachedAccountScopeId, projectId: getCurrentProjectId(), generation: spaRouteGeneration }),
|
|
338
351
|
isExperimentalOtEnabled,
|
|
339
352
|
readOtBridgeStatus,
|
|
340
353
|
formatOtStatusLabel,
|
|
@@ -383,6 +396,11 @@
|
|
|
383
396
|
openCustomInstructionsSettings: () => openCustomInstructionsSettings(),
|
|
384
397
|
onHistoryRowJump: record => jumpToHistoryRun(record),
|
|
385
398
|
resetProjectNameCache: () => recentProjects.resetProjectNameCache(),
|
|
399
|
+
clearHistoryComposer: () => {
|
|
400
|
+
pendingRetryReplacement = null;
|
|
401
|
+
clearTaskComposer();
|
|
402
|
+
persistComposerAttachmentsToState();
|
|
403
|
+
},
|
|
386
404
|
getPanel: () => panel,
|
|
387
405
|
getState: () => state,
|
|
388
406
|
setState: next => { state = next; },
|
|
@@ -407,8 +425,15 @@
|
|
|
407
425
|
SessionState: Modules.SessionState,
|
|
408
426
|
getState: () => state,
|
|
409
427
|
setState: next => { state = next; },
|
|
410
|
-
|
|
411
|
-
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
|
+
},
|
|
412
437
|
applyStateToPanel,
|
|
413
438
|
showPluginToast,
|
|
414
439
|
tr
|
|
@@ -427,9 +452,14 @@
|
|
|
427
452
|
RunActivityModel: Modules.RunActivityModel,
|
|
428
453
|
SubagentActivityView: Modules.SubagentActivityView,
|
|
429
454
|
RunGuidanceView: Modules.RunGuidanceView,
|
|
455
|
+
getPendingGuidance: run => runGuidanceController.getPending(run),
|
|
430
456
|
RunResultActions: runResultActions,
|
|
431
457
|
RunScrollLayout: Modules.RunScrollLayout,
|
|
432
458
|
RunFailureNotice: Modules.RunFailureNotice,
|
|
459
|
+
I18n: i18n,
|
|
460
|
+
FailureReasons,
|
|
461
|
+
localizeVisibleReason: value => localizeVisibleReason(value),
|
|
462
|
+
localizeCompileReason: value => Modules.CompileAdapter.localizeCompileReason(value, getLocale()),
|
|
433
463
|
tr,
|
|
434
464
|
tx,
|
|
435
465
|
getLocale,
|
|
@@ -457,6 +487,8 @@
|
|
|
457
487
|
refillComposerForRetry,
|
|
458
488
|
canEditRun: run => runController.isRetryReplacementEligible(run, WritebackSettlement.projectRunSettlement(run)),
|
|
459
489
|
retryWriteback: retryRunWriteback,
|
|
490
|
+
getCurrentProjectId,
|
|
491
|
+
getAccountScopeId: () => cachedAccountScopeId,
|
|
460
492
|
showNativeSetupGuidance: () => showNativeUpdateGuidanceModal({}),
|
|
461
493
|
openProjectFileForFailure,
|
|
462
494
|
openStorageSettings,
|
|
@@ -609,6 +641,14 @@
|
|
|
609
641
|
locale: getLocale()
|
|
610
642
|
};
|
|
611
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
|
+
},
|
|
612
652
|
startBuild: async params => {
|
|
613
653
|
const gate = await nativeCompatibilityController.ensureForMethod('codex.run');
|
|
614
654
|
if (!gate.ok) throw new Error(gate.response?.error?.message || 'Native host update required.');
|
|
@@ -765,8 +805,10 @@
|
|
|
765
805
|
window,
|
|
766
806
|
getSettingsPanelInstance: () => settingsPanelInstance,
|
|
767
807
|
getSelectedModel: () => state?.model || '', getSelectedProviderId: () => state?.providerId || 'builtin',
|
|
808
|
+
getSelectedProviderRevision: () => state?.providerRevision,
|
|
768
809
|
setSelectedProviderId: projectProviderSelection.commit,
|
|
769
810
|
confirmProviderSwitch: projectProviderSelection.confirmSwitch,
|
|
811
|
+
assertProviderSwitchAllowed: projectProviderSelection.assertSwitchAllowed,
|
|
770
812
|
refreshModelOptions: loadModelOptions, persistInputs: persistPanelInputs
|
|
771
813
|
});
|
|
772
814
|
// v1.8.1 D3: the dashboard was a one-shot render — another tab finishing a
|
|
@@ -848,6 +890,7 @@
|
|
|
848
890
|
tr,
|
|
849
891
|
tx,
|
|
850
892
|
appendPlainLog,
|
|
893
|
+
onChange: () => getComposerDraftPersistence().changed(),
|
|
851
894
|
limits: {
|
|
852
895
|
maxAttachmentBytes: MAX_COMPOSER_ATTACHMENT_BYTES,
|
|
853
896
|
maxAttachmentTotalBytes: MAX_COMPOSER_ATTACHMENT_TOTAL_BYTES,
|
|
@@ -905,9 +948,15 @@
|
|
|
905
948
|
saveState,
|
|
906
949
|
sendBackgroundNative,
|
|
907
950
|
getCurrentProjectId,
|
|
951
|
+
getScope: () => getCodexLocalSkillsCatalogScope(),
|
|
908
952
|
getSkillLoadingSettings,
|
|
909
953
|
isCodexOverleafSkillEnabled,
|
|
910
954
|
setCodexOverleafSkillEnabled,
|
|
955
|
+
isCodexLocalSkillEnabled,
|
|
956
|
+
setCodexLocalSkillEnabled,
|
|
957
|
+
getCodexLocalSkills: () => getCodexLocalSkillsCatalog(),
|
|
958
|
+
getCodexLocalSkillWarnings: () => getCodexLocalSkillWarnings(),
|
|
959
|
+
refreshCodexLocalSkills: options => primeCodexLocalSkillsCatalog(options),
|
|
911
960
|
setProjectSettingsStatus,
|
|
912
961
|
tr,
|
|
913
962
|
tx,
|
|
@@ -940,6 +989,7 @@
|
|
|
940
989
|
let storageKey = LEGACY_STORAGE_KEY;
|
|
941
990
|
let globalPreferencesController = null;
|
|
942
991
|
let currentRunView = null;
|
|
992
|
+
let runSubmissionPending = false;
|
|
943
993
|
let pendingRetryReplacement = null;
|
|
944
994
|
let runQueueScheduler = null;
|
|
945
995
|
let runInputCoordinator = null;
|
|
@@ -960,6 +1010,7 @@
|
|
|
960
1010
|
let pendingStreamRenderEvents = new Map();
|
|
961
1011
|
let storageNoticeKeys = new Set();
|
|
962
1012
|
let composerSkillInvocation = null;
|
|
1013
|
+
let composerDraftRevision = 0;
|
|
963
1014
|
let slashCodexOverleafSkills = [];
|
|
964
1015
|
let slashCodexOverleafSkillsLoaded = false;
|
|
965
1016
|
let slashCodexOverleafSkillsLoading = false;
|
|
@@ -1017,6 +1068,7 @@
|
|
|
1017
1068
|
const diagnosticsExporter = Modules.DiagnosticsExport.create({
|
|
1018
1069
|
root, window, document, Blob, URL, TextEncoder, AuditRecords, StorageDb: Modules.StorageDb,
|
|
1019
1070
|
CodexOverleafCompatibility, getState: () => state, getCurrentRunView: () => currentRunView,
|
|
1071
|
+
getAccountScopeId: () => cachedAccountScopeId || '',
|
|
1020
1072
|
getCurrentProjectId, findRunRecord, getMirrorFreshness, getExtensionCompatibilityMetadata,
|
|
1021
1073
|
getModelDiscovery, getGovernanceRulesForCurrentProject, sendBackgroundNative,
|
|
1022
1074
|
fallbackNativeCompatibility, isNativeCompatibilityCompatible, getNativeCompatibilityClassification,
|
|
@@ -1094,6 +1146,7 @@
|
|
|
1094
1146
|
await refreshAccountScopeId();
|
|
1095
1147
|
storageKey = getProjectStorageKey(LEGACY_STORAGE_KEY, window.location.href);
|
|
1096
1148
|
state = normalizeLoadedPanelState(await loadStoredState());
|
|
1149
|
+
await getRunReviewController().recoverInterruptedUndoStatuses();
|
|
1097
1150
|
initializeRunQueueScheduler();
|
|
1098
1151
|
recoverInterruptedRunJournals()
|
|
1099
1152
|
.then(() => applyStateToPanel())
|
|
@@ -1165,7 +1218,8 @@
|
|
|
1165
1218
|
version: CodexOverleafCompatibility?.BUILD_TARGET_VERSION ||
|
|
1166
1219
|
chrome.runtime.getManifest?.().version ||
|
|
1167
1220
|
'',
|
|
1168
|
-
extensionId: getCurrentExtensionId()
|
|
1221
|
+
extensionId: getCurrentExtensionId(),
|
|
1222
|
+
installIdentity: { managed: MANAGED_INSTALLATION }
|
|
1169
1223
|
};
|
|
1170
1224
|
}
|
|
1171
1225
|
|
|
@@ -1309,6 +1363,7 @@
|
|
|
1309
1363
|
onTaskKeydown: handleTaskInputKeydown,
|
|
1310
1364
|
onTaskInput: handleTaskInput,
|
|
1311
1365
|
onSlashMenuClick: handleSlashMenuClick,
|
|
1366
|
+
onMenuLayoutChange: updateSlashMenuLayout,
|
|
1312
1367
|
onClearSkillInvocation: clearComposerSkillInvocation,
|
|
1313
1368
|
onAddContext: toggleContextTray,
|
|
1314
1369
|
onAttachClick: () => panel.querySelector('[data-attach-input]')?.click(),
|
|
@@ -1335,34 +1390,87 @@
|
|
|
1335
1390
|
renderContextSelection();
|
|
1336
1391
|
scheduleMirrorPrefetch({ reason: 'cold-start', delayMs: 2500 });
|
|
1337
1392
|
primeCodexOverleafSkillsCatalog().catch(() => { /* non-blocking */ });
|
|
1393
|
+
primeCodexLocalSkillsCatalog().catch(() => { /* retry through settings, slash, or submission */ });
|
|
1338
1394
|
}
|
|
1339
1395
|
|
|
1340
1396
|
PanelRenderer.setVisible(panel, true);
|
|
1341
1397
|
}
|
|
1342
1398
|
|
|
1343
|
-
//
|
|
1344
|
-
//
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
// the Skills page. Fire-and-forget on panel open; only fetches when the
|
|
1348
|
-
// catalog is empty.
|
|
1349
|
-
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 } = {}) {
|
|
1350
1403
|
if (Array.isArray(state?.codexOverleafSkills) && state.codexOverleafSkills.length) {
|
|
1351
|
-
return;
|
|
1352
|
-
}
|
|
1353
|
-
if (getSkillLoadingSettings().loadCodexOverleafSkills === false)
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
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;
|
|
1359
1471
|
});
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
return;
|
|
1363
|
-
}
|
|
1364
|
-
state = { ...state, codexOverleafSkills: skills };
|
|
1365
|
-
saveStateSoon();
|
|
1472
|
+
codexLocalSkillsCatalogRequest = request;
|
|
1473
|
+
return request.promise;
|
|
1366
1474
|
}
|
|
1367
1475
|
|
|
1368
1476
|
function closePanel() {
|
|
@@ -1445,6 +1553,10 @@
|
|
|
1445
1553
|
panel.querySelectorAll('[data-i18n]').forEach(element => {
|
|
1446
1554
|
element.textContent = tr(element.dataset.i18n);
|
|
1447
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 边栏') });
|
|
1448
1560
|
|
|
1449
1561
|
setElementTitleAndAria('[data-panel-resize-handle]', tr('resizePanel'), tr('resizePanel'));
|
|
1450
1562
|
setElementTitleAndAria('[data-refresh]', tr('refreshProbe'), tr('refreshProbe'));
|
|
@@ -1514,7 +1626,7 @@
|
|
|
1514
1626
|
contextStatus.textContent = tr('contextStatus');
|
|
1515
1627
|
}
|
|
1516
1628
|
// Re-render rather than patch text: the first div is the animated mark.
|
|
1517
|
-
if (
|
|
1629
|
+
if (arguments[0]?.reprojectDynamic !== false) runTimelineView.refreshLocale?.();
|
|
1518
1630
|
|
|
1519
1631
|
syncModeControls();
|
|
1520
1632
|
updateOtStatusDisplay();
|
|
@@ -1523,9 +1635,19 @@
|
|
|
1523
1635
|
updateModelDisplay();
|
|
1524
1636
|
renderSessionList();
|
|
1525
1637
|
renderContextSelection();
|
|
1638
|
+
renderContextSummary();
|
|
1639
|
+
renderPendingInputs();
|
|
1640
|
+
renderComposerAttachments();
|
|
1641
|
+
renderComposerSkillInvocation();
|
|
1526
1642
|
writingStyleSettings.sync();
|
|
1527
1643
|
updateSkillsEntrySummary();
|
|
1644
|
+
localSkillsPanel.refreshLocale?.();
|
|
1528
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();
|
|
1529
1651
|
if (settingsPanelInstance?.container?.querySelector('[data-storage-card]')?.open) refreshStorageUsageSummary();
|
|
1530
1652
|
}
|
|
1531
1653
|
|
|
@@ -1685,15 +1807,7 @@
|
|
|
1685
1807
|
function resetContextProject() { return contextTrayController.resetContextProject(); }
|
|
1686
1808
|
|
|
1687
1809
|
async function runTask(options = {}) {
|
|
1688
|
-
//
|
|
1689
|
-
// turn (v1.7.5) BEFORE startRunView creates the new run view: the mirror's
|
|
1690
|
-
// wrap-up events must land in the run that produced them, not the new
|
|
1691
|
-
// one, and mirror.sync landing mid-run would swap the local baseline
|
|
1692
|
-
// under Codex.
|
|
1693
|
-
const pendingMirror = getPendingMirrorRefresh?.();
|
|
1694
|
-
if (pendingMirror) {
|
|
1695
|
-
await pendingMirror.catch(() => {});
|
|
1696
|
-
}
|
|
1810
|
+
// Capture the click before any preparation can yield to another draft/session.
|
|
1697
1811
|
readPanelInputs();
|
|
1698
1812
|
const queuedInput = options.queuedInput || null;
|
|
1699
1813
|
const task = String(queuedInput?.text || state.task || '').trim();
|
|
@@ -1701,6 +1815,11 @@
|
|
|
1701
1815
|
appendLog(tx('Enter a task first.', '请先输入任务。'));
|
|
1702
1816
|
return;
|
|
1703
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;
|
|
1704
1823
|
const submittedWritingStyleScope = { accountScopeId: cachedAccountScopeId, projectId: getCurrentProjectId() };
|
|
1705
1824
|
const submittedPanelState = {
|
|
1706
1825
|
mode: state.mode,
|
|
@@ -1713,10 +1832,35 @@
|
|
|
1713
1832
|
focusFiles: getActiveFocusFiles(),
|
|
1714
1833
|
selectionContext: selectionContextView.capture()
|
|
1715
1834
|
};
|
|
1716
|
-
const
|
|
1717
|
-
|
|
1718
|
-
const
|
|
1719
|
-
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(() => {});
|
|
1720
1864
|
await providerSettingsCoordinator.ensureLoaded();
|
|
1721
1865
|
if (!queuedInput) await writingStyleSettings.ensureLoaded(true);
|
|
1722
1866
|
if (submittedWritingStyleScope.accountScopeId !== cachedAccountScopeId
|
|
@@ -1724,6 +1868,10 @@
|
|
|
1724
1868
|
throw new Error(tx('The active project changed before submission. Submit the task again.',
|
|
1725
1869
|
'提交前活动项目已改变,请重新提交任务。'));
|
|
1726
1870
|
}
|
|
1871
|
+
if (!findSessionById(submittedSessionId) || currentRunView) {
|
|
1872
|
+
throw new Error(tx('The submitted session changed before submission. Submit the task again.',
|
|
1873
|
+
'提交所属会话已改变,请重新提交任务。'));
|
|
1874
|
+
}
|
|
1727
1875
|
submittedPanelState.writingStyle = queuedInput ? null : writingStyleSettings.capture();
|
|
1728
1876
|
const executionSnapshot = queuedInput
|
|
1729
1877
|
? RunExecutionSnapshot.resolveForExecution(
|
|
@@ -1735,9 +1883,9 @@
|
|
|
1735
1883
|
const submittedMode = executionSnapshot.mode;
|
|
1736
1884
|
const submittedRequireReviewing = executionSnapshot.requireReviewing === true;
|
|
1737
1885
|
const replaceRunId = queuedInput ? '' : runController.resolveRetryReplacement({
|
|
1738
|
-
candidate:
|
|
1739
|
-
activeSessionId:
|
|
1740
|
-
runs:
|
|
1886
|
+
candidate: submittedRetryReplacement,
|
|
1887
|
+
activeSessionId: submittedSessionId,
|
|
1888
|
+
runs: findSessionById(submittedSessionId)?.runs,
|
|
1741
1889
|
projectRunSettlement: run => WritebackSettlement.projectRunSettlement(run)
|
|
1742
1890
|
});
|
|
1743
1891
|
|
|
@@ -1745,6 +1893,9 @@
|
|
|
1745
1893
|
runCancellationController = new AbortController();
|
|
1746
1894
|
setRunning(true);
|
|
1747
1895
|
currentRunView = startRunView({
|
|
1896
|
+
sessionId: submittedSessionId,
|
|
1897
|
+
sessionContext: submittedSession,
|
|
1898
|
+
codexThreadId: submittedThreadId,
|
|
1748
1899
|
task,
|
|
1749
1900
|
mode: submittedMode,
|
|
1750
1901
|
model: executionSnapshot.model,
|
|
@@ -1756,9 +1907,10 @@
|
|
|
1756
1907
|
queueItemId: queuedInput?.id || '',
|
|
1757
1908
|
replaceRunId
|
|
1758
1909
|
});
|
|
1759
|
-
if (!queuedInput) pendingRetryReplacement = null;
|
|
1910
|
+
if (!queuedInput && pendingRetryReplacement === submittedRetryReplacement) pendingRetryReplacement = null;
|
|
1760
1911
|
const runSessionId = currentRunView.sessionId;
|
|
1761
1912
|
let runAuditDraft = null;
|
|
1913
|
+
let localSkillRestorePoint = null;
|
|
1762
1914
|
try {
|
|
1763
1915
|
if (queuedInput?.id) {
|
|
1764
1916
|
await runQueueScheduler?.markExecuting(runSessionId, queuedInput.id, currentRunView.recordId);
|
|
@@ -1770,14 +1922,32 @@
|
|
|
1770
1922
|
focusFiles: executionSnapshot.focusFiles,
|
|
1771
1923
|
executionSnapshot
|
|
1772
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
|
+
}
|
|
1773
1931
|
announceCrossTabRunStart();
|
|
1774
1932
|
// Binary attachments belong to this submitted turn. The run record has
|
|
1775
1933
|
// already captured their snapshots, so clear them from the composer
|
|
1776
1934
|
// together with the submitted text. Persistent @context remains intact.
|
|
1777
|
-
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
|
+
}
|
|
1778
1948
|
syncComposerSendAvailability(true);
|
|
1779
1949
|
appendRunEvent({
|
|
1780
|
-
title: submittedSkillInvocation?.id === 'skill-installer'
|
|
1950
|
+
title: submittedSkillInvocation?.id === 'skill-installer' && !submittedSkillInvocation.scope
|
|
1781
1951
|
? tx('I will use the Codex skill installer for this request.', '我会用 Codex skill installer 处理这个请求。')
|
|
1782
1952
|
: tx('I will first understand your request, then inspect the relevant Overleaf files.', '我会先理解你的请求,再检查相关 Overleaf 文件。'),
|
|
1783
1953
|
status: 'running',
|
|
@@ -1792,7 +1962,7 @@
|
|
|
1792
1962
|
'@context': formatContextItems(executionSnapshot.focusFiles)
|
|
1793
1963
|
}
|
|
1794
1964
|
});
|
|
1795
|
-
if (submittedSkillInvocation?.id === 'skill-installer') {
|
|
1965
|
+
if (submittedSkillInvocation?.id === 'skill-installer' && !submittedSkillInvocation.scope) {
|
|
1796
1966
|
await awaitRunStep(runSkillInstallerTask({
|
|
1797
1967
|
task,
|
|
1798
1968
|
runSessionId,
|
|
@@ -1800,6 +1970,8 @@
|
|
|
1800
1970
|
submittedMode,
|
|
1801
1971
|
submittedCustomInstructions,
|
|
1802
1972
|
submittedSkillLoadingSettings,
|
|
1973
|
+
submittedEnabledLocalSkillIds,
|
|
1974
|
+
submittedEnabledSkillIds,
|
|
1803
1975
|
submittedAttachments,
|
|
1804
1976
|
submittedSkillInvocation,
|
|
1805
1977
|
executionSnapshot
|
|
@@ -1860,6 +2032,7 @@
|
|
|
1860
2032
|
project,
|
|
1861
2033
|
snapshotWarnings,
|
|
1862
2034
|
focusFiles,
|
|
2035
|
+
focusFilesAreLiteral: true,
|
|
1863
2036
|
isUsableProjectFileContent: Modules.ProjectFiles.isUsableProjectFileContent
|
|
1864
2037
|
});
|
|
1865
2038
|
let restrictToFocusFiles = runController.shouldRestrictWritebackToFocus({ focusFiles, focusedPartialSnapshot });
|
|
@@ -1928,8 +2101,7 @@
|
|
|
1928
2101
|
}
|
|
1929
2102
|
}
|
|
1930
2103
|
|
|
1931
|
-
const
|
|
1932
|
-
const codexThreadId = activeSession?.codexThreadId || '';
|
|
2104
|
+
const codexThreadId = submittedThreadId;
|
|
1933
2105
|
|
|
1934
2106
|
// Resolve @compile-log if mentioned in task
|
|
1935
2107
|
let compileLogContext = null;
|
|
@@ -1957,14 +2129,14 @@
|
|
|
1957
2129
|
}
|
|
1958
2130
|
});
|
|
1959
2131
|
appendRunEvent({
|
|
1960
|
-
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())}`),
|
|
1961
2133
|
status: 'failed',
|
|
1962
2134
|
failure: contextFailure
|
|
1963
2135
|
});
|
|
1964
2136
|
}
|
|
1965
2137
|
}
|
|
1966
2138
|
|
|
1967
|
-
|
|
2139
|
+
let sensitiveFindings = await awaitRunStep(runSensitivePreflight({
|
|
1968
2140
|
task,
|
|
1969
2141
|
project,
|
|
1970
2142
|
rules: getGovernanceRulesForCurrentProject(),
|
|
@@ -2001,6 +2173,8 @@
|
|
|
2001
2173
|
compileLogContext,
|
|
2002
2174
|
customInstructions: submittedCustomInstructions,
|
|
2003
2175
|
skillLoadingSettings: submittedSkillLoadingSettings,
|
|
2176
|
+
enabledCodexOverleafSkillIds: submittedEnabledSkillIds,
|
|
2177
|
+
enabledCodexLocalSkillIds: submittedEnabledLocalSkillIds,
|
|
2004
2178
|
attachments: submittedAttachments,
|
|
2005
2179
|
skillInvocation: submittedSkillInvocation,
|
|
2006
2180
|
submittedMode
|
|
@@ -2022,6 +2196,25 @@
|
|
|
2022
2196
|
fileOverlays = null;
|
|
2023
2197
|
otWarmStart = false;
|
|
2024
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
|
+
}
|
|
2025
2218
|
response = await awaitRunStep(sendTrackedCodexRun(buildCodexRunParams({
|
|
2026
2219
|
task,
|
|
2027
2220
|
project,
|
|
@@ -2034,6 +2227,8 @@
|
|
|
2034
2227
|
compileLogContext,
|
|
2035
2228
|
customInstructions: submittedCustomInstructions,
|
|
2036
2229
|
skillLoadingSettings: submittedSkillLoadingSettings,
|
|
2230
|
+
enabledCodexOverleafSkillIds: submittedEnabledSkillIds,
|
|
2231
|
+
enabledCodexLocalSkillIds: submittedEnabledLocalSkillIds,
|
|
2037
2232
|
attachments: submittedAttachments,
|
|
2038
2233
|
skillInvocation: submittedSkillInvocation,
|
|
2039
2234
|
submittedMode
|
|
@@ -2065,6 +2260,8 @@
|
|
|
2065
2260
|
compileLogContext,
|
|
2066
2261
|
customInstructions: submittedCustomInstructions,
|
|
2067
2262
|
skillLoadingSettings: submittedSkillLoadingSettings,
|
|
2263
|
+
enabledCodexOverleafSkillIds: submittedEnabledSkillIds,
|
|
2264
|
+
enabledCodexLocalSkillIds: submittedEnabledLocalSkillIds,
|
|
2068
2265
|
attachments: submittedAttachments,
|
|
2069
2266
|
skillInvocation: submittedSkillInvocation,
|
|
2070
2267
|
submittedMode
|
|
@@ -2085,7 +2282,12 @@
|
|
|
2085
2282
|
void finalizeAuditRecord(runAuditDraft, { resultStatus: 'cancelled' });
|
|
2086
2283
|
return;
|
|
2087
2284
|
}
|
|
2088
|
-
|
|
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 });
|
|
2089
2291
|
// Structured FailureReason (§9.7): split bridge-availability from
|
|
2090
2292
|
// Codex-side errors. `native_connection_failed` / `native_unavailable`
|
|
2091
2293
|
// / `native_update_required` indicate the bridge itself is missing,
|
|
@@ -2097,7 +2299,11 @@
|
|
|
2097
2299
|
// steps) existed but were never emitted, so everything collapsed into
|
|
2098
2300
|
// "no usable result" and pointed users at the wrong recovery.
|
|
2099
2301
|
const runErrorMessage = String(response.error?.message || '');
|
|
2100
|
-
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'
|
|
2101
2307
|
? buildContentFailure('native_request_failed', { path: 'codex.run' }, {
|
|
2102
2308
|
// The host IS installed — the request was interrupted mid-flight.
|
|
2103
2309
|
// Telling this user to reinstall would be a misdiagnosis.
|
|
@@ -2422,6 +2628,8 @@
|
|
|
2422
2628
|
submittedMode,
|
|
2423
2629
|
submittedCustomInstructions,
|
|
2424
2630
|
submittedSkillLoadingSettings,
|
|
2631
|
+
submittedEnabledLocalSkillIds,
|
|
2632
|
+
submittedEnabledSkillIds,
|
|
2425
2633
|
submittedAttachments,
|
|
2426
2634
|
submittedSkillInvocation,
|
|
2427
2635
|
executionSnapshot
|
|
@@ -2430,8 +2638,7 @@
|
|
|
2430
2638
|
title: tx('Starting the Codex skill installer.', '正在启动 Codex skill installer。'),
|
|
2431
2639
|
status: 'running'
|
|
2432
2640
|
});
|
|
2433
|
-
const
|
|
2434
|
-
const codexThreadId = activeSession?.codexThreadId || '';
|
|
2641
|
+
const codexThreadId = currentRunView?.codexThreadId || '';
|
|
2435
2642
|
const params = {
|
|
2436
2643
|
projectId: getCurrentProjectId(),
|
|
2437
2644
|
mode: submittedMode,
|
|
@@ -2439,21 +2646,23 @@
|
|
|
2439
2646
|
model: executionSnapshot.model,
|
|
2440
2647
|
reasoningEffort: executionSnapshot.reasoningEffort,
|
|
2441
2648
|
speedTier: executionSnapshot.speedTier,
|
|
2442
|
-
session:
|
|
2649
|
+
session: currentRunView?.sessionContext,
|
|
2650
|
+
clientRunId: currentRunView?.recordId || '',
|
|
2651
|
+
clientSessionId: runSessionId,
|
|
2652
|
+
accountScopeId: currentRunView?.runAccountScopeId || '',
|
|
2443
2653
|
threadId: codexThreadId || undefined,
|
|
2444
2654
|
customInstructions: submittedCustomInstructions || undefined,
|
|
2445
2655
|
loadCodexLocalSkills: submittedSkillLoadingSettings?.loadCodexLocalSkills !== false,
|
|
2446
2656
|
loadCodexOverleafSkills: submittedSkillLoadingSettings?.loadCodexOverleafSkills !== false,
|
|
2657
|
+
enabledCodexLocalSkillIds: submittedEnabledLocalSkillIds,
|
|
2658
|
+
enabledCodexOverleafSkillIds: submittedEnabledSkillIds,
|
|
2447
2659
|
attachments: submittedAttachments.length ? submittedAttachments : undefined,
|
|
2448
2660
|
skillInvocation: submittedSkillInvocation,
|
|
2449
2661
|
focusFiles: [],
|
|
2450
2662
|
skipMirrorSync: true
|
|
2451
2663
|
};
|
|
2452
2664
|
|
|
2453
|
-
const response = await
|
|
2454
|
-
method: 'codex.run',
|
|
2455
|
-
params
|
|
2456
|
-
});
|
|
2665
|
+
const response = await sendTrackedCodexRun(params);
|
|
2457
2666
|
|
|
2458
2667
|
if (!response.ok) {
|
|
2459
2668
|
if (runCancellationRequested || isRunCancellationError(response.error)) {
|
|
@@ -2653,19 +2862,31 @@
|
|
|
2653
2862
|
if (currentRunView) {
|
|
2654
2863
|
return queueComposerInput();
|
|
2655
2864
|
}
|
|
2865
|
+
if (runSubmissionPending) return;
|
|
2866
|
+
runSubmissionPending = true;
|
|
2656
2867
|
runTask().catch(error => {
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2868
|
+
if (!currentRunView) {
|
|
2869
|
+
setRunning(false);
|
|
2870
|
+
nativeChannel.clearActiveRequest();
|
|
2871
|
+
stopRunElapsedTick();
|
|
2872
|
+
runCancellationController = null;
|
|
2873
|
+
}
|
|
2662
2874
|
console.error('[codex-overleaf] failed to start task', error);
|
|
2663
|
-
appendPlainLog(tx(`Could not start Codex task: ${error.message}`, `无法启动 Codex 任务:${error.message}`));
|
|
2664
|
-
});
|
|
2875
|
+
appendPlainLog(tx(`Could not start Codex task: ${localizeVisibleReason(error.message)}`, `无法启动 Codex 任务:${localizeVisibleReason(error.message)}`));
|
|
2876
|
+
}).finally(() => { runSubmissionPending = false; });
|
|
2665
2877
|
}
|
|
2666
2878
|
|
|
2667
2879
|
const runGuidanceController = Modules.RunGuidanceController.create({
|
|
2668
|
-
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),
|
|
2669
2890
|
renderRunEvent, cssEscape, bumpUnreadIfDetached
|
|
2670
2891
|
});
|
|
2671
2892
|
|
|
@@ -2679,7 +2900,16 @@
|
|
|
2679
2900
|
readInputs: readPanelInputs,
|
|
2680
2901
|
getTask: () => panel?.querySelector('[data-task]')?.value || '',
|
|
2681
2902
|
hasAttachments: () => getComposerAttachmentsForRun().length > 0,
|
|
2682
|
-
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
|
+
}),
|
|
2683
2913
|
prepareClaim: () => providerSettingsCoordinator.ensureLoaded(),
|
|
2684
2914
|
resolveExecutionSnapshot: snapshot => RunExecutionSnapshot.resolveForExecution(snapshot, {
|
|
2685
2915
|
catalog: providerSettingsCoordinator.getCatalog()
|
|
@@ -2698,10 +2928,18 @@
|
|
|
2698
2928
|
);
|
|
2699
2929
|
},
|
|
2700
2930
|
run: item => runTask({ queuedInput: item }),
|
|
2701
|
-
|
|
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),
|
|
2702
2938
|
getContainer: () => panel?.querySelector('[data-pending-inputs]'),
|
|
2703
2939
|
createView: options => PendingInputView.create(options),
|
|
2704
2940
|
appendEvent: (title, status) => appendRunEvent({ title, status }),
|
|
2941
|
+
previewGuidance: runGuidanceController.preview,
|
|
2942
|
+
clearGuidancePreview: runGuidanceController.clearPreview,
|
|
2705
2943
|
appendGuidance: runGuidanceController.upsert,
|
|
2706
2944
|
removeGuidance: runGuidanceController.remove,
|
|
2707
2945
|
toast: (message, status) => showPluginToast(message, { status }),
|
|
@@ -2741,6 +2979,18 @@
|
|
|
2741
2979
|
}
|
|
2742
2980
|
|
|
2743
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
|
+
}
|
|
2744
2994
|
return runInputCoordinator?.queueComposerInput();
|
|
2745
2995
|
}
|
|
2746
2996
|
|
|
@@ -2790,7 +3040,7 @@
|
|
|
2790
3040
|
const nativeResult = results[0];
|
|
2791
3041
|
const nativeResponse = nativeResult?.status === 'fulfilled' ? nativeResult.value : null;
|
|
2792
3042
|
if (nativeResponse && !nativeResponse.ok) {
|
|
2793
|
-
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 没有响应'));
|
|
2794
3044
|
appendRunEvent({
|
|
2795
3045
|
title: tx(`Cancel request was not delivered: ${message}`, `中断请求没有送达:${message}`),
|
|
2796
3046
|
status: 'failed'
|
|
@@ -2832,25 +3082,8 @@
|
|
|
2832
3082
|
force: true
|
|
2833
3083
|
}
|
|
2834
3084
|
});
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
const released = result.lockReleased === true || result.cancelled === true;
|
|
2838
|
-
appendPlainLog(released
|
|
2839
|
-
? tx(
|
|
2840
|
-
`Force-released the stuck Codex run for project ${projectKey}. You can retry now.`,
|
|
2841
|
-
`已强制释放项目 ${projectKey} 的 Codex 占用,可以重试了。`
|
|
2842
|
-
)
|
|
2843
|
-
: tx(
|
|
2844
|
-
`No stuck run was found for project ${projectKey}.`,
|
|
2845
|
-
`没有发现项目 ${projectKey} 上的卡住任务。`
|
|
2846
|
-
));
|
|
2847
|
-
return response;
|
|
2848
|
-
}
|
|
2849
|
-
const message = response?.error?.message || tx('native host did not respond', 'native host 没有响应');
|
|
2850
|
-
appendPlainLog(tx(
|
|
2851
|
-
`Force-release request was not delivered: ${message}`,
|
|
2852
|
-
`强制释放请求没有送达:${message}`
|
|
2853
|
-
));
|
|
3085
|
+
appendPlainLog(Modules.RunRecoveryActions.formatForceCancelOutcome(response, tx));
|
|
3086
|
+
if (response?.ok !== true && response?.error?.message) appendPlainLog(localizeVisibleReason(response.error.message));
|
|
2854
3087
|
return response;
|
|
2855
3088
|
}
|
|
2856
3089
|
|
|
@@ -2954,6 +3187,12 @@
|
|
|
2954
3187
|
.filter(id => id && isCodexOverleafSkillEnabled(id));
|
|
2955
3188
|
}
|
|
2956
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
|
+
|
|
2957
3196
|
function buildCodexRunParams({
|
|
2958
3197
|
task,
|
|
2959
3198
|
project,
|
|
@@ -2966,12 +3205,16 @@
|
|
|
2966
3205
|
compileLogContext,
|
|
2967
3206
|
customInstructions,
|
|
2968
3207
|
skillLoadingSettings,
|
|
3208
|
+
enabledCodexOverleafSkillIds,
|
|
3209
|
+
enabledCodexLocalSkillIds,
|
|
2969
3210
|
attachments,
|
|
2970
3211
|
skillInvocation,
|
|
2971
3212
|
submittedMode
|
|
2972
3213
|
} = {}) {
|
|
2973
3214
|
const executionSnapshot = currentRunView?.executionSnapshot || captureCurrentExecutionSnapshot();
|
|
2974
|
-
const submittedState = RunExecutionSnapshot.applyToState(
|
|
3215
|
+
const submittedState = RunExecutionSnapshot.applyToState({
|
|
3216
|
+
...state, session: currentRunView?.sessionContext || state.session
|
|
3217
|
+
}, executionSnapshot);
|
|
2975
3218
|
const params = runController.buildCodexRunParams({
|
|
2976
3219
|
currentProjectId: getCurrentProjectId(),
|
|
2977
3220
|
state: submittedState,
|
|
@@ -2991,7 +3234,9 @@
|
|
|
2991
3234
|
? getSkillLoadingSettings()
|
|
2992
3235
|
: { loadCodexLocalSkills: true, loadCodexOverleafSkills: true })
|
|
2993
3236
|
: skillLoadingSettings,
|
|
2994
|
-
enabledCodexOverleafSkillIds:
|
|
3237
|
+
enabledCodexOverleafSkillIds: enabledCodexOverleafSkillIds === undefined
|
|
3238
|
+
? getEnabledCodexOverleafSkillIds() : enabledCodexOverleafSkillIds,
|
|
3239
|
+
enabledCodexLocalSkillIds,
|
|
2995
3240
|
attachments,
|
|
2996
3241
|
skillInvocation,
|
|
2997
3242
|
compileLogContext,
|
|
@@ -3009,11 +3254,23 @@
|
|
|
3009
3254
|
}
|
|
3010
3255
|
|
|
3011
3256
|
async function sendTrackedCodexRun(params) {
|
|
3012
|
-
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();
|
|
3013
3271
|
if (!compatibilityGate.ok) {
|
|
3014
3272
|
return compatibilityGate.response;
|
|
3015
3273
|
}
|
|
3016
|
-
throwIfRunCancellationRequested();
|
|
3017
3274
|
const tracked = nativeChannel.sendNativeTracked(nativeCompatibilityController.attachEvidence({
|
|
3018
3275
|
method: 'codex.run',
|
|
3019
3276
|
params
|
|
@@ -3044,6 +3301,7 @@
|
|
|
3044
3301
|
const projectKey = getCurrentProjectId();
|
|
3045
3302
|
const recovery = await activeTurnControl.recoverJournals({
|
|
3046
3303
|
projectKey,
|
|
3304
|
+
accountScopeId: cachedAccountScopeId,
|
|
3047
3305
|
requireOwnerLost: Modules.StorageDb.sharedSessionsEnabled?.() === true,
|
|
3048
3306
|
normalizeInterruptedRun: record => Object.assign(record, Modules.SessionState.normalizeRuns([record], {
|
|
3049
3307
|
restoreRunningRuns: true,
|
|
@@ -3091,7 +3349,7 @@
|
|
|
3091
3349
|
maxEvents: MAX_RUN_EVENTS
|
|
3092
3350
|
});
|
|
3093
3351
|
if (recovery.changed) {
|
|
3094
|
-
await saveState();
|
|
3352
|
+
if (await saveState({ recoveryRuns: recovery.recoveredRuns }) !== true) return;
|
|
3095
3353
|
for (const requestId of recovery.acknowledgeIds) {
|
|
3096
3354
|
await activeTurnControl.acknowledge(requestId).catch(() => false);
|
|
3097
3355
|
}
|
|
@@ -3103,11 +3361,17 @@
|
|
|
3103
3361
|
if (!StorageDb || !AuditRecords) {
|
|
3104
3362
|
return null;
|
|
3105
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;
|
|
3106
3369
|
const executionSnapshot = input.executionSnapshot
|
|
3107
3370
|
|| currentRunView?.executionSnapshot
|
|
3108
3371
|
|| captureCurrentExecutionSnapshot();
|
|
3109
3372
|
const draft = AuditRecords.buildAuditDraftRecord({
|
|
3110
|
-
|
|
3373
|
+
accountScopeId,
|
|
3374
|
+
projectId,
|
|
3111
3375
|
sessionId: input.sessionId || '',
|
|
3112
3376
|
turnId: currentRunView?.recordId || '',
|
|
3113
3377
|
mode: input.mode || executionSnapshot.mode,
|
|
@@ -3142,8 +3406,10 @@
|
|
|
3142
3406
|
return draft;
|
|
3143
3407
|
}
|
|
3144
3408
|
const record = StorageDb.buildAuditLogRecord
|
|
3145
|
-
? StorageDb.buildAuditLogRecord({ ...draft, ...updates, id: draft.id,
|
|
3146
|
-
|
|
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 };
|
|
3147
3413
|
return StorageDb.putRecord('auditLogs', record).then(() => record).catch(() => draft);
|
|
3148
3414
|
}
|
|
3149
3415
|
|
|
@@ -3173,121 +3439,10 @@
|
|
|
3173
3439
|
}
|
|
3174
3440
|
|
|
3175
3441
|
async function runSensitivePreflight(options) {
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
return { blocked: false, findings: [] };
|
|
3181
|
-
}
|
|
3182
|
-
if (!SensitiveScan?.scanSensitiveInputs && !useExistingMirror) {
|
|
3183
|
-
return { blocked: false, findings: [] };
|
|
3184
|
-
}
|
|
3185
|
-
let findings = SensitiveScan?.scanSensitiveInputs
|
|
3186
|
-
? SensitiveScan.scanSensitiveInputs({
|
|
3187
|
-
task,
|
|
3188
|
-
files: (project?.files || []).filter(isTextSnapshotFile)
|
|
3189
|
-
})
|
|
3190
|
-
: [];
|
|
3191
|
-
if (useExistingMirror) {
|
|
3192
|
-
findings = dedupeSensitiveFindings([
|
|
3193
|
-
...findings,
|
|
3194
|
-
...await scanNativeMirrorSensitiveFindings()
|
|
3195
|
-
]);
|
|
3196
|
-
}
|
|
3197
|
-
if (!findings.length) {
|
|
3198
|
-
return { blocked: false, findings: [] };
|
|
3199
|
-
}
|
|
3200
|
-
appendRunEvent({
|
|
3201
|
-
title: tx(
|
|
3202
|
-
`Sensitive content check found ${findings.length} item(s).`,
|
|
3203
|
-
`敏感内容检查发现 ${findings.length} 项。`
|
|
3204
|
-
),
|
|
3205
|
-
status: 'failed',
|
|
3206
|
-
detail: findings.slice(0, 8).map(finding => ({
|
|
3207
|
-
[tr('detailFile')]: finding.path || finding.source || 'task',
|
|
3208
|
-
[tr('detailReason')]: finding.detectorId,
|
|
3209
|
-
[tx('Preview', '预览')]: finding.preview || '[REDACTED]'
|
|
3210
|
-
}))
|
|
3211
|
-
});
|
|
3212
|
-
if (normalizedRules.sensitiveConfirmAllowed === true) {
|
|
3213
|
-
const approved = await showPluginConfirm({
|
|
3214
|
-
title: tr('sensitiveConfirmTitle'),
|
|
3215
|
-
message: formatSensitiveConfirmMessage(findings),
|
|
3216
|
-
confirmLabel: tr('sensitiveConfirmRun'),
|
|
3217
|
-
cancelLabel: tr('confirmDefaultCancel'),
|
|
3218
|
-
destructive: true
|
|
3219
|
-
});
|
|
3220
|
-
return { blocked: !approved, findings };
|
|
3221
|
-
}
|
|
3222
|
-
appendCompletionReport({
|
|
3223
|
-
conclusion: tx(
|
|
3224
|
-
'This run was blocked before Codex received project context because sensitive content was detected.',
|
|
3225
|
-
'检测到敏感内容,本轮已在向 Codex 发送项目上下文前阻止。'
|
|
3226
|
-
),
|
|
3227
|
-
status: 'blocked',
|
|
3228
|
-
operations: [],
|
|
3229
|
-
applyResults: [],
|
|
3230
|
-
nextStep: tx(
|
|
3231
|
-
'Remove the sensitive content, narrow @context, or enable explicit sensitive confirmation in Project Settings.',
|
|
3232
|
-
'请移除敏感内容、缩小 @context,或在项目设置里允许敏感内容显式确认。'
|
|
3233
|
-
)
|
|
3234
|
-
});
|
|
3235
|
-
return { blocked: true, findings };
|
|
3236
|
-
}
|
|
3237
|
-
|
|
3238
|
-
function formatSensitiveConfirmMessage(findings = []) {
|
|
3239
|
-
const visible = findings.slice(0, 8).map(finding => {
|
|
3240
|
-
const path = finding.path || finding.source || 'task';
|
|
3241
|
-
return `${path}: ${finding.detectorId || 'sensitive'} (${finding.preview || '[REDACTED]'})`;
|
|
3242
|
-
});
|
|
3243
|
-
return [
|
|
3244
|
-
tr('sensitiveConfirmMessage'),
|
|
3245
|
-
'',
|
|
3246
|
-
...visible,
|
|
3247
|
-
findings.length > visible.length ? tx(`${findings.length - visible.length} more finding(s).`, `另有 ${findings.length - visible.length} 项。`) : ''
|
|
3248
|
-
].filter(Boolean).join('\n');
|
|
3249
|
-
}
|
|
3250
|
-
|
|
3251
|
-
async function scanNativeMirrorSensitiveFindings() {
|
|
3252
|
-
try {
|
|
3253
|
-
const response = await sendBackgroundNative({
|
|
3254
|
-
method: 'mirror.scanSensitive',
|
|
3255
|
-
params: { projectId: getCurrentProjectId() }
|
|
3256
|
-
});
|
|
3257
|
-
if (response?.ok) {
|
|
3258
|
-
return Array.isArray(response.result?.findings) ? response.result.findings : [];
|
|
3259
|
-
}
|
|
3260
|
-
return [{
|
|
3261
|
-
detectorId: 'mirror-sensitive-scan-unavailable',
|
|
3262
|
-
source: 'local-mirror',
|
|
3263
|
-
preview: response?.error?.message || 'Could not scan the local mirror before Codex runs.'
|
|
3264
|
-
}];
|
|
3265
|
-
} catch (error) {
|
|
3266
|
-
return [{
|
|
3267
|
-
detectorId: 'mirror-sensitive-scan-unavailable',
|
|
3268
|
-
source: 'local-mirror',
|
|
3269
|
-
preview: error?.message || 'Could not scan the local mirror before Codex runs.'
|
|
3270
|
-
}];
|
|
3271
|
-
}
|
|
3272
|
-
}
|
|
3273
|
-
|
|
3274
|
-
function dedupeSensitiveFindings(findings = []) {
|
|
3275
|
-
const seen = new Set();
|
|
3276
|
-
const deduped = [];
|
|
3277
|
-
for (const finding of findings || []) {
|
|
3278
|
-
const key = [
|
|
3279
|
-
finding?.detectorId || '',
|
|
3280
|
-
finding?.source || '',
|
|
3281
|
-
finding?.path || '',
|
|
3282
|
-
finding?.preview || ''
|
|
3283
|
-
].join('\u0000');
|
|
3284
|
-
if (seen.has(key)) {
|
|
3285
|
-
continue;
|
|
3286
|
-
}
|
|
3287
|
-
seen.add(key);
|
|
3288
|
-
deduped.push(finding);
|
|
3289
|
-
}
|
|
3290
|
-
return deduped;
|
|
3442
|
+
return Modules.SensitivePreflight.create({
|
|
3443
|
+
SensitiveScan, normalizeGovernanceRules, isTextSnapshotFile, sendBackgroundNative,
|
|
3444
|
+
getCurrentProjectId, appendRunEvent, appendCompletionReport, showPluginConfirm, tr, tx
|
|
3445
|
+
}).run(options);
|
|
3291
3446
|
}
|
|
3292
3447
|
|
|
3293
3448
|
function appendRunCancelledReport() {
|
|
@@ -3354,6 +3509,7 @@
|
|
|
3354
3509
|
}
|
|
3355
3510
|
|
|
3356
3511
|
function handleTaskInput() {
|
|
3512
|
+
composerDraftRevision += 1;
|
|
3357
3513
|
autosizeTaskTextarea();
|
|
3358
3514
|
syncComposerSendAvailability();
|
|
3359
3515
|
renderPendingInputs();
|
|
@@ -3376,10 +3532,8 @@
|
|
|
3376
3532
|
subtitle: tr('slashInstallSkillSubtitle')
|
|
3377
3533
|
}
|
|
3378
3534
|
];
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
}
|
|
3382
|
-
for (const skill of slashCodexOverleafSkills) {
|
|
3535
|
+
const settings = getSkillLoadingSettings();
|
|
3536
|
+
for (const skill of settings.loadCodexOverleafSkills === false ? [] : slashCodexOverleafSkills) {
|
|
3383
3537
|
const id = String(skill?.id || '').trim();
|
|
3384
3538
|
if (!isSafeSkillId(id)) {
|
|
3385
3539
|
continue;
|
|
@@ -3395,13 +3549,28 @@
|
|
|
3395
3549
|
scope: 'codex-overleaf',
|
|
3396
3550
|
skillId: id,
|
|
3397
3551
|
title,
|
|
3398
|
-
subtitle: tr('
|
|
3552
|
+
subtitle: tr('slashUseOverleafSkillSubtitle')
|
|
3399
3553
|
});
|
|
3400
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
|
+
}
|
|
3401
3565
|
return commands;
|
|
3402
3566
|
}
|
|
3403
3567
|
|
|
3404
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
|
+
}
|
|
3405
3574
|
if (slashCodexOverleafSkillsLoaded || slashCodexOverleafSkillsLoading) {
|
|
3406
3575
|
return;
|
|
3407
3576
|
}
|
|
@@ -3435,7 +3604,8 @@
|
|
|
3435
3604
|
const commands = getSlashCommands().filter(command => {
|
|
3436
3605
|
return !query
|
|
3437
3606
|
|| command.title.toLowerCase().includes(query)
|
|
3438
|
-
|| command.id.toLowerCase().includes(query)
|
|
3607
|
+
|| command.id.toLowerCase().includes(query)
|
|
3608
|
+
|| String(command.searchName || '').toLowerCase().includes(query);
|
|
3439
3609
|
});
|
|
3440
3610
|
renderSlashMenu(commands);
|
|
3441
3611
|
return;
|
|
@@ -3650,6 +3820,17 @@
|
|
|
3650
3820
|
renderedSlashCommands = new Map(commands.map(command => [command.id, command]));
|
|
3651
3821
|
menu.dataset.activeIndex = '0';
|
|
3652
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`);
|
|
3653
3834
|
}
|
|
3654
3835
|
|
|
3655
3836
|
function handleSlashMenuClick(event) {
|
|
@@ -3708,6 +3889,18 @@
|
|
|
3708
3889
|
buttons.forEach((button, buttonIndex) => {
|
|
3709
3890
|
button.dataset.active = buttonIndex === index ? 'true' : 'false';
|
|
3710
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
|
+
}
|
|
3711
3904
|
}
|
|
3712
3905
|
|
|
3713
3906
|
function selectSlashCommand(commandId) {
|
|
@@ -3729,7 +3922,7 @@
|
|
|
3729
3922
|
activateSkillInstallerComposerContext();
|
|
3730
3923
|
return;
|
|
3731
3924
|
}
|
|
3732
|
-
if (command.kind === 'codex-overleaf-skill') {
|
|
3925
|
+
if (command.kind === 'codex-overleaf-skill' || command.kind === 'codex-local-skill') {
|
|
3733
3926
|
activateCodexOverleafSkillComposerContext(command);
|
|
3734
3927
|
}
|
|
3735
3928
|
}
|
|
@@ -3756,6 +3949,7 @@
|
|
|
3756
3949
|
}
|
|
3757
3950
|
|
|
3758
3951
|
function activateSkillInstallerComposerContext() {
|
|
3952
|
+
composerDraftRevision += 1;
|
|
3759
3953
|
composerSkillInvocation = {
|
|
3760
3954
|
id: 'skill-installer',
|
|
3761
3955
|
title: tr('skillInstallerComposerLabel')
|
|
@@ -3766,6 +3960,7 @@
|
|
|
3766
3960
|
}
|
|
3767
3961
|
|
|
3768
3962
|
function activateCodexOverleafSkillComposerContext(command) {
|
|
3963
|
+
composerDraftRevision += 1;
|
|
3769
3964
|
const id = String(command?.skillId || '').trim();
|
|
3770
3965
|
if (!isSafeSkillId(id)) {
|
|
3771
3966
|
return;
|
|
@@ -3773,7 +3968,7 @@
|
|
|
3773
3968
|
composerSkillInvocation = {
|
|
3774
3969
|
id,
|
|
3775
3970
|
title: String(command?.title || id).trim().slice(0, 80) || id,
|
|
3776
|
-
scope: 'codex-overleaf'
|
|
3971
|
+
scope: command.scope === 'codex-local' ? 'codex-local' : 'codex-overleaf'
|
|
3777
3972
|
};
|
|
3778
3973
|
renderComposerSkillInvocation();
|
|
3779
3974
|
const input = panel?.querySelector('[data-task]');
|
|
@@ -3781,6 +3976,7 @@
|
|
|
3781
3976
|
}
|
|
3782
3977
|
|
|
3783
3978
|
function clearComposerSkillInvocation() {
|
|
3979
|
+
composerDraftRevision += 1;
|
|
3784
3980
|
composerSkillInvocation = null;
|
|
3785
3981
|
renderComposerSkillInvocation();
|
|
3786
3982
|
panel?.querySelector('[data-task]')?.focus?.();
|
|
@@ -3791,7 +3987,7 @@
|
|
|
3791
3987
|
if (!normalized) {
|
|
3792
3988
|
return null;
|
|
3793
3989
|
}
|
|
3794
|
-
if (normalized.id === 'skill-installer') {
|
|
3990
|
+
if (normalized.id === 'skill-installer' && !normalized.scope) {
|
|
3795
3991
|
return {
|
|
3796
3992
|
id: 'skill-installer',
|
|
3797
3993
|
title: tr('skillInstallerComposerLabel')
|
|
@@ -3806,13 +4002,9 @@
|
|
|
3806
4002
|
return null;
|
|
3807
4003
|
}
|
|
3808
4004
|
const title = String(value?.title || id).trim().slice(0, 80) || id;
|
|
3809
|
-
if (id === 'skill-installer') {
|
|
3810
|
-
|
|
3811
|
-
}
|
|
3812
|
-
if (value?.scope !== 'codex-overleaf') {
|
|
3813
|
-
return null;
|
|
3814
|
-
}
|
|
3815
|
-
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 };
|
|
3816
4008
|
}
|
|
3817
4009
|
|
|
3818
4010
|
function isSafeSkillId(id) {
|
|
@@ -3833,7 +4025,7 @@
|
|
|
3833
4025
|
}
|
|
3834
4026
|
const clear = context.querySelector('[data-composer-skill-clear]');
|
|
3835
4027
|
if (clear) {
|
|
3836
|
-
const clearLabel = active?.id === 'skill-installer'
|
|
4028
|
+
const clearLabel = active?.id === 'skill-installer' && !active.scope
|
|
3837
4029
|
? tr('skillInstallerComposerClear')
|
|
3838
4030
|
: tr('composerSkillClear');
|
|
3839
4031
|
clear.title = clearLabel;
|
|
@@ -3996,19 +4188,11 @@
|
|
|
3996
4188
|
// it from cross-project queries (spec §5.2 storage rules).
|
|
3997
4189
|
window.codexOverleafDeriveAccountScopeId = () => cachedAccountScopeId;
|
|
3998
4190
|
var sharedSessionViewSync = Modules.SharedSessionViewSync.install({
|
|
3999
|
-
Bridge: Modules.SharedSessionBridge,
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
accountScopeId: cachedAccountScopeId,
|
|
4003
|
-
projectId: getCurrentProjectId(),
|
|
4004
|
-
generation: spaRouteGeneration
|
|
4005
|
-
}),
|
|
4006
|
-
getState: () => state,
|
|
4191
|
+
Bridge: Modules.SharedSessionBridge, StorageDb: Modules.StorageDb,
|
|
4192
|
+
getScope: () => ({ accountScopeId: cachedAccountScopeId, projectId: getCurrentProjectId(), generation: spaRouteGeneration }),
|
|
4193
|
+
getState: () => state, setState: next => { state = next; },
|
|
4007
4194
|
isBusy: () => Boolean(readLiveRunViewForSaveStateGuard() || document.querySelector('dialog[open]')),
|
|
4008
|
-
|
|
4009
|
-
state = normalizePanelState({ ...state, sessions }, { restoreRunningRuns: false });
|
|
4010
|
-
applyStateToPanel();
|
|
4011
|
-
},
|
|
4195
|
+
normalize: normalizePanelState, render: applyStateToPanel,
|
|
4012
4196
|
notice: error => appendStorageNoticeOnce('shared-history-refresh', tx(
|
|
4013
4197
|
`Could not refresh shared history: ${error.message}`,
|
|
4014
4198
|
`共享会话历史刷新失败:${error.message}`
|
|
@@ -4092,7 +4276,7 @@
|
|
|
4092
4276
|
chromeApi: chrome, getState: () => state, setState: next => { state = next; }, getPanel: () => panel,
|
|
4093
4277
|
applyTheme: applyPanelTheme, applyLocale: () => { applyLocaleToPanel(); refreshDashboardSoon(); },
|
|
4094
4278
|
onPreloadChange: enabled => contextTrayController.scheduleContextPrefetch({ enabled }),
|
|
4095
|
-
onSkillsChange: () => { renderLocalSkillList(); updateSkillsEntrySummary(); },
|
|
4279
|
+
onSkillsChange: () => { renderLocalSkillList(); updateSkillsEntrySummary(); updateSlashMenuForTaskInput(); },
|
|
4096
4280
|
onError: error => showPluginToast(tx(
|
|
4097
4281
|
`Global settings could not be saved or loaded: ${error.message}. Please retry.`,
|
|
4098
4282
|
`全局设置未能保存或读取:${error.message}。请重试。`), { status: 'failed', sticky: true })
|
|
@@ -4108,6 +4292,7 @@
|
|
|
4108
4292
|
storageKey = getProjectStorageKey(LEGACY_STORAGE_KEY, window.location.href);
|
|
4109
4293
|
const reloaded = await loadStoredState();
|
|
4110
4294
|
state = normalizeLoadedPanelState(reloaded);
|
|
4295
|
+
await getRunReviewController().recoverInterruptedUndoStatuses();
|
|
4111
4296
|
applyStateToPanel();
|
|
4112
4297
|
} catch (_error) {
|
|
4113
4298
|
// Reload failures fall back to the in-memory state; the next saveState
|
|
@@ -4169,6 +4354,7 @@
|
|
|
4169
4354
|
function leaveActiveProject(newId) {
|
|
4170
4355
|
const prevId = activeProjectId;
|
|
4171
4356
|
activeProjectId = newId; // null for non-project URLs (spec §5.7.1)
|
|
4357
|
+
getComposerDraftPersistence().syncScope();
|
|
4172
4358
|
if (!prevId || prevId === newId) {
|
|
4173
4359
|
return;
|
|
4174
4360
|
}
|
|
@@ -4209,6 +4395,7 @@
|
|
|
4209
4395
|
// project derivation: account menu is global chrome and should be
|
|
4210
4396
|
// readable on per-project URLs too).
|
|
4211
4397
|
await refreshAccountScopeId();
|
|
4398
|
+
getComposerDraftPersistence().syncScope();
|
|
4212
4399
|
if (generation !== spaRouteGeneration || pathname !== window.location.pathname) {
|
|
4213
4400
|
return;
|
|
4214
4401
|
}
|
|
@@ -4437,7 +4624,7 @@
|
|
|
4437
4624
|
const applied = await applyTaskOperations(project, operations);
|
|
4438
4625
|
applyResults.push(applied);
|
|
4439
4626
|
appendApplyResult(applied);
|
|
4440
|
-
recordUndoFromApply(project, applied);
|
|
4627
|
+
await recordUndoFromApply(project, applied);
|
|
4441
4628
|
const approved = await showPluginConfirm({
|
|
4442
4629
|
title: tx('Confirm delete plan?', '确认删除计划?'),
|
|
4443
4630
|
message: formatDeletePlan(result.deletePlan || []),
|
|
@@ -4451,7 +4638,7 @@
|
|
|
4451
4638
|
const deleteApplied = await applyTaskOperations(project, pendingOperations, { allowHighRisk: true });
|
|
4452
4639
|
applyResults.push(deleteApplied);
|
|
4453
4640
|
appendApplyResult(deleteApplied);
|
|
4454
|
-
recordUndoFromApply(project, deleteApplied);
|
|
4641
|
+
await recordUndoFromApply(project, deleteApplied);
|
|
4455
4642
|
appendLog(tx('Deleted files after confirmation.', '已按确认删除文件。'));
|
|
4456
4643
|
const summaryLine = appendChangeSummary({
|
|
4457
4644
|
notes,
|
|
@@ -4504,7 +4691,7 @@
|
|
|
4504
4691
|
appendOperationsPreview(operations, tx('Preparing to write', '准备写入'));
|
|
4505
4692
|
const applied = await applyTaskOperations(project, operations);
|
|
4506
4693
|
appendApplyResult(applied);
|
|
4507
|
-
recordUndoFromApply(project, applied);
|
|
4694
|
+
await recordUndoFromApply(project, applied);
|
|
4508
4695
|
appendLog(mode === 'auto' ? tx('Auto write task completed.', '自动写入任务完成。') : tx('Task completed.', '任务完成。'));
|
|
4509
4696
|
const summaryLine = appendChangeSummary({
|
|
4510
4697
|
notes,
|
|
@@ -4529,6 +4716,7 @@
|
|
|
4529
4716
|
}
|
|
4530
4717
|
|
|
4531
4718
|
async function applyTaskOperations(project, operations, options = {}) {
|
|
4719
|
+
const target = currentRunView;
|
|
4532
4720
|
const runRequireReviewing = typeof options.requireReviewing === 'boolean'
|
|
4533
4721
|
? options.requireReviewing
|
|
4534
4722
|
: state.requireReviewing === true;
|
|
@@ -4538,7 +4726,13 @@
|
|
|
4538
4726
|
title: tr('undoNoWritesTitle'),
|
|
4539
4727
|
status: 'completed'
|
|
4540
4728
|
});
|
|
4541
|
-
}
|
|
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) {
|
|
4542
4736
|
appendRunEvent({
|
|
4543
4737
|
title: tx('Saved a recoverable version before this run writes.', '已保存本轮写入前的可恢复版本。'),
|
|
4544
4738
|
status: 'completed',
|
|
@@ -4547,10 +4741,7 @@
|
|
|
4547
4741
|
}
|
|
4548
4742
|
});
|
|
4549
4743
|
}
|
|
4550
|
-
|
|
4551
|
-
if (!reviewing.ok) {
|
|
4552
|
-
return buildReviewingBlockedApplyResult(partitioned.safe, reviewing, partitioned.skipped);
|
|
4553
|
-
}
|
|
4744
|
+
|
|
4554
4745
|
const applied = partitioned.safe.length
|
|
4555
4746
|
? await callPageBridge('applyOperations', {
|
|
4556
4747
|
operations: partitioned.safe,
|
|
@@ -4558,7 +4749,7 @@
|
|
|
4558
4749
|
requireReviewing: runRequireReviewing,
|
|
4559
4750
|
requireEditing: !runRequireReviewing,
|
|
4560
4751
|
// Welcome-panel + write-guard:
|
|
4561
|
-
runProjectId:
|
|
4752
|
+
runProjectId: target?.runProjectId || ''
|
|
4562
4753
|
})
|
|
4563
4754
|
: { ok: true, applied: [], skipped: [] };
|
|
4564
4755
|
|
|
@@ -4962,7 +5153,7 @@
|
|
|
4962
5153
|
focusFiles: getActiveFocusFiles()
|
|
4963
5154
|
});
|
|
4964
5155
|
const sanitizedProject = sanitizeRunProjectSnapshot(project);
|
|
4965
|
-
if (sanitizedProject?.capabilities?.fullProjectSnapshot) {
|
|
5156
|
+
if (sanitizedProject?.capabilities?.fullProjectSnapshot || sanitizedProject?.capabilities?.pathIdentityComplete === false) {
|
|
4966
5157
|
return sanitizedProject;
|
|
4967
5158
|
}
|
|
4968
5159
|
const pageStateProject = await callPageBridge('getProjectSnapshot', {
|
|
@@ -4983,25 +5174,37 @@
|
|
|
4983
5174
|
}
|
|
4984
5175
|
|
|
4985
5176
|
function sanitizeRunProjectSnapshot(project) {
|
|
4986
|
-
if (!project || typeof project !== 'object')
|
|
4987
|
-
|
|
4988
|
-
}
|
|
4989
|
-
const activePath = normalizeSnapshotPath(project.activePath || '');
|
|
5177
|
+
if (!project || typeof project !== 'object') return project;
|
|
5178
|
+
const normalize = Modules.ProjectFiles.normalizeSafeProjectPath;
|
|
4990
5179
|
const files = Array.isArray(project.files) ? project.files : [];
|
|
4991
|
-
const
|
|
4992
|
-
const
|
|
4993
|
-
|
|
4994
|
-
|
|
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);
|
|
4995
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;
|
|
4996
5197
|
return {
|
|
4997
5198
|
...project,
|
|
4998
|
-
activePath: '',
|
|
4999
|
-
files:
|
|
5199
|
+
activePath: rejectActive ? '' : project.activePath,
|
|
5200
|
+
files: safeFiles.map(file => rejectActive && file?.active ? { ...file, active: false } : file),
|
|
5000
5201
|
capabilities: {
|
|
5001
5202
|
...(project.capabilities || {}),
|
|
5203
|
+
...(skipped.length ? { fullProjectSnapshot: false, contentComplete: false, pathIdentityComplete: false,
|
|
5204
|
+
skipped: [...(project.capabilities?.skipped || []), ...skipped] } : {}),
|
|
5002
5205
|
diagnostics: {
|
|
5003
5206
|
...(project.capabilities?.diagnostics || {}),
|
|
5004
|
-
rejectedActivePath: project.activePath
|
|
5207
|
+
...(rejectActive ? { rejectedActivePath: project.activePath } : {})
|
|
5005
5208
|
}
|
|
5006
5209
|
}
|
|
5007
5210
|
};
|
|
@@ -5087,11 +5290,8 @@
|
|
|
5087
5290
|
}
|
|
5088
5291
|
|
|
5089
5292
|
function normalizeSnapshotPath(path) {
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
.replace(/\\/g, '/')
|
|
5093
|
-
.trim()
|
|
5094
|
-
.replace(/^\/+/, '');
|
|
5293
|
+
// Snapshot/overlay/focus arrays already carry canonical names from file chips.
|
|
5294
|
+
return Modules.ProjectFiles.normalizeSafeProjectPath(path);
|
|
5095
5295
|
}
|
|
5096
5296
|
|
|
5097
5297
|
function mergeProjectWithSyncChangeBaseFiles(project = {}, syncChanges = []) {
|
|
@@ -5303,8 +5503,10 @@
|
|
|
5303
5503
|
legacyKey,
|
|
5304
5504
|
accountScopeId
|
|
5305
5505
|
);
|
|
5506
|
+
const composerAttachments = await loadComposerAttachmentsForProject(accountScopeId, projectId);
|
|
5306
5507
|
|
|
5307
5508
|
return {
|
|
5509
|
+
composerAttachments,
|
|
5308
5510
|
model: prefs.model || '', providerId: prefs.providerId || 'builtin',
|
|
5309
5511
|
reasoningEffort: prefs.reasoningEffort || '',
|
|
5310
5512
|
speedTier: prefs.speedTier || '',
|
|
@@ -5316,6 +5518,7 @@
|
|
|
5316
5518
|
loadCodexLocalSkills: prefs.loadCodexLocalSkills !== false,
|
|
5317
5519
|
loadCodexOverleafSkills: prefs.loadCodexOverleafSkills !== false,
|
|
5318
5520
|
codexOverleafSkillEnabled: prefs.codexOverleafSkillEnabled || {},
|
|
5521
|
+
codexLocalSkillEnabled: prefs.codexLocalSkillEnabled || {},
|
|
5319
5522
|
experimentalOtByProject: prefs.experimentalOtByProject || {},
|
|
5320
5523
|
customInstructionsByProject: prefs.customInstructionsByProject || {},
|
|
5321
5524
|
governanceRulesByProject: normalizeGovernanceRulesByProject(prefs.governanceRulesByProject),
|
|
@@ -5328,6 +5531,8 @@
|
|
|
5328
5531
|
focusFiles: session.focusFiles || [], selectionContext: session.selectionContext?.projectId === projectId ? session.selectionContext : null,
|
|
5329
5532
|
projectReferenceFiles: session.projectReferenceFiles || [],
|
|
5330
5533
|
codexThreadId: session.codexThreadId || '',
|
|
5534
|
+
forkedFromSessionId: session.forkedFromSessionId || '',
|
|
5535
|
+
forkedFromRunId: session.forkedFromRunId || '',
|
|
5331
5536
|
createdAt: session.createdAt,
|
|
5332
5537
|
updatedAt: session.updatedAt,
|
|
5333
5538
|
runs: Array.isArray(session.runs) ? session.runs : [],
|
|
@@ -5368,6 +5573,7 @@
|
|
|
5368
5573
|
loadCodexLocalSkills: fallback.loadCodexLocalSkills !== false,
|
|
5369
5574
|
loadCodexOverleafSkills: fallback.loadCodexOverleafSkills !== false,
|
|
5370
5575
|
codexOverleafSkillEnabled: fallback.codexOverleafSkillEnabled || {},
|
|
5576
|
+
codexLocalSkillEnabled: fallback.codexLocalSkillEnabled || {},
|
|
5371
5577
|
experimentalOtByProject: fallback.experimentalOtByProject || {},
|
|
5372
5578
|
customInstructionsByProject: fallback.customInstructionsByProject || {},
|
|
5373
5579
|
governanceRulesByProject: normalizeGovernanceRulesByProject(fallback.governanceRulesByProject),
|
|
@@ -5401,30 +5607,16 @@
|
|
|
5401
5607
|
try {
|
|
5402
5608
|
const StorageDb = Modules.StorageDb;
|
|
5403
5609
|
const Migration = Modules.StorageMigration;
|
|
5404
|
-
//
|
|
5405
|
-
//
|
|
5406
|
-
//
|
|
5407
|
-
// is in flight on a different project (mid-run SPA navigation), saving
|
|
5408
|
-
// under that URL-derived id would pollute another project's Recent-
|
|
5409
|
-
// projects entry and surface bogus disabled rows. Two safety lanes:
|
|
5410
|
-
// 1. `options.projectIdOverride` lets a caller that legitimately
|
|
5411
|
-
// needs to persist to a non-active project supply the projectId
|
|
5412
|
-
// explicitly (the only such caller today is the post-navigation
|
|
5413
|
-
// settlement path in `persistPostNavigationRunStatus`, which goes
|
|
5414
|
-
// directly through StorageDb — but the override is in place so
|
|
5415
|
-
// future callers do not have to monkey-patch around saveState).
|
|
5416
|
-
// 2. When no override is set AND a navigation-divergent run is in
|
|
5417
|
-
// flight (currentRunView.runProjectId !== URL-derived id), log a
|
|
5418
|
-
// warning and skip persistence. The settlement path owns that
|
|
5419
|
-
// run's persistence; routing through saveState would write to the
|
|
5420
|
-
// 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.
|
|
5421
5613
|
const projectIdOverride = options && typeof options.projectIdOverride === 'string'
|
|
5422
5614
|
? options.projectIdOverride
|
|
5423
5615
|
: '';
|
|
5424
5616
|
const urlProjectId = getCurrentProjectId();
|
|
5425
5617
|
const projectId = projectIdOverride || urlProjectId;
|
|
5426
5618
|
if (!projectId) {
|
|
5427
|
-
if (options?.reviewRunIds?.length) throw new Error('
|
|
5619
|
+
if (options?.requireDurable || options?.reviewRunIds?.length) throw new Error('Persistence lost its project.'); return;
|
|
5428
5620
|
}
|
|
5429
5621
|
// Read currentRunView through a defensive accessor so historical test
|
|
5430
5622
|
// harnesses that inline `saveState` without re-declaring the module-
|
|
@@ -5436,13 +5628,14 @@
|
|
|
5436
5628
|
'Skipped saveState: run is in flight on a different project than the current URL; settlement owns persistence.',
|
|
5437
5629
|
'已跳过 saveState:本次运行所属项目与当前 URL 不一致,写回由 settlement 负责。'
|
|
5438
5630
|
));
|
|
5439
|
-
if (options?.reviewRunIds?.length) throw new Error('
|
|
5631
|
+
if (options?.requireDurable || options?.reviewRunIds?.length) throw new Error('Persistence project changed.'); return;
|
|
5440
5632
|
}
|
|
5441
5633
|
const compactState = prepareStateForStorage(state, { onAggressive: notifyAggressiveCompactionOnce });
|
|
5442
5634
|
compactState.autoRecompile = state.autoRecompile;
|
|
5443
5635
|
compactState.loadCodexLocalSkills = state.loadCodexLocalSkills !== false;
|
|
5444
5636
|
compactState.loadCodexOverleafSkills = state.loadCodexOverleafSkills !== false;
|
|
5445
5637
|
compactState.codexOverleafSkillEnabled = getCodexOverleafSkillEnabled();
|
|
5638
|
+
compactState.codexLocalSkillEnabled = getCodexLocalSkillEnabled();
|
|
5446
5639
|
compactState.experimentalOtByProject = state.experimentalOtByProject;
|
|
5447
5640
|
compactState.customInstructionsByProject = state.customInstructionsByProject;
|
|
5448
5641
|
compactState.governanceRulesByProject = state.governanceRulesByProject;
|
|
@@ -5453,11 +5646,14 @@
|
|
|
5453
5646
|
? structuredClone(compactState)
|
|
5454
5647
|
: JSON.parse(JSON.stringify(compactState));
|
|
5455
5648
|
|
|
5456
|
-
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({
|
|
5457
5653
|
state: stateSnapshot,
|
|
5458
5654
|
compactState: compactStateSnapshot,
|
|
5459
5655
|
projectId,
|
|
5460
|
-
deletedSessionIds: options?.deletedSessionIds, reviewRunIds: options?.reviewRunIds,
|
|
5656
|
+
deletedSessionIds: options?.deletedSessionIds, reviewRunIds: options?.reviewRunIds, recoveryRuns: options?.recoveryRuns,
|
|
5461
5657
|
Migration,
|
|
5462
5658
|
StorageDb,
|
|
5463
5659
|
SessionPersistence: Modules.SessionPersistence,
|
|
@@ -5465,7 +5661,9 @@
|
|
|
5465
5661
|
normalizeGovernanceRulesByProject,
|
|
5466
5662
|
normalizeCustomInstructionsByProject,
|
|
5467
5663
|
persistenceContext
|
|
5468
|
-
|
|
5664
|
+
});
|
|
5665
|
+
await getComposerDraftPersistence().save({ accountScopeId: persistenceContext.scope.accountScopeId, projectId });
|
|
5666
|
+
};
|
|
5469
5667
|
|
|
5470
5668
|
const persistenceCoordinator = getScopedPersistenceCoordinator();
|
|
5471
5669
|
if (!persistenceCoordinator) {
|
|
@@ -5485,7 +5683,7 @@
|
|
|
5485
5683
|
persistScopedState
|
|
5486
5684
|
);
|
|
5487
5685
|
if (!committed.ok && (
|
|
5488
|
-
options?.reviewRunIds?.length || options?.queueMutation
|
|
5686
|
+
options?.requireDurable || options?.reviewRunIds?.length || options?.recoveryRuns?.length || options?.queueMutation
|
|
5489
5687
|
|| options?.queueMutations?.length
|
|
5490
5688
|
|| committed.reason !== 'stale_view'
|
|
5491
5689
|
)) {
|
|
@@ -5493,8 +5691,23 @@
|
|
|
5493
5691
|
code: committed.reason
|
|
5494
5692
|
});
|
|
5495
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;
|
|
5496
5707
|
}
|
|
5497
5708
|
} catch (error) {
|
|
5709
|
+
if (options?.requireDurable) throw error;
|
|
5710
|
+
if (error?.code === 'recovery_scope_changed') return;
|
|
5498
5711
|
if (
|
|
5499
5712
|
error?.code === 'account_scope_unavailable' &&
|
|
5500
5713
|
!options?.reviewRunIds?.length && !options?.queueMutation &&
|
|
@@ -5522,7 +5735,7 @@
|
|
|
5522
5735
|
emitStorageQuotaFailure(error, fallbackError);
|
|
5523
5736
|
}
|
|
5524
5737
|
if (typeof appendStorageNoticeOnce === 'function') {
|
|
5525
|
-
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)}`));
|
|
5526
5739
|
} else {
|
|
5527
5740
|
throw error;
|
|
5528
5741
|
}
|
|
@@ -5531,12 +5744,12 @@
|
|
|
5531
5744
|
}
|
|
5532
5745
|
}
|
|
5533
5746
|
|
|
5534
|
-
|
|
5535
5747
|
function prepareCompactFallbackState(inputState) {
|
|
5536
5748
|
const compact = prepareStateForStorage(inputState);
|
|
5537
5749
|
return {
|
|
5538
5750
|
...compact,
|
|
5539
5751
|
__codexOverleafCompactFallback: true,
|
|
5752
|
+
composerAttachments: [],
|
|
5540
5753
|
task: '',
|
|
5541
5754
|
session: null,
|
|
5542
5755
|
sessions: [],
|
|
@@ -5629,7 +5842,7 @@
|
|
|
5629
5842
|
// here (e.g. fallback chrome.storage.local.set rethrew).
|
|
5630
5843
|
emitStorageQuotaFailure(error, null);
|
|
5631
5844
|
}
|
|
5632
|
-
if (persistenceOptions?.reviewRunIds?.length) throw error;
|
|
5845
|
+
if (persistenceOptions?.requireDurable || persistenceOptions?.reviewRunIds?.length) throw error;
|
|
5633
5846
|
appendPlainLog(tx(`Failed to save session state: ${formatStateSaveError(error)}`, `保存会话状态失败:${formatStateSaveError(error)}`));
|
|
5634
5847
|
})
|
|
5635
5848
|
.finally(() => {
|
|
@@ -5705,7 +5918,7 @@
|
|
|
5705
5918
|
if (isStorageQuotaError(error)) {
|
|
5706
5919
|
return tx('Local session history is too large. Delete some old tasks and retry.', '本地会话记录太大,请删除一些旧任务后重试。');
|
|
5707
5920
|
}
|
|
5708
|
-
return error?.message || String(error);
|
|
5921
|
+
return localizeVisibleReason(error?.message || String(error));
|
|
5709
5922
|
}
|
|
5710
5923
|
|
|
5711
5924
|
async function persistPanelInputs(event) {
|
|
@@ -5817,18 +6030,59 @@
|
|
|
5817
6030
|
return speedSelect?.value || state?.speedTier || 'standard';
|
|
5818
6031
|
}
|
|
5819
6032
|
|
|
5820
|
-
|
|
5821
|
-
let composerAttachmentsRestoredOnce = false;
|
|
6033
|
+
let composerDraftPersistence = null;
|
|
5822
6034
|
let lastRunActivityBumpMs = 0;
|
|
5823
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
|
+
|
|
5824
6062
|
function persistComposerAttachmentsToState() {
|
|
5825
6063
|
if (typeof composerAttachmentController?.serializePersistableAttachments === 'function') {
|
|
5826
6064
|
state.composerAttachments = composerAttachmentController.serializePersistableAttachments();
|
|
5827
6065
|
}
|
|
5828
6066
|
}
|
|
5829
6067
|
|
|
5830
|
-
function clearTaskComposer({ keepAttachments = false } = {}) {
|
|
6068
|
+
function clearTaskComposer({ keepAttachments = false, submission = null } = {}) {
|
|
5831
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
|
+
}
|
|
5832
6086
|
if (taskInput) {
|
|
5833
6087
|
taskInput.value = '';
|
|
5834
6088
|
}
|
|
@@ -5841,6 +6095,30 @@
|
|
|
5841
6095
|
selectionContextView.clear();
|
|
5842
6096
|
state = updateActiveSession(state, { task: '' });
|
|
5843
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;
|
|
5844
6122
|
}
|
|
5845
6123
|
|
|
5846
6124
|
function applyStateToPanel(options = {}) {
|
|
@@ -5882,23 +6160,14 @@
|
|
|
5882
6160
|
applySessionLabel();
|
|
5883
6161
|
renderSessionList();
|
|
5884
6162
|
renderRunHistory(options);
|
|
6163
|
+
renderPendingInputs();
|
|
5885
6164
|
getTrackedChangeCaptureController().schedule();
|
|
5886
6165
|
renderContextSelection();
|
|
5887
6166
|
renderContextSummary();
|
|
5888
|
-
|
|
5889
|
-
// restored into the (empty) tray ONCE per page load, before the render
|
|
5890
|
-
// below. Re-running the gate on later renders would resurrect files the
|
|
5891
|
-
// user explicitly removed (fleet P2).
|
|
5892
|
-
if (!composerAttachmentsRestoredOnce
|
|
5893
|
-
&& Array.isArray(state.composerAttachments) && state.composerAttachments.length
|
|
5894
|
-
&& !composerAttachmentController.getAttachmentsForRun?.().length
|
|
5895
|
-
&& typeof composerAttachmentController.restorePersistedAttachments === 'function') {
|
|
5896
|
-
composerAttachmentController.restorePersistedAttachments(state.composerAttachments);
|
|
5897
|
-
}
|
|
5898
|
-
composerAttachmentsRestoredOnce = true;
|
|
6167
|
+
getComposerDraftPersistence().hydrate(state.composerAttachments);
|
|
5899
6168
|
renderComposerAttachments();
|
|
5900
6169
|
renderComposerSkillInvocation();
|
|
5901
|
-
applyLocaleToPanel();
|
|
6170
|
+
applyLocaleToPanel({ reprojectDynamic: false });
|
|
5902
6171
|
if (!panel.querySelector('[data-context-tray]')?.hidden) {
|
|
5903
6172
|
renderContextFiles();
|
|
5904
6173
|
}
|
|
@@ -5959,6 +6228,9 @@
|
|
|
5959
6228
|
}
|
|
5960
6229
|
|
|
5961
6230
|
function startRunView({
|
|
6231
|
+
sessionId = state.activeSessionId,
|
|
6232
|
+
sessionContext = runController.captureSessionContext(state),
|
|
6233
|
+
codexThreadId = '',
|
|
5962
6234
|
task,
|
|
5963
6235
|
mode,
|
|
5964
6236
|
model,
|
|
@@ -5999,41 +6271,39 @@
|
|
|
5999
6271
|
executionSnapshot: executionSnapshot
|
|
6000
6272
|
? RunExecutionSnapshot.cloneSnapshot(executionSnapshot)
|
|
6001
6273
|
: undefined,
|
|
6002
|
-
//
|
|
6003
|
-
// Immutable per-run capture of the project this run was submitted
|
|
6004
|
-
// against. Every writeback / accept / undo dispatch attaches this id
|
|
6005
|
-
// to its page-bridge params so the page-side guard can verify the run
|
|
6006
|
-
// is still acting on the same project before mutating the editor. No
|
|
6007
|
-
// language-level enforcement — the field is treated as immutable by
|
|
6008
|
-
// convention and never reassigned anywhere downstream.
|
|
6274
|
+
// Writeback/Accept/Undo carry this immutable project identity to Page.
|
|
6009
6275
|
runProjectId: getCurrentProjectId() || '',
|
|
6010
6276
|
runAccountScopeId: cachedAccountScopeId || ''
|
|
6011
6277
|
};
|
|
6012
|
-
const active =
|
|
6278
|
+
const active = findSessionById(sessionId);
|
|
6013
6279
|
const titlePatch = active?.titleSource !== 'manual'
|
|
6014
6280
|
? {
|
|
6015
6281
|
title: deriveSessionTitle([], task),
|
|
6016
6282
|
titleSource: 'auto'
|
|
6017
6283
|
}
|
|
6018
6284
|
: {};
|
|
6019
|
-
|
|
6020
|
-
runs: runController.replaceRunForRetry(
|
|
6285
|
+
updateSessionById(sessionId, {
|
|
6286
|
+
runs: runController.replaceRunForRetry(active?.runs, replaceRunId, record),
|
|
6021
6287
|
...titlePatch
|
|
6022
6288
|
});
|
|
6023
6289
|
saveStateSoon(replaceRunId ? 0 : 120);
|
|
6024
6290
|
|
|
6025
6291
|
const log = panel.querySelector('[data-log]');
|
|
6026
|
-
removeEmptyRunsMessage(log);
|
|
6027
|
-
if (replaceRunId) log.querySelector(`[data-run-id="${cssEscape(replaceRunId)}"]`)?.remove();
|
|
6028
6292
|
const root = renderRunCard(record);
|
|
6029
|
-
|
|
6030
|
-
|
|
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
|
+
}
|
|
6031
6299
|
startRunElapsedTick();
|
|
6032
6300
|
renderSessionList();
|
|
6033
6301
|
applySessionLabel();
|
|
6034
6302
|
|
|
6035
6303
|
return {
|
|
6036
|
-
sessionId
|
|
6304
|
+
sessionId,
|
|
6305
|
+
sessionContext,
|
|
6306
|
+
codexThreadId,
|
|
6037
6307
|
recordId: record.id,
|
|
6038
6308
|
// Mirror the immutable runProjectId on the view so writeback /
|
|
6039
6309
|
// accept / undo dispatches don't need to re-look up the record.
|
|
@@ -6592,311 +6862,34 @@
|
|
|
6592
6862
|
);
|
|
6593
6863
|
}
|
|
6594
6864
|
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
if (!
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|| (run.trackedChangeStatus === 'needs_review' && run.undoOperations?.some(operation => operation?.undoCreatedFile?.v === 1))) {
|
|
6610
|
-
await undoRunTrackedChanges(runId, run);
|
|
6611
|
-
return;
|
|
6612
|
-
}
|
|
6613
|
-
|
|
6614
|
-
const undoRestore = buildNoTraceUndoRestore(run);
|
|
6615
|
-
const undoOperations = undoRestore.operations;
|
|
6616
|
-
const unsafeFullFileUndo = findUnsafeFullFileUndoOperation(undoOperations, {
|
|
6617
|
-
allowSnapshotRestore: undoRestore.snapshotRestore
|
|
6618
|
-
});
|
|
6619
|
-
if (unsafeFullFileUndo) {
|
|
6620
|
-
appendRunRecordEvent(runId, {
|
|
6621
|
-
title: tr('undoUnsafeFullFileTitle'),
|
|
6622
|
-
status: 'failed',
|
|
6623
|
-
detail: {
|
|
6624
|
-
[tr('detailFile')]: unsafeFullFileUndo.path,
|
|
6625
|
-
[tr('detailReason')]: tr('undoUnsafeFullFileReason')
|
|
6626
|
-
}
|
|
6627
|
-
});
|
|
6628
|
-
return;
|
|
6629
|
-
}
|
|
6630
|
-
|
|
6631
|
-
// v1.8.0 C3: multi-file undos confirm through a per-file selection —
|
|
6632
|
-
// undoing a subset leaves undoStatus 'partial' and the button usable;
|
|
6633
|
-
// re-undoing an already-restored file is skipped by the base check.
|
|
6634
|
-
const undoPaths = Array.from(new Set(undoOperations.map(operation => operation?.path).filter(Boolean)));
|
|
6635
|
-
let selectedOperations = undoOperations;
|
|
6636
|
-
if (undoPaths.length > 1 && typeof showUndoFileSelection === 'function') {
|
|
6637
|
-
const selectedPaths = await showUndoFileSelection({
|
|
6638
|
-
title: tr('undoFileSelectTitle'),
|
|
6639
|
-
task: truncateRunTitle(run.task),
|
|
6640
|
-
confirmLabel: tr('undoFileSelectConfirm'),
|
|
6641
|
-
cancelLabel: tr('confirmDefaultCancel'),
|
|
6642
|
-
selectAllLabel: tr('undoFileSelectAll'),
|
|
6643
|
-
paths: undoPaths
|
|
6644
|
-
});
|
|
6645
|
-
if (!selectedPaths) {
|
|
6646
|
-
return;
|
|
6647
|
-
}
|
|
6648
|
-
const selectedSet = new Set(selectedPaths);
|
|
6649
|
-
selectedOperations = undoOperations.filter(operation => selectedSet.has(operation?.path));
|
|
6650
|
-
} else {
|
|
6651
|
-
const approved = await showPluginConfirm({
|
|
6652
|
-
title: tr('undoNoTraceTitle'),
|
|
6653
|
-
message: [
|
|
6654
|
-
truncateRunTitle(run.task),
|
|
6655
|
-
'',
|
|
6656
|
-
tr('undoNoTraceMessage', { files: formatOperationFiles(undoOperations) })
|
|
6657
|
-
].join('\n'),
|
|
6658
|
-
confirmLabel: tr('undoConfirm'),
|
|
6659
|
-
cancelLabel: tr('confirmDefaultCancel'),
|
|
6660
|
-
destructive: true
|
|
6661
|
-
});
|
|
6662
|
-
if (!approved) {
|
|
6663
|
-
return;
|
|
6664
|
-
}
|
|
6665
|
-
}
|
|
6666
|
-
|
|
6667
|
-
const deadlineAt = Date.now() + getPageBridgeTimeoutMs('rejectTrackedChanges');
|
|
6668
|
-
setRunUndoStatus(runId, 'running');
|
|
6669
|
-
appendRunRecordEvent(runId, {
|
|
6670
|
-
title: tr('undoNoTraceStarted'),
|
|
6671
|
-
status: 'running',
|
|
6672
|
-
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 })
|
|
6673
6879
|
});
|
|
6880
|
+
return runReviewController;
|
|
6881
|
+
}
|
|
6674
6882
|
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
baseFiles: run.undoBaseFiles || [],
|
|
6678
|
-
reviewingPolicy: 'no-trace-undo',
|
|
6679
|
-
// Undo remains bound to its original project, including after navigation.
|
|
6680
|
-
runProjectId: getRunProjectIdForWriteback(run)
|
|
6681
|
-
});
|
|
6682
|
-
const undoApplied = isUndoResultEffectivelyApplied(run, result);
|
|
6683
|
-
writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
|
|
6684
|
-
appendUndoReviewingPolicyEvent(runId, result.reviewingPolicy);
|
|
6685
|
-
appendRunRecordEvent(runId, {
|
|
6686
|
-
title: tr('undoResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
|
|
6687
|
-
status: undoApplied ? 'completed' : result.skipped?.length ? 'failed' : 'completed',
|
|
6688
|
-
detail: {
|
|
6689
|
-
[tr('detailUndone')]: (result.applied || []).map(item => ({
|
|
6690
|
-
[tr('detailAction')]: formatOperationType(item.operation?.type),
|
|
6691
|
-
[tr('detailFile')]: item.operation?.path
|
|
6692
|
-
})),
|
|
6693
|
-
[tr('detailSkipped')]: (result.skipped || []).map(item => ({
|
|
6694
|
-
[tr('detailAction')]: formatOperationType(item.operation?.type),
|
|
6695
|
-
[tr('detailFile')]: item.operation?.path,
|
|
6696
|
-
[tr('detailReason')]: formatApplyResultReason(item)
|
|
6697
|
-
}))
|
|
6698
|
-
}
|
|
6699
|
-
});
|
|
6700
|
-
// A subset undo never closes the ledger: unselected files can still
|
|
6701
|
-
// be undone later, so the run stays 'partial' and the button usable.
|
|
6702
|
-
const fullSelection = selectedOperations.length === undoOperations.length;
|
|
6703
|
-
applyLegacyUndoSettlement(
|
|
6704
|
-
runId,
|
|
6705
|
-
undoApplied && fullSelection ? 'applied' : 'partial',
|
|
6706
|
-
result
|
|
6707
|
-
);
|
|
6883
|
+
async function undoRun(runId) {
|
|
6884
|
+
return getRunReviewController().undoRun(runId);
|
|
6708
6885
|
}
|
|
6709
6886
|
|
|
6710
6887
|
async function undoRunTrackedChanges(runId, run) {
|
|
6711
|
-
|
|
6712
|
-
const trackedUndo = Array.isArray(run.undoTrackedChanges) && run.undoTrackedChanges.length > 0;
|
|
6713
|
-
const hasTextUndo = trackedUndo || buildTrackedUndoPostFiles(run).length > 0;
|
|
6714
|
-
const approved = await showPluginConfirm({
|
|
6715
|
-
title: !hasTextUndo ? tr('undoNoTraceTitle') : trackedUndo ? tr('undoTrackedTitle') : tr('undoNativeTitle'),
|
|
6716
|
-
message: [
|
|
6717
|
-
truncateRunTitle(run.task),
|
|
6718
|
-
'',
|
|
6719
|
-
...(hasTextUndo ? [trackedUndo
|
|
6720
|
-
? tr('undoTrackedMessage', { files: formatTrackedChangeFiles(run.undoTrackedChanges) })
|
|
6721
|
-
: tr('undoNativeMessage', { files: formatTrackedUndoFiles(run) })] : []),
|
|
6722
|
-
...(createdUndoOperations.length ? [tr('undoNoTraceMessage', { files: formatOperationFiles(createdUndoOperations) })] : [])
|
|
6723
|
-
].join('\n'),
|
|
6724
|
-
confirmLabel: trackedUndo ? tr('undoTrackedConfirm') : tr('undoConfirm'),
|
|
6725
|
-
cancelLabel: tr('confirmDefaultCancel'),
|
|
6726
|
-
destructive: true
|
|
6727
|
-
});
|
|
6728
|
-
if (!approved) {
|
|
6729
|
-
return;
|
|
6730
|
-
}
|
|
6731
|
-
|
|
6732
|
-
const deadlineAt = Date.now() + getPageBridgeTimeoutMs('rejectTrackedChanges');
|
|
6733
|
-
// Keep the review lifecycle until every pending part has settled.
|
|
6734
|
-
// Its local lock stays active while verified recovery progress is persisted.
|
|
6735
|
-
const lifecycleReject = (trackedUndo || createdUndoOperations.length > 0) && isTrackedChangeLifecycleRun(run);
|
|
6736
|
-
if (lifecycleReject) {
|
|
6737
|
-
trackedChangeInFlight.set(runId, 'reject');
|
|
6738
|
-
refreshRunCardControls(runId);
|
|
6739
|
-
} else {
|
|
6740
|
-
setRunUndoStatus(runId, 'running');
|
|
6741
|
-
}
|
|
6742
|
-
appendRunRecordEvent(runId, {
|
|
6743
|
-
title: !hasTextUndo ? tr('undoNoTraceStarted') : trackedUndo ? tr('undoTrackedStarted') : tr('undoNativeStarted'),
|
|
6744
|
-
status: 'running',
|
|
6745
|
-
detail: { [tr('detailWillUndo')]: !hasTextUndo ? formatOperationFiles(createdUndoOperations)
|
|
6746
|
-
: trackedUndo ? formatTrackedChangeFiles(run.undoTrackedChanges) : formatTrackedUndoFiles(run) }
|
|
6747
|
-
});
|
|
6748
|
-
|
|
6749
|
-
try {
|
|
6750
|
-
const trackedUndoPostFilesAtDispatch = buildTrackedUndoPostFiles(run);
|
|
6751
|
-
let result = hasTextUndo ? await callPageBridge('rejectTrackedChanges', {
|
|
6752
|
-
trackedChanges: run.undoTrackedChanges || [], deadlineAt,
|
|
6753
|
-
expectedFiles: run.undoExpectedFiles || [],
|
|
6754
|
-
postFiles: trackedUndoPostFilesAtDispatch,
|
|
6755
|
-
untrackedUndo: run.executionSnapshot
|
|
6756
|
-
? run.executionSnapshot.requireReviewing === false && ['submitted', 'legacy-captured'].includes(run.executionSnapshot.source)
|
|
6757
|
-
: getTrackedChangeCaptureController().hasLegacyUntrackedCheckpoint(run),
|
|
6758
|
-
// The page-side project guard still refuses writes after navigation.
|
|
6759
|
-
runProjectId: getRunProjectIdForWriteback(run)
|
|
6760
|
-
}) : { ok: true, applied: [], skipped: [] };
|
|
6761
|
-
if (createdUndoOperations.length && lifecycleReject && hasTextUndo) {
|
|
6762
|
-
WritebackSettlement.attachUndoNotVerifiedFailure(run, result, { buildFailure: buildContentFailure, postFiles: trackedUndoPostFilesAtDispatch });
|
|
6763
|
-
await applyTrackedChangeSettlement(runId, 'reject', { ...result, ok: false });
|
|
6764
|
-
}
|
|
6765
|
-
if (createdUndoOperations.length) {
|
|
6766
|
-
let createdResult;
|
|
6767
|
-
try {
|
|
6768
|
-
if (result.ok === false || result.skipped?.length) {
|
|
6769
|
-
throw new Error('Text changes remain unresolved; created files were kept for the next undo attempt.');
|
|
6770
|
-
}
|
|
6771
|
-
createdResult = await callPageBridge('applyOperations', {
|
|
6772
|
-
operations: createdUndoOperations, deadlineAt,
|
|
6773
|
-
baseFiles: run.undoBaseFiles || [],
|
|
6774
|
-
reviewingPolicy: 'no-trace-undo',
|
|
6775
|
-
runProjectId: getRunProjectIdForWriteback(run)
|
|
6776
|
-
});
|
|
6777
|
-
} catch (error) {
|
|
6778
|
-
createdResult = { ok: false, applied: [], skipped: createdUndoOperations.map(operation => ({
|
|
6779
|
-
operation, result: { ok: false, code: 'undo_operation_failed', reason: error.message }
|
|
6780
|
-
})) };
|
|
6781
|
-
}
|
|
6782
|
-
const applied = [...(result.applied || []), ...(createdResult.applied || [])];
|
|
6783
|
-
const skipped = [...(result.skipped || []), ...(createdResult.skipped || [])];
|
|
6784
|
-
for (const operation of createdUndoOperations) {
|
|
6785
|
-
if (!(createdResult.applied || []).some(entry => entry.operation?.path === operation.path
|
|
6786
|
-
&& entry.result?.ok === true && entry.result?.verified === true)
|
|
6787
|
-
&& !(createdResult.skipped || []).some(entry => entry.operation?.path === operation.path)) {
|
|
6788
|
-
skipped.push({ operation, result: { ok: false, code: 'undo_operation_failed',
|
|
6789
|
-
reason: 'The created file has no verified deletion receipt.' } });
|
|
6790
|
-
}
|
|
6791
|
-
}
|
|
6792
|
-
result = { ...result, ok: result.ok !== false && createdResult.ok !== false && !skipped.length, applied, skipped };
|
|
6793
|
-
}
|
|
6794
|
-
writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
|
|
6795
|
-
appendRunRecordEvent(runId, {
|
|
6796
|
-
title: createdUndoOperations.length ? tr('undoResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }) : trackedUndo
|
|
6797
|
-
? tr('undoTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 })
|
|
6798
|
-
: tr('undoNativeResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
|
|
6799
|
-
status: result.skipped?.length ? 'failed' : 'completed',
|
|
6800
|
-
detail: {
|
|
6801
|
-
[trackedUndo && !createdUndoOperations.length ? tr('detailRejected') : tr('detailUndone')]: (result.applied || []).map(item => ({
|
|
6802
|
-
[tr('detailFile')]: item.trackedChange?.path || item.operation?.path || tr('unknownFile'),
|
|
6803
|
-
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || item.operation?.type
|
|
6804
|
-
})),
|
|
6805
|
-
[tr('detailSkipped')]: (result.skipped || []).map(item => ({
|
|
6806
|
-
[tr('detailFile')]: item.trackedChange?.path || item.operation?.path || tr('unknownFile'),
|
|
6807
|
-
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || item.operation?.type || '',
|
|
6808
|
-
[tr('detailReason')]: formatBridgeResultReason(item.result, item.trackedChange?.path || item.operation?.path)
|
|
6809
|
-
}))
|
|
6810
|
-
}
|
|
6811
|
-
});
|
|
6812
|
-
if (lifecycleReject) {
|
|
6813
|
-
WritebackSettlement.attachUndoNotVerifiedFailure(run, result, {
|
|
6814
|
-
buildFailure: buildContentFailure,
|
|
6815
|
-
postFiles: trackedUndoPostFilesAtDispatch
|
|
6816
|
-
});
|
|
6817
|
-
await applyTrackedChangeSettlement(runId, 'reject', result);
|
|
6818
|
-
return;
|
|
6819
|
-
}
|
|
6820
|
-
applyLegacyUndoSettlement(runId, result.skipped?.length ? 'partial' : 'applied', result);
|
|
6821
|
-
} finally {
|
|
6822
|
-
if (lifecycleReject) {
|
|
6823
|
-
trackedChangeInFlight.delete(runId);
|
|
6824
|
-
refreshRunCardControls(runId);
|
|
6825
|
-
}
|
|
6826
|
-
}
|
|
6888
|
+
return getRunReviewController().undoRunTrackedChanges(runId, run);
|
|
6827
6889
|
}
|
|
6828
6890
|
|
|
6829
6891
|
async function acceptRun(runId) {
|
|
6830
|
-
|
|
6831
|
-
if (!run || !isTrackedChangeLifecycleRun(run)) {
|
|
6832
|
-
return;
|
|
6833
|
-
}
|
|
6834
|
-
const status = run.trackedChangeStatus || '';
|
|
6835
|
-
// pending and needs_review are both actionable per §7: needs_review means
|
|
6836
|
-
// the prior attempt could not prove a clean post-action state, and the
|
|
6837
|
-
// user is supposed to be able to retry after inspecting Overleaf.
|
|
6838
|
-
if (status !== 'pending' && status !== 'needs_review') {
|
|
6839
|
-
return;
|
|
6840
|
-
}
|
|
6841
|
-
if (!Array.isArray(run.undoTrackedChanges) || !run.undoTrackedChanges.length) {
|
|
6842
|
-
return;
|
|
6843
|
-
}
|
|
6844
|
-
if (trackedChangeInFlight.get(runId)) {
|
|
6845
|
-
return;
|
|
6846
|
-
}
|
|
6847
|
-
|
|
6848
|
-
trackedChangeInFlight.set(runId, 'accept');
|
|
6849
|
-
refreshRunCardControls(runId);
|
|
6850
|
-
appendRunRecordEvent(runId, {
|
|
6851
|
-
title: tr('runAcceptTrackedStarted'),
|
|
6852
|
-
status: 'running',
|
|
6853
|
-
detail: { [tr('detailAccepted')]: formatTrackedChangeFiles(run.undoTrackedChanges) }
|
|
6854
|
-
});
|
|
6855
|
-
|
|
6856
|
-
try {
|
|
6857
|
-
const result = await callPageBridge('acceptTrackedChanges', {
|
|
6858
|
-
trackedChanges: run.undoTrackedChanges || [],
|
|
6859
|
-
expectedFiles: run.undoExpectedFiles || [],
|
|
6860
|
-
postFiles: buildTrackedUndoPostFiles(run),
|
|
6861
|
-
appliedOperations: Array.isArray(run.appliedOperations) ? run.appliedOperations : [],
|
|
6862
|
-
runProjectId: getRunProjectIdForWriteback(run)
|
|
6863
|
-
});
|
|
6864
|
-
appendAcceptDiagnosticEvents(runId, Array.isArray(result.diagnostics) ? result.diagnostics : []);
|
|
6865
|
-
appendRunRecordEvent(runId, {
|
|
6866
|
-
title: tr('runAcceptTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
|
|
6867
|
-
status: result.skipped?.length ? 'failed' : 'completed',
|
|
6868
|
-
detail: {
|
|
6869
|
-
[tr('detailAccepted')]: (result.applied || []).map(item => ({
|
|
6870
|
-
[tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
|
|
6871
|
-
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
|
|
6872
|
-
})),
|
|
6873
|
-
[tr('detailSkipped')]: (result.skipped || []).map(item => ({
|
|
6874
|
-
[tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
|
|
6875
|
-
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || '',
|
|
6876
|
-
[tr('detailReason')]: formatBridgeResultReason(item.result, item.trackedChange?.path)
|
|
6877
|
-
}))
|
|
6878
|
-
}
|
|
6879
|
-
});
|
|
6880
|
-
if (result.ok === false) {
|
|
6881
|
-
appendRunRecordEvent(runId, {
|
|
6882
|
-
title: tr('runAcceptTrackedFailed'),
|
|
6883
|
-
status: 'failed',
|
|
6884
|
-
detail: {
|
|
6885
|
-
[tr('detailReason')]: tr('runAcceptTrackedFailedReason')
|
|
6886
|
-
}
|
|
6887
|
-
});
|
|
6888
|
-
if (!WritebackSettlement.isSuccessfulTrackedChangeSettlement(result)) {
|
|
6889
|
-
refreshRunCardControls(runId);
|
|
6890
|
-
}
|
|
6891
|
-
}
|
|
6892
|
-
WritebackSettlement.attachAcceptNotVerifiedFailure(run, result, {
|
|
6893
|
-
buildFailure: buildContentFailure
|
|
6894
|
-
});
|
|
6895
|
-
await applyTrackedChangeSettlement(runId, 'accept', result);
|
|
6896
|
-
} finally {
|
|
6897
|
-
trackedChangeInFlight.delete(runId);
|
|
6898
|
-
refreshRunCardControls(runId);
|
|
6899
|
-
}
|
|
6892
|
+
return getRunReviewController().acceptRun(runId);
|
|
6900
6893
|
}
|
|
6901
6894
|
|
|
6902
6895
|
function getRunProjectIdForWriteback(run) {
|
|
@@ -6905,32 +6898,6 @@
|
|
|
6905
6898
|
: '';
|
|
6906
6899
|
}
|
|
6907
6900
|
|
|
6908
|
-
// Translates the page bridge's Accept All `diagnostics` array into one
|
|
6909
|
-
// run-card event row per step. Each entry is `{ step, info }`; the step
|
|
6910
|
-
// names map 1:1 to the runAcceptTrackedStep* i18n keys. We render `info`
|
|
6911
|
-
// as the event detail so the user can copy-paste the full trace without
|
|
6912
|
-
// any extra page-state query.
|
|
6913
|
-
function appendAcceptDiagnosticEvents(runId, diagnostics) {
|
|
6914
|
-
for (const entry of diagnostics || []) {
|
|
6915
|
-
const step = entry && entry.step;
|
|
6916
|
-
const info = (entry && entry.info) || {};
|
|
6917
|
-
const titleKey = WritebackSettlement.ACCEPT_DIAGNOSTIC_TITLE_KEYS[step];
|
|
6918
|
-
if (!titleKey) {
|
|
6919
|
-
continue;
|
|
6920
|
-
}
|
|
6921
|
-
const title = titleKey === 'runAcceptTrackedStepReplayStart' || titleKey === 'runAcceptTrackedStepReplayDone'
|
|
6922
|
-
? tr(titleKey, { path: info.path || tr('unknownFile') })
|
|
6923
|
-
: tr(titleKey);
|
|
6924
|
-
const status = WritebackSettlement.acceptDiagnosticStatus(step, info);
|
|
6925
|
-
appendRunRecordEvent(runId, {
|
|
6926
|
-
title,
|
|
6927
|
-
kind: 'activity',
|
|
6928
|
-
status,
|
|
6929
|
-
detail: info
|
|
6930
|
-
});
|
|
6931
|
-
}
|
|
6932
|
-
}
|
|
6933
|
-
|
|
6934
6901
|
function buildContentFailure(code, operation, overrides) {
|
|
6935
6902
|
return WritebackSettlement.buildContentFailure(
|
|
6936
6903
|
code,
|
|
@@ -6940,27 +6907,29 @@
|
|
|
6940
6907
|
);
|
|
6941
6908
|
}
|
|
6942
6909
|
|
|
6943
|
-
async function applyTrackedChangeSettlement(runId, kind, result) {
|
|
6910
|
+
async function applyTrackedChangeSettlement(runId, kind, result, options = {}) {
|
|
6944
6911
|
const record = findRunRecord(runId);
|
|
6945
|
-
const
|
|
6946
|
-
const run = kind === 'reject' && createdUndo ? advanceRunUndoProgress(record, result) : record;
|
|
6912
|
+
const run = kind === 'reject' ? advanceRunUndoProgress(record, result) : record;
|
|
6947
6913
|
const settlement = WritebackSettlement.settleTrackedChangeLifecycle({
|
|
6948
6914
|
kind,
|
|
6949
6915
|
run,
|
|
6950
6916
|
result,
|
|
6951
6917
|
failureReasons: FailureReasons
|
|
6952
6918
|
});
|
|
6953
|
-
if (settlement.decision === 'blocked'
|
|
6919
|
+
if (settlement.decision === 'blocked'
|
|
6920
|
+
&& !(kind === 'reject' && (run !== record || record?.undoOperations?.some(Modules.UndoOperations.isFileUndoOperation)))) {
|
|
6954
6921
|
return;
|
|
6955
6922
|
}
|
|
6956
6923
|
await Modules.ScopedPersistenceCoordinator.applyReviewTransition({
|
|
6957
6924
|
getState: () => state, runId, transition: WritebackSettlement.applySettlementTransition(run, settlement),
|
|
6958
|
-
persist: flushQueuedSaveState
|
|
6925
|
+
persist: flushQueuedSaveState, assertCurrent: options.assertCurrent
|
|
6959
6926
|
});
|
|
6960
6927
|
}
|
|
6961
6928
|
|
|
6962
6929
|
|
|
6963
6930
|
function advanceRunUndoProgress(run, result) {
|
|
6931
|
+
getDirectoryCleanupRecovery().capture({ recordId: run.id, sessionId: state.activeSessionId,
|
|
6932
|
+
runProjectId: run.runProjectId, runAccountScopeId: run.runAccountScopeId }, result);
|
|
6964
6933
|
const postFiles = buildTrackedUndoPostFiles(run);
|
|
6965
6934
|
return Modules.UndoOperations.advanceUndoCheckpoint(run, result, file =>
|
|
6966
6935
|
WritebackSettlement.isUndoVerifiedContentMatching({ undoExpectedFiles: [file] }, result, { postFiles })
|
|
@@ -6972,9 +6941,9 @@
|
|
|
6972
6941
|
}
|
|
6973
6942
|
const trackedEditorUndoCount = hasTrackedEditorUndo(run) ? 1 : 0;
|
|
6974
6943
|
if (trackedEditorUndoCount) {
|
|
6975
|
-
return trackedEditorUndoCount +
|
|
6944
|
+
return trackedEditorUndoCount + Modules.UndoOperations.getFileUndoOperations(run).length;
|
|
6976
6945
|
}
|
|
6977
|
-
return (run.undoOperations
|
|
6946
|
+
return Modules.UndoOperations.prepareRunUndo(run).undoOperations.length
|
|
6978
6947
|
+ (run.undoTrackedChanges?.length || 0);
|
|
6979
6948
|
}
|
|
6980
6949
|
|
|
@@ -6986,17 +6955,39 @@
|
|
|
6986
6955
|
sessionId: target.sessionId, runId: target.recordId, createdAt: new Date().toISOString() };
|
|
6987
6956
|
}
|
|
6988
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
|
+
}
|
|
6989
6968
|
async function stageWritebackRecovery(operations, target, requireReviewing) {
|
|
6990
|
-
const
|
|
6991
|
-
|
|
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();
|
|
6992
6982
|
const creates = operations.filter(op => op.type === 'create' && typeof op.content === 'string');
|
|
6993
|
-
record.retryRequireReviewing = requireReviewing === true;
|
|
6983
|
+
record.retryRequireReviewing = requireReviewing === true;
|
|
6994
6984
|
if (!creates.length) return;
|
|
6995
|
-
const candidate = Modules.WritebackIntent.normalize({ ...
|
|
6985
|
+
const candidate = Modules.WritebackIntent.normalize({ ...owner, id: crypto.randomUUID(),
|
|
6996
6986
|
operations: creates, baseFiles: [], requireReviewing });
|
|
6997
6987
|
const safe = Modules.SessionState.pickWritebackRecovery({ ...record, retryWriteback: candidate }).retryWriteback;
|
|
6998
6988
|
if (safe) record.retryWriteback = safe; else delete record.retryWriteback;
|
|
6999
6989
|
await flushQueuedSaveState();
|
|
6990
|
+
assertCurrent();
|
|
7000
6991
|
}
|
|
7001
6992
|
|
|
7002
6993
|
async function settleWritebackRecovery(applied, project, verification, target) {
|
|
@@ -7040,6 +7031,8 @@
|
|
|
7040
7031
|
|| owner.accountScopeId !== cachedAccountScopeId || owner.sessionId !== session.id
|
|
7041
7032
|
|| owner.runId !== record.id || record.undoStatus === 'applied' || record.trackedChangeStatus === 'rejected') return;
|
|
7042
7033
|
const originalStatus = record.status;
|
|
7034
|
+
const retryAutoRecompile = typeof record.executionSnapshot?.autoRecompile === 'boolean'
|
|
7035
|
+
? record.executionSnapshot.autoRecompile : undefined;
|
|
7043
7036
|
// State is renormalized during the awaits below (probes, session sync), which
|
|
7044
7037
|
// replaces run objects; always write through the current copy.
|
|
7045
7038
|
const live = () => findRunRecord(runId, session.id) || record;
|
|
@@ -7075,6 +7068,7 @@
|
|
|
7075
7068
|
const result = await applySyncChangesToOverleaf(intent.operations.map(op => op.type === 'edit'
|
|
7076
7069
|
? { type: 'write', path: op.path, patches: op.patches, content: op.replaceAll } : op.type === 'delete' ? { type: 'delete', path: op.path } : { ...op, type: 'create' }),
|
|
7077
7070
|
{ id: owner.projectId, files: intent.baseFiles }, { mode: 'auto', requireReviewing: intent.requireReviewing,
|
|
7071
|
+
autoRecompile: retryAutoRecompile,
|
|
7078
7072
|
retryCreates: true, assistantMessage: tx('Retried the remaining file transfers for this task.', '已重试本轮未完成的文件同步。') });
|
|
7079
7073
|
assertOwner();
|
|
7080
7074
|
if (result.settlement?.facts) live().settlement = WritebackSettlement.mergeRetrySettlement(live().settlement, result.settlement.facts); // Keep files outside the retry.
|
|
@@ -7195,7 +7189,9 @@
|
|
|
7195
7189
|
});
|
|
7196
7190
|
}
|
|
7197
7191
|
|
|
7198
|
-
function recordUndoFromApply(project, applyResult) {
|
|
7192
|
+
async function recordUndoFromApply(project, applyResult) {
|
|
7193
|
+
const directoryRecovery = Modules.DirectoryCleanupRecovery ? getDirectoryCleanupRecovery() : null;
|
|
7194
|
+
directoryRecovery?.capture(currentRunView, applyResult);
|
|
7199
7195
|
const appliedEntries = getAppliedEntries(applyResult).filter(entry => entry.result?.idempotent !== true);
|
|
7200
7196
|
if (!currentRunView?.recordId || !appliedEntries.length) {
|
|
7201
7197
|
return;
|
|
@@ -7231,6 +7227,7 @@
|
|
|
7231
7227
|
combinedTrackedChanges,
|
|
7232
7228
|
record.undoExpectedFiles
|
|
7233
7229
|
);
|
|
7230
|
+
record.undoOperations = Modules.UndoOperations.getFileUndoOperations(record);
|
|
7234
7231
|
record.undoStatus = '';
|
|
7235
7232
|
record.partialWriteback = skippedEntries.length > 0;
|
|
7236
7233
|
// Recording tracked-change refs enters the run into the tracked-change
|
|
@@ -7363,6 +7360,7 @@
|
|
|
7363
7360
|
activity: Modules.RunActivityModel.capture(event, sanitizeAssistantVisibleText),
|
|
7364
7361
|
guidanceId: sanitizeAssistantVisibleText(event.guidanceId)
|
|
7365
7362
|
};
|
|
7363
|
+
if (event.failure) normalized.failure = sanitizeAssistantVisibleValue(event.failure);
|
|
7366
7364
|
record.events = [...(record.events || []), normalized].slice(-MAX_RUN_EVENTS);
|
|
7367
7365
|
saveStateSoon();
|
|
7368
7366
|
|
|
@@ -7387,21 +7385,8 @@
|
|
|
7387
7385
|
refreshRunCardControls(runId);
|
|
7388
7386
|
}
|
|
7389
7387
|
|
|
7390
|
-
function applyLegacyUndoSettlement(runId, undoStatus, result) {
|
|
7391
|
-
|
|
7392
|
-
if (!run) return;
|
|
7393
|
-
const checkpoint = run.undoOperations?.some(operation => operation?.undoCreatedFile?.v === 1)
|
|
7394
|
-
? advanceRunUndoProgress(run, result) : run;
|
|
7395
|
-
Object.assign(run, WritebackSettlement.applySettlementTransition(
|
|
7396
|
-
checkpoint,
|
|
7397
|
-
WritebackSettlement.settleLegacyUndo({
|
|
7398
|
-
run: checkpoint,
|
|
7399
|
-
status: undoStatus,
|
|
7400
|
-
result
|
|
7401
|
-
})
|
|
7402
|
-
));
|
|
7403
|
-
saveStateSoon();
|
|
7404
|
-
refreshRunCardControls(runId);
|
|
7388
|
+
async function applyLegacyUndoSettlement(runId, undoStatus, result) {
|
|
7389
|
+
return getRunReviewController().applyLegacyUndoSettlement(runId, undoStatus, result);
|
|
7405
7390
|
}
|
|
7406
7391
|
|
|
7407
7392
|
function refreshRunCardControls(runId) {
|