codex-overleaf-link 2.5.0 → 2.6.0
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 +155 -392
- package/README.zh-CN.md +154 -393
- 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 +283 -55
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +129 -16
- package/extension/src/backgroundUpdateCoordinator.js +242 -29
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +171 -8
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +104 -47
- package/extension/src/content/composerPanel.js +110 -12
- package/extension/src/content/contentRuntime.js +1555 -926
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +48 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +11077 -3412
- package/extension/src/content/generated/content.bundle.meta.json +14 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +10 -0
- package/extension/src/content/localSkillsPanel.js +104 -14
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +47 -11
- package/extension/src/content/modelPicker.js +8 -4
- package/extension/src/content/nativeCompatibilityController.js +74 -30
- package/extension/src/content/otWarmMirror.js +5 -6
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +17 -4
- package/extension/src/content/panelMaintenance.js +46 -14
- package/extension/src/content/panelRenderer.js +57 -27
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +67 -6
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +73 -14
- package/extension/src/content/recentProjects.js +15 -8
- package/extension/src/content/runActivitySummary.js +160 -18
- package/extension/src/content/runCompletionMeta.js +532 -0
- package/extension/src/content/runController.js +39 -15
- package/extension/src/content/runFailureNotice.js +29 -13
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +157 -47
- package/extension/src/content/runRecoveryActions.js +147 -0
- package/extension/src/content/runResultActions.js +28 -143
- package/extension/src/content/runReviewController.js +633 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +367 -175
- package/extension/src/content/scopedPersistencePanelState.js +134 -20
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +153 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +50 -4
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +22 -5
- package/extension/src/content/settingsWorkbench.js +132 -89
- package/extension/src/content/sharedSessionViewSync.js +49 -11
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +118 -12
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +116 -94
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/overleafCapabilities.js +4 -1
- 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/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +114 -26
- 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 +103 -155
- package/extension/src/pageBridge.js +126 -69
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +373 -17
- package/extension/src/shared/auditRecords.js +4 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +129 -66
- package/extension/src/shared/composerAttachmentDraft.js +156 -0
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +349 -4
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +590 -24
- package/extension/src/shared/lineReferences.js +38 -31
- 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/panelIcons.js +39 -0
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/reviewing.js +130 -1
- package/extension/src/shared/runActivityModel.js +70 -4
- package/extension/src/shared/runExecutionSnapshot.js +13 -0
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +132 -28
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +118 -27
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +166 -52
- package/extension/src/shared/storageDb.js +90 -26
- package/extension/src/shared/storageMigration.js +78 -112
- package/extension/src/shared/storageRunActions.js +267 -30
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +253 -32
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +17 -3
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +58 -40
- package/extension/styles/panel.css +1123 -493
- 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 +269 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +20 -1
- package/native-host/src/chatCompletionsBridge.js +48 -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 +114 -127
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +234 -198
- 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/providerContextError.js +20 -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 +92 -57
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +409 -46
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +5 -8
- package/scripts/install-managed.mjs +1 -0
|
@@ -15,11 +15,56 @@
|
|
|
15
15
|
return JSON.parse(JSON.stringify(value));
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// Review owns recovery progress and its settlement decision. Log events,
|
|
19
|
+
// compilation/mirror evidence, and later draft/display data have other writers.
|
|
20
|
+
const REVIEW_FIELDS = ['appliedOperations', 'undoOperations', 'undoBaseFiles', 'undoTrackedChanges',
|
|
21
|
+
'undoExpectedFiles', 'undoStatus', 'trackedChangeStatus', 'trackedChangeCaptures', 'interruptedDraft',
|
|
22
|
+
'retryWriteback', 'saveCheck'];
|
|
23
|
+
const REVIEW_FACT_FIELDS = ['documentEffect', 'fileSettlements', 'failures', 'recoveryAvailability', 'recoveryDisposition'];
|
|
24
|
+
const AUXILIARY_EVIDENCE = ['mirrored', 'compiled'];
|
|
25
|
+
const REVIEW_SOURCE_FIELDS = ['id', 'runProjectId', 'runAccountScopeId', 'startedAt', 'nativeEventSeq',
|
|
26
|
+
'codexThreadId', 'codexTurnId', 'executionSnapshot', 'forkSnapshot'];
|
|
27
|
+
const factsOf = record => record?.settlement || record?.settlementFacts || {};
|
|
28
|
+
function reviewSnapshot(record, SessionState) {
|
|
29
|
+
const normalized = SessionState?.normalizeRuns?.([record])?.[0] || record;
|
|
30
|
+
const facts = factsOf(normalized);
|
|
31
|
+
return JSON.stringify([
|
|
32
|
+
[...REVIEW_SOURCE_FIELDS, ...REVIEW_FIELDS].map(key => normalized[key]),
|
|
33
|
+
REVIEW_FACT_FIELDS.map(key => facts[key]),
|
|
34
|
+
Object.entries(facts.evidence || {}).filter(([key]) => !AUXILIARY_EVIDENCE.includes(key)).sort(([a], [b]) => a.localeCompare(b))
|
|
35
|
+
]);
|
|
36
|
+
}
|
|
37
|
+
function restoreReviewFields(record, previous, staged, legacyUndo) {
|
|
38
|
+
for (const key of REVIEW_FIELDS) {
|
|
39
|
+
if (Object.hasOwn(previous, key)) record[key] = cloneSnapshot(previous[key]);
|
|
40
|
+
else delete record[key];
|
|
41
|
+
}
|
|
42
|
+
Object.assign(record, legacyUndo ? { undoStatus: 'partial' } : { trackedChangeStatus: 'needs_review' });
|
|
43
|
+
const currentFacts = factsOf(record), stagedFacts = factsOf(staged);
|
|
44
|
+
const restored = cloneSnapshot(factsOf(previous));
|
|
45
|
+
const preserveLateFields = (target, current, initial, excluded) => {
|
|
46
|
+
for (const key of new Set([...Object.keys(current), ...Object.keys(initial)])) {
|
|
47
|
+
if (excluded.includes(key) || JSON.stringify(current[key]) === JSON.stringify(initial[key])) continue;
|
|
48
|
+
if (Object.hasOwn(current, key)) target[key] = cloneSnapshot(current[key]); else delete target[key];
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
preserveLateFields(restored, currentFacts, stagedFacts, [...REVIEW_FACT_FIELDS, 'evidence']);
|
|
52
|
+
const evidence = { ...(restored.evidence || {}) };
|
|
53
|
+
const currentEvidence = currentFacts.evidence || {}, stagedEvidence = stagedFacts.evidence || {};
|
|
54
|
+
preserveLateFields(evidence, currentEvidence, stagedEvidence,
|
|
55
|
+
[...Object.keys(currentEvidence), ...Object.keys(stagedEvidence)].filter(key => !AUXILIARY_EVIDENCE.includes(key)));
|
|
56
|
+
if (Object.keys(evidence).length) restored.evidence = evidence; else delete restored.evidence;
|
|
57
|
+
delete record.settlementFacts;
|
|
58
|
+
if (Object.keys(restored).length) record.settlement = restored; else delete record.settlement;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function applyReviewTransition({ getState, runId, transition, persist, legacyUndo = false, assertCurrent, SessionState }) {
|
|
19
62
|
const targets = () => Array.from(new Set([
|
|
20
63
|
...(getState().runs || []),
|
|
21
64
|
...(getState().sessions || []).flatMap(session => session.runs || [])
|
|
22
65
|
])).filter(run => run.id === runId);
|
|
66
|
+
assertCurrent?.();
|
|
67
|
+
const capturedState = getState(), capturedSessionId = capturedState.activeSessionId;
|
|
23
68
|
const records = targets();
|
|
24
69
|
const sessions = (getState().sessions || []).filter(session =>
|
|
25
70
|
(session.runs || []).some(run => run.id === runId));
|
|
@@ -31,15 +76,31 @@
|
|
|
31
76
|
(Date.parse(session.lastActivityAt) || 0) + 1)).toISOString();
|
|
32
77
|
session.lastActivityAt = session.updatedAt;
|
|
33
78
|
}
|
|
79
|
+
const snapshot = record => reviewSnapshot(record, SessionState);
|
|
80
|
+
const stagedRecords = records.map(cloneSnapshot), staged = records.map(snapshot);
|
|
81
|
+
const sameRecord = (original, record) => original === record
|
|
82
|
+
|| (assertCurrent && SessionState?.sameRunIdentity?.(original, record));
|
|
83
|
+
const stillStaged = record => records.some((original, index) =>
|
|
84
|
+
sameRecord(original, record) && snapshot(record) === staged[index]);
|
|
85
|
+
const sameView = () => getState().activeSessionId === capturedSessionId
|
|
86
|
+
&& (getState() === capturedState || (assertCurrent && SessionState?.sameRunIdentity));
|
|
34
87
|
try {
|
|
35
|
-
await persist({ preserveRunActionPayload: true, reviewRunIds: [runId] });
|
|
88
|
+
await persist({ preserveRunActionPayload: true, reviewRunIds: [runId], requireDurable: true });
|
|
89
|
+
assertCurrent?.();
|
|
90
|
+
const current = targets();
|
|
91
|
+
if (!sameView() || !current.length || current.some(record => !stillStaged(record))
|
|
92
|
+
|| records.some(original => !current.some(record => sameRecord(original, record))))
|
|
93
|
+
throw reviewPersistenceError();
|
|
36
94
|
} catch (error) {
|
|
95
|
+
try { assertCurrent?.(); } catch (_stale) { throw error; }
|
|
96
|
+
// The envelope still failed closed, but an exact run-scoped readback
|
|
97
|
+
// already acknowledges this outcome. Keep the error/notice, not a false retry.
|
|
98
|
+
if (error.code === 'shared_session_conflict' && error.durableReviewRunIds?.includes(runId)) throw error;
|
|
37
99
|
// The remote action may have succeeded. Keep its recovery data and expose uncertainty.
|
|
38
100
|
for (const record of targets()) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
101
|
+
if (!sameView() || !stillStaged(record)) continue;
|
|
102
|
+
const index = records.findIndex((original, index) => sameRecord(original, record) && snapshot(record) === staged[index]);
|
|
103
|
+
restoreReviewFields(record, previous, stagedRecords[index], legacyUndo);
|
|
43
104
|
}
|
|
44
105
|
throw error;
|
|
45
106
|
}
|
|
@@ -51,6 +112,28 @@
|
|
|
51
112
|
});
|
|
52
113
|
}
|
|
53
114
|
|
|
115
|
+
function verifyRecoveredRuns(recoveredRuns, proposed, written) {
|
|
116
|
+
for (var target of recoveredRuns || []) {
|
|
117
|
+
var expectedSession = proposed.find(session => session.id === target.sessionId);
|
|
118
|
+
var storedSession = (written || []).find(session => session.id === target.sessionId
|
|
119
|
+
&& session.projectId === expectedSession?.projectId
|
|
120
|
+
&& session.accountScopeId === expectedSession?.accountScopeId);
|
|
121
|
+
var expected = expectedSession?.runs?.find(run => run.id === target.runId);
|
|
122
|
+
var stored = storedSession?.runs?.find(run => run.id === target.runId);
|
|
123
|
+
var retained = expected && stored
|
|
124
|
+
&& expected.runProjectId === target.projectId && stored.runProjectId === target.projectId
|
|
125
|
+
&& Number(stored.nativeEventSeq || 0) >= Number(expected.nativeEventSeq || 0)
|
|
126
|
+
&& stored.codexThreadId === expected.codexThreadId
|
|
127
|
+
&& stored.codexTurnId === expected.codexTurnId
|
|
128
|
+
&& JSON.stringify(stored.interruptedDraft) === JSON.stringify(expected.interruptedDraft)
|
|
129
|
+
&& (expected.events || []).every(event => (stored.events || [])
|
|
130
|
+
.some(saved => JSON.stringify(saved) === JSON.stringify(event)));
|
|
131
|
+
if (!retained) throw Object.assign(new Error('Recovered run was not durably saved; its replay journal is retained.'), {
|
|
132
|
+
code: 'recovery_state_not_persisted'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
54
137
|
async function persistPanelState(input) {
|
|
55
138
|
var state = cloneSnapshot(input.state || {});
|
|
56
139
|
var compactState = cloneSnapshot(input.compactState || {});
|
|
@@ -71,6 +154,12 @@
|
|
|
71
154
|
code: 'stale_view'
|
|
72
155
|
});
|
|
73
156
|
}
|
|
157
|
+
if ((input.recoveryRuns || []).some(target => target.projectId !== projectId
|
|
158
|
+
|| target.accountScopeId !== accountScopeId)) {
|
|
159
|
+
throw Object.assign(new Error('Journal recovery scope changed before persistence.'), {
|
|
160
|
+
code: 'recovery_scope_changed'
|
|
161
|
+
});
|
|
162
|
+
}
|
|
74
163
|
var reviewRuns = (input.reviewRunIds || []).map(id =>
|
|
75
164
|
(state.sessions || []).flatMap(session => session.runs || []).find(run => run.id === id));
|
|
76
165
|
if (reviewRuns.some(run => !run)) throw reviewPersistenceError();
|
|
@@ -92,6 +181,8 @@
|
|
|
92
181
|
latestPrefs && latestPrefs.experimentalOtByProject || {},
|
|
93
182
|
{ [projectId]: input.normalizeExperimentalOtByProject(state.experimentalOtByProject)[projectId] === true }
|
|
94
183
|
);
|
|
184
|
+
prefs.reviewingAvailabilityByProject = Object.assign({}, latestPrefs?.reviewingAvailabilityByProject || {},
|
|
185
|
+
state.reviewingAvailabilityByProject || {});
|
|
95
186
|
prefs.governanceRulesByProject = Object.assign(
|
|
96
187
|
{},
|
|
97
188
|
latestPrefs && latestPrefs.governanceRulesByProject || {},
|
|
@@ -129,25 +220,48 @@
|
|
|
129
220
|
focusFiles: Array.isArray(session.focusFiles) ? session.focusFiles : [],
|
|
130
221
|
createdAt: session.createdAt,
|
|
131
222
|
updatedAt: session.updatedAt,
|
|
132
|
-
|
|
223
|
+
pendingInputs: (Array.isArray(session.pendingInputs) ? session.pendingInputs : [])
|
|
133
224
|
.filter(function (item) { return !queueTombstones[item?.id]; })
|
|
134
225
|
}), { preserveRunActionPayload: true });
|
|
135
226
|
});
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
227
|
+
sessionRecords = StorageDb.compactSessionBatchActions?.(sessionRecords, { activeSessionId: state.activeSessionId }) || sessionRecords;
|
|
228
|
+
var written;
|
|
229
|
+
try {
|
|
230
|
+
written = await input.SessionPersistence.writeSessions({
|
|
231
|
+
Migration: Migration,
|
|
232
|
+
StorageDb: StorageDb,
|
|
233
|
+
projectId: projectId,
|
|
234
|
+
accountScopeId: accountScopeId,
|
|
235
|
+
deletedSessionIds: deletedSessionIds,
|
|
236
|
+
sessionRecords: sessionRecords,
|
|
237
|
+
reviewRunIds: input.reviewRunIds,
|
|
238
|
+
queueClaims: persistenceMeta.queueClaims || {},
|
|
239
|
+
queueTombstones: queueTombstones,
|
|
240
|
+
writerId: input.persistenceContext?.writerId || ''
|
|
241
|
+
});
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (error.code === 'shared_session_conflict' && error.reviewReceipts && StorageDb.reviewStateSnapshot) {
|
|
244
|
+
error.durableReviewRunIds = (input.reviewRunIds || []).filter(id => sessionRecords.some(expected => {
|
|
245
|
+
const proposed = expected.runs?.find(run => run.id === id);
|
|
246
|
+
const saved = error.reviewReceipts.find(record => record.id === expected.id
|
|
247
|
+
&& record.accountScopeId === expected.accountScopeId && record.projectId === expected.projectId)
|
|
248
|
+
?.runs?.find(run => run.id === id);
|
|
249
|
+
return proposed && saved && StorageDb.reviewStateSnapshot(proposed) === StorageDb.reviewStateSnapshot(saved);
|
|
250
|
+
}));
|
|
251
|
+
delete error.reviewReceipts;
|
|
252
|
+
}
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
148
255
|
if (!reviewRuns.every(reviewRun => (written || []).some(session => (session.runs || []).some(run =>
|
|
149
|
-
run.id === reviewRun.id
|
|
256
|
+
run.id === reviewRun.id
|
|
257
|
+
&& (!reviewRun.trackedChangeStatus && ['applied', 'partial'].includes(reviewRun.undoStatus)
|
|
258
|
+
? run.undoStatus === reviewRun.undoStatus && (reviewRun.undoStatus === 'applied'
|
|
259
|
+
|| ['undoOperations', 'undoBaseFiles', 'undoExpectedFiles', 'undoTrackedChanges']
|
|
260
|
+
.every(field => JSON.stringify(run[field] || []) === JSON.stringify(reviewRun[field] || [])))
|
|
261
|
+
: run.trackedChangeStatus === reviewRun.trackedChangeStatus)
|
|
150
262
|
)))) throw reviewPersistenceError();
|
|
263
|
+
verifyRecoveredRuns(input.recoveryRuns, sessionRecords, written);
|
|
264
|
+
return written;
|
|
151
265
|
}
|
|
152
266
|
|
|
153
267
|
return Object.freeze({ persistPanelState: persistPanelState, applyReviewTransition: applyReviewTransition });
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
(function initCodexOverleafSensitivePreflight(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafSensitivePreflight = factory();
|
|
6
|
+
}
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function sensitivePreflightFactory() {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
// Report evidence admits detector identities only, never matched text or previews.
|
|
11
|
+
const REPORT_DETECTOR_IDS = new Set(['private-key', 'bearer-token', 'api-token', 'aws-access-key',
|
|
12
|
+
'google-api-key', 'huggingface-token', 'gitlab-token', 'stripe-live-secret', 'jwt-token', 'secret-assignment']);
|
|
13
|
+
|
|
14
|
+
function create({
|
|
15
|
+
SensitiveScan, normalizeGovernanceRules, isTextSnapshotFile, sendBackgroundNative,
|
|
16
|
+
getCurrentProjectId, appendRunEvent, appendCompletionReport, showPluginConfirm, tr, tx
|
|
17
|
+
}) {
|
|
18
|
+
async function runSensitivePreflight(options) {
|
|
19
|
+
options = options || {};
|
|
20
|
+
const { task, project, rules, useExistingMirror = false, guidance = false } = options;
|
|
21
|
+
const normalizedRules = normalizeGovernanceRules(rules);
|
|
22
|
+
if (normalizedRules.sensitiveCheckEnabled === false) {
|
|
23
|
+
return { blocked: false, findings: [] };
|
|
24
|
+
}
|
|
25
|
+
if (!SensitiveScan?.scanSensitiveInputs && !useExistingMirror) {
|
|
26
|
+
return { blocked: false, findings: [] };
|
|
27
|
+
}
|
|
28
|
+
let findings = SensitiveScan?.scanSensitiveInputs
|
|
29
|
+
? SensitiveScan.scanSensitiveInputs({
|
|
30
|
+
task,
|
|
31
|
+
files: guidance ? [] : (project?.files || []).filter(isTextSnapshotFile)
|
|
32
|
+
})
|
|
33
|
+
: [];
|
|
34
|
+
if (useExistingMirror && !guidance) {
|
|
35
|
+
findings = dedupeSensitiveFindings([
|
|
36
|
+
...findings,
|
|
37
|
+
...await scanNativeMirrorSensitiveFindings()
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
40
|
+
if (!findings.length) {
|
|
41
|
+
return { blocked: false, findings: [] };
|
|
42
|
+
}
|
|
43
|
+
options.assertCurrent?.();
|
|
44
|
+
appendRunEvent({
|
|
45
|
+
title: tx(
|
|
46
|
+
`Sensitive content check found ${findings.length} item(s).`,
|
|
47
|
+
`敏感内容检查发现 ${findings.length} 项。`
|
|
48
|
+
),
|
|
49
|
+
status: 'failed',
|
|
50
|
+
detail: findings.slice(0, 8).map(finding => ({
|
|
51
|
+
[tr('detailFile')]: finding.path || finding.source || 'task',
|
|
52
|
+
[tr('detailReason')]: finding.detectorId,
|
|
53
|
+
[tx('Preview', '预览')]: finding.preview || '[REDACTED]'
|
|
54
|
+
}))
|
|
55
|
+
});
|
|
56
|
+
if (normalizedRules.sensitiveConfirmAllowed === true) {
|
|
57
|
+
const approved = await showPluginConfirm({
|
|
58
|
+
title: tr('sensitiveConfirmTitle'),
|
|
59
|
+
message: formatSensitiveConfirmMessage(findings),
|
|
60
|
+
confirmLabel: guidance ? tx('Send guidance', '发送指导') : tr('sensitiveConfirmRun'),
|
|
61
|
+
cancelLabel: tr('confirmDefaultCancel'),
|
|
62
|
+
destructive: true
|
|
63
|
+
});
|
|
64
|
+
options.assertCurrent?.();
|
|
65
|
+
return { blocked: !approved, findings };
|
|
66
|
+
}
|
|
67
|
+
// Blocking an extra message must not finish the task already in progress.
|
|
68
|
+
if (guidance) return { blocked: true, findings };
|
|
69
|
+
const detectorIds = [...new Set(findings.map(finding => finding?.detectorId))];
|
|
70
|
+
appendCompletionReport({ conclusionKey: 'sensitive_blocked',
|
|
71
|
+
conclusion: tx(
|
|
72
|
+
'This run was blocked before Codex received project context because sensitive content was detected.',
|
|
73
|
+
'检测到敏感内容,本轮已在向 Codex 发送项目上下文前阻止。'
|
|
74
|
+
),
|
|
75
|
+
status: 'blocked',
|
|
76
|
+
unchangedReason: tx('Sensitive content was found before sending.', '发送前发现了敏感内容。'),
|
|
77
|
+
failure: { code: 'sensitive_content_blocked', stage: 'preflight', severity: 'blocked',
|
|
78
|
+
retryable: false, changedDocument: false,
|
|
79
|
+
evidence: {
|
|
80
|
+
// An unknown type makes the cause generic, even alongside one known type.
|
|
81
|
+
detectorIds: detectorIds.every(id => REPORT_DETECTOR_IDS.has(id)) ? detectorIds.slice(0, 5) : [],
|
|
82
|
+
findingCount: findings.length
|
|
83
|
+
} },
|
|
84
|
+
operations: [],
|
|
85
|
+
applyResults: [],
|
|
86
|
+
nextStep: tx(
|
|
87
|
+
'Remove the sensitive content, narrow @context, or enable explicit sensitive confirmation in Project Settings.',
|
|
88
|
+
'请移除敏感内容、缩小 @context,或在项目设置里允许敏感内容显式确认。'
|
|
89
|
+
)
|
|
90
|
+
});
|
|
91
|
+
return { blocked: true, findings };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function formatSensitiveConfirmMessage(findings = []) {
|
|
95
|
+
const visible = findings.slice(0, 8).map(finding => {
|
|
96
|
+
const path = finding.path || finding.source || 'task';
|
|
97
|
+
return `${path}: ${finding.detectorId || 'sensitive'} (${finding.preview || '[REDACTED]'})`;
|
|
98
|
+
});
|
|
99
|
+
return [
|
|
100
|
+
tr('sensitiveConfirmMessage'),
|
|
101
|
+
'',
|
|
102
|
+
...visible,
|
|
103
|
+
findings.length > visible.length ? tx(`${findings.length - visible.length} more finding(s).`, `另有 ${findings.length - visible.length} 项。`) : ''
|
|
104
|
+
].filter(Boolean).join('\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function scanNativeMirrorSensitiveFindings() {
|
|
108
|
+
try {
|
|
109
|
+
const response = await sendBackgroundNative({
|
|
110
|
+
method: 'mirror.scanSensitive',
|
|
111
|
+
params: { projectId: getCurrentProjectId() }
|
|
112
|
+
});
|
|
113
|
+
if (response?.ok) {
|
|
114
|
+
return Array.isArray(response.result?.findings) ? response.result.findings : [];
|
|
115
|
+
}
|
|
116
|
+
return [{
|
|
117
|
+
detectorId: 'mirror-sensitive-scan-unavailable',
|
|
118
|
+
source: 'local-mirror',
|
|
119
|
+
preview: response?.error?.message || 'Could not scan the local mirror before Codex runs.'
|
|
120
|
+
}];
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return [{
|
|
123
|
+
detectorId: 'mirror-sensitive-scan-unavailable',
|
|
124
|
+
source: 'local-mirror',
|
|
125
|
+
preview: error?.message || 'Could not scan the local mirror before Codex runs.'
|
|
126
|
+
}];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function dedupeSensitiveFindings(findings = []) {
|
|
131
|
+
const seen = new Set();
|
|
132
|
+
const deduped = [];
|
|
133
|
+
for (const finding of findings || []) {
|
|
134
|
+
const key = [
|
|
135
|
+
finding?.detectorId || '',
|
|
136
|
+
finding?.source || '',
|
|
137
|
+
finding?.path || '',
|
|
138
|
+
finding?.preview || ''
|
|
139
|
+
].join('\u0000');
|
|
140
|
+
if (seen.has(key)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
seen.add(key);
|
|
144
|
+
deduped.push(finding);
|
|
145
|
+
}
|
|
146
|
+
return deduped;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { run: runSensitivePreflight };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { create };
|
|
153
|
+
});
|
|
@@ -7,15 +7,32 @@
|
|
|
7
7
|
|
|
8
8
|
function create(deps = {}) {
|
|
9
9
|
const {
|
|
10
|
-
SessionState, getState, setState, sendBackgroundNative, saveState,
|
|
10
|
+
SessionState, getState, setState, getScope, sendBackgroundNative, saveState,
|
|
11
11
|
applyStateToPanel, showPluginToast, tr
|
|
12
12
|
} = deps;
|
|
13
13
|
|
|
14
14
|
async function forkRunFromNode(runId) {
|
|
15
15
|
const previousState = getState();
|
|
16
|
+
const skillPreferences = {
|
|
17
|
+
loadCodexLocalSkills: previousState.loadCodexLocalSkills !== false,
|
|
18
|
+
loadCodexOverleafSkills: previousState.loadCodexOverleafSkills !== false,
|
|
19
|
+
codexLocalSkillEnabled: { ...previousState.codexLocalSkillEnabled }
|
|
20
|
+
};
|
|
16
21
|
const source = findSource(previousState, runId);
|
|
17
22
|
if (!source) return failFork('session_fork_source_missing', tr('forkRunUnavailable'));
|
|
18
|
-
const
|
|
23
|
+
const scope = getScope ? { ...getScope() } : null;
|
|
24
|
+
const requestedActiveId = previousState.activeSessionId;
|
|
25
|
+
// The Native request and copied history must describe the same origin,
|
|
26
|
+
// even when the source session is edited while the request is pending.
|
|
27
|
+
const { session, run, runIndex } = JSON.parse(JSON.stringify(source));
|
|
28
|
+
const sameScope = () => {
|
|
29
|
+
const current = getScope?.();
|
|
30
|
+
return !scope || ['accountScopeId', 'projectId', 'generation'].every(key => current?.[key] === scope[key]);
|
|
31
|
+
};
|
|
32
|
+
const currentSource = () => {
|
|
33
|
+
const current = findSource(getState(), runId);
|
|
34
|
+
return current?.session.id === session.id && current.run.codexTurnId === run.codexTurnId;
|
|
35
|
+
};
|
|
19
36
|
const draftThreadId = run.codexTurnId && run.interruptedDraft?.turnId === run.codexTurnId
|
|
20
37
|
? run.interruptedDraft.threadId : '';
|
|
21
38
|
// Provider switches replace the session thread, not the selected node's origin.
|
|
@@ -25,25 +42,39 @@
|
|
|
25
42
|
}
|
|
26
43
|
let response;
|
|
27
44
|
try {
|
|
45
|
+
const resolveLocalSkills = deps.captureLocalSkillIdsForFork || deps.getEnabledCodexLocalSkillIds;
|
|
46
|
+
const enabledCodexLocalSkillIds = skillPreferences.loadCodexLocalSkills
|
|
47
|
+
? captureLocalSkillIds(await resolveLocalSkills?.(skillPreferences)) : [];
|
|
48
|
+
if (!sameScope() || !currentSource()) return null;
|
|
49
|
+
if (resolveLocalSkills && !Array.isArray(enabledCodexLocalSkillIds)) {
|
|
50
|
+
throw codedError('codex_local_skill_unavailable', tr('forkRunFailed'));
|
|
51
|
+
}
|
|
28
52
|
response = await sendBackgroundNative({
|
|
29
53
|
method: 'codex.thread.fork',
|
|
30
54
|
params: {
|
|
31
55
|
threadId: sourceThreadId,
|
|
32
56
|
lastTurnId: run.codexTurnId,
|
|
33
57
|
speedTier: session.speedTier,
|
|
34
|
-
loadCodexLocalSkills:
|
|
35
|
-
loadCodexOverleafSkills:
|
|
58
|
+
loadCodexLocalSkills: skillPreferences.loadCodexLocalSkills,
|
|
59
|
+
loadCodexOverleafSkills: skillPreferences.loadCodexOverleafSkills,
|
|
60
|
+
enabledCodexLocalSkillIds
|
|
36
61
|
}
|
|
37
62
|
});
|
|
38
63
|
} catch (error) {
|
|
64
|
+
if (!sameScope() || !currentSource()) return null;
|
|
39
65
|
return failFork(error?.code || 'session_fork_failed', error?.message || tr('forkRunFailed'));
|
|
40
66
|
}
|
|
67
|
+
if (!sameScope() || !currentSource()) {
|
|
68
|
+
if (response?.ok && response.result?.threadId) await clearFork(response.result.threadId);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
41
71
|
if (!response?.ok || !response.result?.threadId) {
|
|
42
72
|
return failFork(response?.error?.code || 'session_fork_failed', response?.error?.message || tr('forkRunFailed'));
|
|
43
73
|
}
|
|
44
74
|
const copiedRuns = session.runs.slice(0, runIndex + 1).map(copyRunSnapshot);
|
|
45
75
|
const titleBase = String(session.title || copiedRuns[0]?.task || tr('newSessionFallback')).trim();
|
|
46
|
-
const
|
|
76
|
+
const mergeBase = getState();
|
|
77
|
+
const forkTitle = buildUniqueForkTitle(mergeBase.sessions, titleBase);
|
|
47
78
|
const forked = SessionState.createSession({
|
|
48
79
|
title: forkTitle,
|
|
49
80
|
titleSource: 'manual',
|
|
@@ -62,27 +93,55 @@
|
|
|
62
93
|
forkedFromSessionId: session.id,
|
|
63
94
|
forkedFromRunId: run.id
|
|
64
95
|
});
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
})
|
|
96
|
+
const activeId = mergeBase.activeSessionId === requestedActiveId
|
|
97
|
+
? forked.id : mergeBase.activeSessionId;
|
|
98
|
+
const installed = SessionState.setActiveSession({
|
|
99
|
+
...mergeBase, sessions: [...(mergeBase.sessions || []), forked]
|
|
100
|
+
}, activeId);
|
|
101
|
+
const installedSnapshot = JSON.stringify(installed);
|
|
102
|
+
const forkSnapshot = JSON.stringify(forked);
|
|
103
|
+
setState(installed);
|
|
70
104
|
try {
|
|
105
|
+
// Keep the visible composer and active-session owner aligned during save.
|
|
106
|
+
applyStateToPanel();
|
|
71
107
|
await saveState();
|
|
72
108
|
} catch (error) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
109
|
+
if (sameScope()) {
|
|
110
|
+
const current = getState();
|
|
111
|
+
const currentFork = current.sessions?.find(item => item.id === forked.id);
|
|
112
|
+
// Never erase work entered into the fork after its optimistic insert.
|
|
113
|
+
if (!currentFork || JSON.stringify(currentFork) === forkSnapshot) {
|
|
114
|
+
if (currentFork) {
|
|
115
|
+
if (current === installed && JSON.stringify(current) === installedSnapshot) {
|
|
116
|
+
setState(mergeBase);
|
|
117
|
+
} else {
|
|
118
|
+
const remaining = current.sessions.filter(item => item.id !== forked.id);
|
|
119
|
+
const fallbackId = remaining.some(item => item.id === mergeBase.activeSessionId)
|
|
120
|
+
? mergeBase.activeSessionId : remaining[remaining.length - 1]?.id;
|
|
121
|
+
setState(SessionState.setActiveSession({ ...current, sessions: remaining },
|
|
122
|
+
current.activeSessionId === forked.id ? fallbackId : current.activeSessionId));
|
|
123
|
+
}
|
|
124
|
+
try { applyStateToPanel(); } catch (_renderError) { /* Preserve the original failure and finish cleanup. */ }
|
|
125
|
+
}
|
|
126
|
+
await clearFork(response.result.threadId, forked.id);
|
|
127
|
+
}
|
|
128
|
+
if (sameScope()) showPluginToast(error.message || tr('forkRunFailed'), { status: 'failed', sticky: true });
|
|
129
|
+
}
|
|
79
130
|
throw error;
|
|
80
131
|
}
|
|
81
|
-
|
|
132
|
+
// Persistence owns its captured scope. A late result cannot render a new
|
|
133
|
+
// view or delete a thread that may already have been saved in the old one.
|
|
134
|
+
if (!sameScope() || !getState().sessions?.some(item => item.id === forked.id)) return null;
|
|
82
135
|
showPluginToast(tr('forkRunDone'), { status: 'completed' });
|
|
83
136
|
return forked;
|
|
84
137
|
}
|
|
85
138
|
|
|
139
|
+
async function clearFork(threadId, sessionId) {
|
|
140
|
+
try {
|
|
141
|
+
await sendBackgroundNative({ method: 'codex.history.clearPlugin', params: { sessionId, threadId } });
|
|
142
|
+
} catch (_error) { /* Best-effort cleanup must not restore a stale view. */ }
|
|
143
|
+
}
|
|
144
|
+
|
|
86
145
|
function failFork(code, message) {
|
|
87
146
|
showPluginToast(message, { status: 'failed', sticky: true });
|
|
88
147
|
throw codedError(code, message);
|
|
@@ -91,6 +150,10 @@
|
|
|
91
150
|
return { forkRunFromNode };
|
|
92
151
|
}
|
|
93
152
|
|
|
153
|
+
function captureLocalSkillIds(value) {
|
|
154
|
+
return Array.isArray(value) ? [...value] : undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
94
157
|
function buildUniqueForkTitle(sessions = [], sourceTitle = '') {
|
|
95
158
|
const fallback = String(sourceTitle || 'New Session').trim() || 'New Session';
|
|
96
159
|
const baseTitle = fallback
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
const {
|
|
12
12
|
SessionPanel,
|
|
13
13
|
SessionState,
|
|
14
|
-
SessionMenuView
|
|
14
|
+
SessionMenuView,
|
|
15
|
+
PanelIcons
|
|
15
16
|
} = deps;
|
|
16
17
|
const {
|
|
17
18
|
createSession,
|
|
@@ -30,6 +31,9 @@
|
|
|
30
31
|
saveState,
|
|
31
32
|
saveStateSoon,
|
|
32
33
|
readPanelInputs,
|
|
34
|
+
flushComposerDraft,
|
|
35
|
+
setSessionTransition,
|
|
36
|
+
getDraftScope,
|
|
33
37
|
applyStateToPanel,
|
|
34
38
|
syncSessionProvider,
|
|
35
39
|
getPanel,
|
|
@@ -47,6 +51,7 @@
|
|
|
47
51
|
getActiveSession,
|
|
48
52
|
getPanel,
|
|
49
53
|
getSessionDisplayTitle,
|
|
54
|
+
icons: PanelIcons,
|
|
50
55
|
getState,
|
|
51
56
|
isDisplayableSession,
|
|
52
57
|
isSessionRunning,
|
|
@@ -60,7 +65,31 @@
|
|
|
60
65
|
closeSessionMenu
|
|
61
66
|
} = sessionMenuView;
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
let transitionToken = null;
|
|
69
|
+
|
|
70
|
+
async function withSessionTransition(change) {
|
|
71
|
+
if (transitionToken) return;
|
|
72
|
+
const token = {};
|
|
73
|
+
transitionToken = token;
|
|
74
|
+
const scope = getDraftScope?.();
|
|
75
|
+
try {
|
|
76
|
+
// Lock before the first wait, including an outstanding IME commit.
|
|
77
|
+
await setSessionTransition?.(true);
|
|
78
|
+
if (getDraftScope?.() !== scope) return;
|
|
79
|
+
return await change(scope);
|
|
80
|
+
} finally {
|
|
81
|
+
if (transitionToken === token) {
|
|
82
|
+
transitionToken = null;
|
|
83
|
+
setSessionTransition?.(false);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function startNewSession() {
|
|
89
|
+
return withSessionTransition(createNewSession);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function createNewSession(draftScope) {
|
|
64
93
|
// Leaving a still-running session is allowed but never silent: the run
|
|
65
94
|
// keeps going in the background and lands in its own session.
|
|
66
95
|
const runningActive = getActiveSession(getState());
|
|
@@ -74,7 +103,10 @@
|
|
|
74
103
|
return;
|
|
75
104
|
}
|
|
76
105
|
}
|
|
106
|
+
if (getDraftScope?.() !== draftScope) return;
|
|
77
107
|
readPanelInputs();
|
|
108
|
+
await flushComposerDraft?.();
|
|
109
|
+
if (getDraftScope?.() !== draftScope) return;
|
|
78
110
|
// Reuse an already-empty, idle active session instead of minting a ghost.
|
|
79
111
|
// Empty sessions are invisible in the list yet still consume the storage cap
|
|
80
112
|
// (slice(-20) / maxSessions), so spamming "New Session" could silently evict
|
|
@@ -99,6 +131,7 @@
|
|
|
99
131
|
if (!evictionApproved) {
|
|
100
132
|
return;
|
|
101
133
|
}
|
|
134
|
+
if (getDraftScope?.() !== draftScope) return;
|
|
102
135
|
}
|
|
103
136
|
const session = createSession({
|
|
104
137
|
mode: getState().mode,
|
|
@@ -114,15 +147,22 @@
|
|
|
114
147
|
activeSessionId: session.id
|
|
115
148
|
}));
|
|
116
149
|
applyStateToPanel();
|
|
150
|
+
const destinationScope = getDraftScope?.();
|
|
117
151
|
await syncSessionProvider?.();
|
|
152
|
+
if (getDraftScope?.() !== destinationScope) return;
|
|
118
153
|
await saveState();
|
|
154
|
+
if (getDraftScope?.() !== destinationScope) return;
|
|
119
155
|
getPanel().querySelector('[data-task]')?.focus();
|
|
120
156
|
}
|
|
121
157
|
|
|
122
|
-
|
|
158
|
+
function switchSession(sessionId) {
|
|
123
159
|
if (sessionId === getState().activeSessionId) {
|
|
124
160
|
return;
|
|
125
161
|
}
|
|
162
|
+
return withSessionTransition(scope => switchToSession(sessionId, scope));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function switchToSession(sessionId, draftScope) {
|
|
126
166
|
const runningActive = getActiveSession(getState());
|
|
127
167
|
if (runningActive && isSessionRunning(runningActive)) {
|
|
128
168
|
const approved = await showPluginConfirm({
|
|
@@ -134,10 +174,15 @@
|
|
|
134
174
|
return;
|
|
135
175
|
}
|
|
136
176
|
}
|
|
177
|
+
if (getDraftScope?.() !== draftScope) return;
|
|
137
178
|
readPanelInputs();
|
|
179
|
+
await flushComposerDraft?.();
|
|
180
|
+
if (getDraftScope?.() !== draftScope) return;
|
|
138
181
|
setState(setActiveSession(getState(), sessionId));
|
|
139
182
|
applyStateToPanel();
|
|
183
|
+
const destinationScope = getDraftScope?.();
|
|
140
184
|
await syncSessionProvider?.();
|
|
185
|
+
if (getDraftScope?.() !== destinationScope) return;
|
|
141
186
|
await saveState();
|
|
142
187
|
}
|
|
143
188
|
|
|
@@ -201,7 +246,7 @@
|
|
|
201
246
|
}
|
|
202
247
|
});
|
|
203
248
|
if (!response?.ok) {
|
|
204
|
-
showPluginToast(tr('deleteSessionHistoryFailedToast', { message: response?.error?.message || '
|
|
249
|
+
showPluginToast(tr('deleteSessionHistoryFailedToast', { message: response?.error?.message || tr('deleteSessionNativeFailureDetail') }), { status: 'warning', sticky: true });
|
|
205
250
|
} else if (response.result?.skipped) {
|
|
206
251
|
showPluginToast(tr('deleteSessionNoThreadToast'), { status: 'info' });
|
|
207
252
|
} else {
|
|
@@ -295,6 +340,7 @@
|
|
|
295
340
|
bindActiveSessionHeader,
|
|
296
341
|
startNewSession,
|
|
297
342
|
switchSession,
|
|
343
|
+
isTransitioning: () => transitionToken !== null,
|
|
298
344
|
deleteSessionWithConfirm,
|
|
299
345
|
renderSessionList,
|
|
300
346
|
commitSessionRename,
|