codex-overleaf-link 2.5.1 → 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 +149 -398
- package/README.zh-CN.md +153 -404
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +78 -3
- package/extension/src/backgroundSharedSessions.js +55 -2
- package/extension/src/backgroundUpdateCoordinator.js +128 -12
- package/extension/src/content/activeTurnControl.js +164 -7
- package/extension/src/content/composerAttachments.js +11 -2
- package/extension/src/content/composerPanel.js +92 -12
- package/extension/src/content/contentRuntime.js +920 -276
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsPanel.js +18 -0
- package/extension/src/content/generated/content.bundle.js +4496 -924
- package/extension/src/content/generated/content.bundle.meta.json +6 -3
- package/extension/src/content/legacyGlobalRegistry.js +4 -0
- package/extension/src/content/localSkillsPanel.js +2 -2
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +42 -5
- package/extension/src/content/modelPicker.js +1 -0
- package/extension/src/content/nativeCompatibilityController.js +29 -10
- package/extension/src/content/otWarmMirror.js +1 -1
- package/extension/src/content/pageBridgeClient.js +2 -2
- package/extension/src/content/panelMaintenance.js +6 -2
- package/extension/src/content/panelRenderer.js +51 -27
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectSettingsCoordinator.js +30 -6
- package/extension/src/content/providerSettingsDialog.js +2 -1
- package/extension/src/content/recentProjects.js +4 -3
- package/extension/src/content/runActivitySummary.js +122 -14
- package/extension/src/content/runCompletionMeta.js +194 -6
- package/extension/src/content/runController.js +2 -1
- package/extension/src/content/runFailureNotice.js +8 -1
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +9 -5
- package/extension/src/content/runRecoveryActions.js +2 -1
- package/extension/src/content/runResultActions.js +22 -20
- package/extension/src/content/runReviewController.js +94 -35
- package/extension/src/content/runTimelineView.js +224 -25
- package/extension/src/content/scopedPersistencePanelState.js +92 -23
- package/extension/src/content/sensitivePreflight.js +14 -1
- package/extension/src/content/sessionManager.js +49 -3
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +15 -2
- package/extension/src/content/settingsWorkbench.js +127 -89
- package/extension/src/content/sharedSessionViewSync.js +4 -3
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateNotice.js +60 -6
- package/extension/src/content/writebackOrchestrator.js +35 -22
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/overleafCapabilities.js +4 -1
- package/extension/src/page/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +9 -1
- package/extension/src/page/writebackRouter.js +24 -77
- package/extension/src/pageBridge.js +107 -66
- package/extension/src/popup.js +1 -1
- package/extension/src/shared/agentTranscript.js +350 -17
- package/extension/src/shared/auditRecords.js +2 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/compileAdapter.js +58 -17
- package/extension/src/shared/composerAttachmentDraft.js +6 -1
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/failureReasons.js +42 -3
- package/extension/src/shared/i18n.js +299 -29
- package/extension/src/shared/lineReferences.js +38 -31
- package/extension/src/shared/pageRpcContract.js +1 -1
- package/extension/src/shared/panelIcons.js +39 -0
- 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/sessionState.js +63 -4
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/sharedSessionBridge.js +81 -13
- package/extension/src/shared/staleGuard.js +72 -63
- package/extension/src/shared/storageDb.js +35 -3
- package/extension/src/shared/storageMigration.js +7 -1
- package/extension/src/shared/storageRunActions.js +62 -8
- package/extension/src/shared/undoOperations.js +126 -28
- package/extension/src/shared/updateRuntimeIdentity.js +16 -2
- package/extension/src/shared/writebackSettlement.js +21 -3
- package/extension/styles/panel.css +1106 -490
- package/native-host/src/bootstrapUpdate.js +52 -16
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +10 -1
- package/native-host/src/chatCompletionsBridge.js +29 -2
- package/native-host/src/codexPromptAssembly.js +86 -118
- package/native-host/src/codexSessionRunner.js +126 -17
- package/native-host/src/providerContextError.js +20 -0
- package/native-host/src/taskRunnerRuntime.js +18 -6
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/updateManager.js +120 -15
- package/package.json +5 -8
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
const RETRYABLE_FAILURE_CODES = new Set([
|
|
24
24
|
'native_request_failed', 'codex_timeout', 'codex_no_usable_result',
|
|
25
|
-
'codex_output_limit', 'stale_source_changed', 'patch_anchor_not_found',
|
|
25
|
+
'codex_output_limit', 'codex_context_limit', 'stale_source_changed', 'patch_anchor_not_found',
|
|
26
26
|
'target_editor_not_ready', 'write_timeout', 'partial_write_needs_review',
|
|
27
27
|
'write_operation_failed'
|
|
28
28
|
]);
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
const retry = buildRecoveryButton(actionKey,
|
|
57
57
|
tx('Edit & resend', '编辑后重发'),
|
|
58
58
|
tx('Put this run\u2019s task back into the composer so you can adjust and resend it.', '把本轮任务填回输入框,修改后可直接重发。'));
|
|
59
|
+
retry.dataset.recoveryAction = 'edit-resend';
|
|
59
60
|
retry.addEventListener('click', clickEvent => {
|
|
60
61
|
clickEvent.stopPropagation();
|
|
61
62
|
refillComposerForRetry(run || null);
|
|
@@ -4,13 +4,12 @@
|
|
|
4
4
|
})(typeof window !== 'undefined' ? window : globalThis, function (RunCompletionMeta) {
|
|
5
5
|
'use strict';
|
|
6
6
|
|
|
7
|
-
const { splitFlatCompletionReport, projectCompletionBody, projectCompletionMeta, summarizeCompletionMeta } = RunCompletionMeta;
|
|
7
|
+
const { splitFlatCompletionReport, projectNotStartedRun, projectFinishedRun, projectCompletionConclusion, projectCompletionBody, projectCompletionMeta, summarizeCompletionMeta } = RunCompletionMeta;
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
function create(deps = {}) {
|
|
11
11
|
const {
|
|
12
12
|
tr,
|
|
13
|
-
getLocale,
|
|
14
13
|
forkRunFromNode,
|
|
15
14
|
document: documentRef = globalThis.document,
|
|
16
15
|
navigator: navigatorRef = globalThis.navigator
|
|
@@ -21,11 +20,27 @@
|
|
|
21
20
|
const reports = root.querySelectorAll('.run-completion-report');
|
|
22
21
|
const report = reports[reports.length - 1];
|
|
23
22
|
if (!actions || !report) return;
|
|
24
|
-
|
|
23
|
+
// Share one row with the facts summary when it exists; the expandable
|
|
24
|
+
// details list stays below both.
|
|
25
|
+
const factsToggle = report.querySelector(':scope > .run-final-answer__facts > .run-final-answer__facts-sum');
|
|
26
|
+
if (factsToggle) factsToggle.after(actions);
|
|
27
|
+
else {
|
|
28
|
+
// Without a facts summary, the report's own Details toggle shares the row.
|
|
29
|
+
const details = report.querySelector(':scope > .run-outcome-details, :scope > .run-outcome-footer > .run-outcome-details');
|
|
30
|
+
if (details) {
|
|
31
|
+
let footer = details.parentElement.matches('.run-outcome-footer') ? details.parentElement : null;
|
|
32
|
+
if (!footer) {
|
|
33
|
+
footer = documentRef.createElement('div');
|
|
34
|
+
footer.className = 'run-outcome-footer';
|
|
35
|
+
details.before(footer);
|
|
36
|
+
footer.append(details);
|
|
37
|
+
}
|
|
38
|
+
footer.append(actions);
|
|
39
|
+
} else report.append(actions);
|
|
40
|
+
}
|
|
25
41
|
actions.hidden = false;
|
|
26
42
|
configureCopyButton(root);
|
|
27
43
|
configureForkButton(root, run);
|
|
28
|
-
configureCompletedTime(actions, run);
|
|
29
44
|
}
|
|
30
45
|
|
|
31
46
|
function configureForkButton(root, run) {
|
|
@@ -40,7 +55,7 @@
|
|
|
40
55
|
button.disabled = !available;
|
|
41
56
|
button.title = tr(available ? 'forkRunTitle' : 'forkRunUnavailable');
|
|
42
57
|
button.setAttribute('aria-label', button.title);
|
|
43
|
-
button.innerHTML = '<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M3 10h3.5c3.5 0 4.2-4 7.5-4h2m-2.5-2.5L16 6l-2.5 2.5M6.5 10c3.5 0 4.2 4 7.5 4h2m-2.5-2.5L16 14l-2.5 2.5"/></svg>';
|
|
58
|
+
button.innerHTML = '<svg viewBox="0 0 20 20" aria-hidden="true"><path transform="translate(.5 0)" d="M3 10h3.5c3.5 0 4.2-4 7.5-4h2m-2.5-2.5L16 6l-2.5 2.5M6.5 10c3.5 0 4.2 4 7.5 4h2m-2.5-2.5L16 14l-2.5 2.5"/></svg>';
|
|
44
59
|
if (!available) return;
|
|
45
60
|
button.addEventListener('click', async event => {
|
|
46
61
|
event.stopPropagation();
|
|
@@ -55,19 +70,6 @@
|
|
|
55
70
|
});
|
|
56
71
|
}
|
|
57
72
|
|
|
58
|
-
function configureCompletedTime(actions, run) {
|
|
59
|
-
const target = actions.querySelector('[data-run-completed-time]');
|
|
60
|
-
const finishedAt = String(run.finishedAt || '').trim();
|
|
61
|
-
const date = finishedAt ? new Date(finishedAt) : null;
|
|
62
|
-
if (!target || !date || !Number.isFinite(date.getTime())) return;
|
|
63
|
-
target.hidden = false;
|
|
64
|
-
target.textContent = new Intl.DateTimeFormat(getLocale(), {
|
|
65
|
-
hour: '2-digit',
|
|
66
|
-
minute: '2-digit'
|
|
67
|
-
}).format(date);
|
|
68
|
-
target.title = date.toLocaleString(getLocale());
|
|
69
|
-
}
|
|
70
|
-
|
|
71
73
|
function configureCopyButton(root) {
|
|
72
74
|
const existing = root.querySelector('[data-run-copy]');
|
|
73
75
|
const button = existing.cloneNode(true);
|
|
@@ -78,7 +80,7 @@
|
|
|
78
80
|
button.disabled = !copyText;
|
|
79
81
|
button.title = tr('copyConclusionTitle');
|
|
80
82
|
button.setAttribute('aria-label', button.title);
|
|
81
|
-
button.innerHTML = '<svg viewBox="0 0 20 20" aria-hidden="true"><rect x="6.5" y="6.5" width="9" height="9" rx="2"/><path d="M13.5 6.5V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v6.5a2 2 0 0 0 2 2h1.5"/></svg>';
|
|
83
|
+
button.innerHTML = '<svg viewBox="0 0 20 20" aria-hidden="true"><g transform="translate(.75 .75)"><rect x="6.5" y="6.5" width="9" height="9" rx="2"/><path d="M13.5 6.5V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v6.5a2 2 0 0 0 2 2h1.5"/></g></svg>';
|
|
82
84
|
if (!copyText) return;
|
|
83
85
|
button.addEventListener('click', async event => {
|
|
84
86
|
event.stopPropagation();
|
|
@@ -109,7 +111,7 @@
|
|
|
109
111
|
if (!copied) throw new Error('clipboard_copy_failed');
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
return Object.freeze({ configureResultActions, projectUndoAvailability, splitFlatCompletionReport, projectCompletionBody, projectCompletionMeta, summarizeCompletionMeta });
|
|
114
|
+
return Object.freeze({ configureResultActions, projectUndoAvailability, splitFlatCompletionReport, projectNotStartedRun, projectFinishedRun, projectCompletionConclusion, projectCompletionBody, projectCompletionMeta, summarizeCompletionMeta });
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
|
|
@@ -58,9 +58,12 @@
|
|
|
58
58
|
if (Modules.StorageDb?.sharedSessionsEnabled?.() !== true) return;
|
|
59
59
|
findRunRecord(runId).undoOwnerProtocol = 1;
|
|
60
60
|
try { await flushQueuedSaveState({ requireDurable: true, reviewRunIds: [runId] }); }
|
|
61
|
-
catch (cause) {
|
|
61
|
+
catch (cause) {
|
|
62
|
+
if (cause.code === 'shared_session_conflict' && cause.durableReviewRunIds?.includes(runId)) notify?.(cause.message);
|
|
63
|
+
else throw Object.assign(new Error('The Undo owner marker could not be durably saved.'), { code: 'review_state_not_persisted', cause });
|
|
64
|
+
}
|
|
62
65
|
reviewOwners.get(runId)?.();
|
|
63
|
-
const saved = await Modules.StorageDb.getRecord('sessions', getState().activeSessionId);
|
|
66
|
+
const saved = await Modules.StorageDb.getRecord('sessions', getState().activeSessionId, { adopt: false });
|
|
64
67
|
if (saved?.runs?.find(run => run.id === runId)?.undoOwnerProtocol !== 1)
|
|
65
68
|
throw Object.assign(new Error('The Undo owner marker was not durably retained.'), { code: 'review_state_not_persisted' });
|
|
66
69
|
reviewOwners.get(runId)?.();
|
|
@@ -68,7 +71,7 @@
|
|
|
68
71
|
async function withSharedReviewOwnership(runId, work) {
|
|
69
72
|
const db = Modules.StorageDb;
|
|
70
73
|
if (db?.sharedSessionsEnabled?.() !== true) return work();
|
|
71
|
-
|
|
74
|
+
let run = findRunRecord(runId);
|
|
72
75
|
if (!run || trackedChangeInFlight.has(runId)) return;
|
|
73
76
|
const assertCurrent = undoOwner(run);
|
|
74
77
|
const sessionId = getState().activeSessionId;
|
|
@@ -77,8 +80,9 @@
|
|
|
77
80
|
reviewOwners.set(runId, assertOwner);
|
|
78
81
|
try {
|
|
79
82
|
assertCurrent();
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
// Only the run is refreshed here; keep the visible session's merge baseline.
|
|
84
|
+
const session = await db.getRecord('sessions', sessionId, { adopt: false });
|
|
85
|
+
run = assertCurrent();
|
|
82
86
|
const latest = session?.runs?.find(record => record.id === runId);
|
|
83
87
|
if (!latest || session.projectId !== getRunProjectIdForWriteback(run) || session.accountScopeId !== getAccountScopeId())
|
|
84
88
|
throw Object.assign(new Error('The shared Undo run is no longer available in its original scope.'), { code: 'review_context_changed' });
|
|
@@ -162,9 +166,10 @@
|
|
|
162
166
|
trackedChangeInFlight.set(runId, 'reject'); refreshRunCardControls(runId);
|
|
163
167
|
try { return await work(); }
|
|
164
168
|
catch (error) {
|
|
165
|
-
let current
|
|
166
|
-
|
|
167
|
-
|
|
169
|
+
let current; try { current = assertOwner(); } catch (_stale) {}
|
|
170
|
+
const durableReviewConflict = error.code === 'shared_session_conflict' && error.durableReviewRunIds?.includes(runId);
|
|
171
|
+
if (current && error.code !== 'review_context_changed' && !durableReviewConflict) {
|
|
172
|
+
if (current.undoStatus === 'running') setRunUndoStatus(runId, 'partial');
|
|
168
173
|
appendRunRecordEvent(runId, { title: tr('undoNoTraceTitle'), status: 'failed',
|
|
169
174
|
detail: { [tr('detailReason')]: error.message } });
|
|
170
175
|
}
|
|
@@ -174,18 +179,59 @@
|
|
|
174
179
|
} finally { trackedChangeInFlight.delete(runId); refreshRunCardControls(runId); }
|
|
175
180
|
}
|
|
176
181
|
const withUndoLock = (runId, work) => withSharedReviewOwnership(runId, () => withLocalUndoLock(runId, work));
|
|
177
|
-
|
|
182
|
+
// Undo restores the source; with Auto compile on, refresh the PDF the way a write does.
|
|
183
|
+
async function recompileAfterUndo(runId, work) {
|
|
184
|
+
const before = findRunRecord(runId);
|
|
185
|
+
const prior = `${before?.undoStatus || ''}|${before?.trackedChangeStatus || ''}`;
|
|
186
|
+
const result = await work();
|
|
187
|
+
const run = findRunRecord(runId);
|
|
188
|
+
const undone = ['applied', 'partial'].includes(run?.undoStatus) || run?.trackedChangeStatus === 'rejected';
|
|
189
|
+
if (undone && `${run.undoStatus || ''}|${run.trackedChangeStatus || ''}` !== prior
|
|
190
|
+
&& getState()?.autoRecompile !== false && run.runProjectId && run.runProjectId === getCurrentProjectId?.()) {
|
|
191
|
+
Promise.resolve(callPageBridge('triggerCompile', { preferUiClick: true, waitForSaveMs: 5000,
|
|
192
|
+
runProjectId: run.runProjectId })).catch(() => {});
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
// Inside the lock: a stale page that adopts another page's finished Undo never reaches its work.
|
|
197
|
+
const undoRun = runId => withUndoLock(runId, () => recompileAfterUndo(runId, () => executeUndo(runId)));
|
|
178
198
|
const acceptRun = runId => withSharedReviewOwnership(runId, () => executeAcceptRun(runId));
|
|
179
|
-
const undoRunTrackedChanges = (runId, run) => withUndoLock(runId,
|
|
199
|
+
const undoRunTrackedChanges = (runId, run) => withUndoLock(runId,
|
|
200
|
+
() => recompileAfterUndo(runId, () => executeTrackedUndo(runId, run)));
|
|
180
201
|
|
|
181
202
|
function undoOwner(run) {
|
|
182
|
-
const
|
|
203
|
+
const sessionId = getState().activeSessionId;
|
|
183
204
|
const scope = [getCurrentProjectId?.(), getAccountScopeId?.(), getRouteGeneration?.()];
|
|
205
|
+
const provenance = value => JSON.stringify([value?.id, value?.runProjectId || '', value?.startedAt || '',
|
|
206
|
+
value?.runAccountScopeId || scope[1] || '', value?.nativeEventSeq || 0, value?.codexThreadId || '', value?.codexTurnId || '', value?.executionSnapshot || null]);
|
|
207
|
+
const sessionScope = () => {
|
|
208
|
+
const session = getState().sessions?.find(item => item.id === sessionId);
|
|
209
|
+
return JSON.stringify([session?.projectId || '', session?.accountScopeId || '']);
|
|
210
|
+
};
|
|
211
|
+
const source = provenance(run), owningSession = sessionScope();
|
|
184
212
|
return () => {
|
|
185
213
|
reviewOwners.get(run.id)?.();
|
|
186
|
-
|
|
214
|
+
const live = findRunRecord(run.id);
|
|
215
|
+
if (getState().activeSessionId !== sessionId || sessionScope() !== owningSession || !live
|
|
216
|
+
|| !(live === run || Modules.SessionState?.sameRunIdentity?.(run, live)) || provenance(live) !== source
|
|
187
217
|
|| JSON.stringify(scope) !== JSON.stringify([getCurrentProjectId?.(), getAccountScopeId?.(), getRouteGeneration?.()]))
|
|
188
218
|
throw Object.assign(new Error('The Undo project, account or session changed.'), { code: 'review_context_changed' });
|
|
219
|
+
return live;
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function checkpointGuard(run, assertOwner) {
|
|
224
|
+
const snapshot = value => {
|
|
225
|
+
const normalized = Modules.SessionState?.normalizeRuns?.([value])?.[0] || value;
|
|
226
|
+
return JSON.stringify(['appliedOperations', 'undoOperations', 'undoBaseFiles', 'undoExpectedFiles',
|
|
227
|
+
'undoTrackedChanges', 'trackedChangeCaptures', 'undoStatus', 'trackedChangeStatus', 'forkSnapshot']
|
|
228
|
+
.map(key => normalized[key]));
|
|
229
|
+
};
|
|
230
|
+
const expected = snapshot(run);
|
|
231
|
+
return () => {
|
|
232
|
+
const live = assertOwner();
|
|
233
|
+
if (snapshot(live) !== expected) throw Object.assign(new Error('The Undo project, account or session changed.'), { code: 'review_context_changed' });
|
|
234
|
+
return live;
|
|
189
235
|
};
|
|
190
236
|
}
|
|
191
237
|
|
|
@@ -198,9 +244,10 @@ function undoOwner(run) {
|
|
|
198
244
|
}
|
|
199
245
|
|
|
200
246
|
async function executeUndo(runId) {
|
|
201
|
-
|
|
247
|
+
let run = findRunRecord(runId);
|
|
202
248
|
if (run) Object.assign(run, Modules.UndoOperations.prepareRunUndo(run));
|
|
203
249
|
const assertCurrent = undoOwner(run);
|
|
250
|
+
let assertInputs = checkpointGuard(run, assertCurrent);
|
|
204
251
|
if (!getRunUndoCount(run) || run.undoStatus === 'applied') {
|
|
205
252
|
return;
|
|
206
253
|
}
|
|
@@ -210,7 +257,7 @@ async function executeUndo(runId) {
|
|
|
210
257
|
const pendingMirror = getPendingMirrorRefresh?.();
|
|
211
258
|
if (pendingMirror) {
|
|
212
259
|
await pendingMirror.catch(() => {});
|
|
213
|
-
|
|
260
|
+
run = assertInputs();
|
|
214
261
|
}
|
|
215
262
|
|
|
216
263
|
if ((Array.isArray(run.undoTrackedChanges) && run.undoTrackedChanges.length) || hasTrackedEditorUndo(run)
|
|
@@ -272,8 +319,9 @@ async function executeUndo(runId) {
|
|
|
272
319
|
}
|
|
273
320
|
}
|
|
274
321
|
|
|
275
|
-
|
|
322
|
+
run = assertInputs();
|
|
276
323
|
const deadlineAt = await prepareUndoDispatch(runId);
|
|
324
|
+
run = assertInputs();
|
|
277
325
|
setRunUndoStatus(runId, 'running');
|
|
278
326
|
appendRunRecordEvent(runId, {
|
|
279
327
|
title: tr('undoNoTraceStarted'),
|
|
@@ -281,7 +329,8 @@ async function executeUndo(runId) {
|
|
|
281
329
|
detail: { [tr('detailWillUndo')]: formatOperationFiles(selectedOperations) }
|
|
282
330
|
});
|
|
283
331
|
|
|
284
|
-
|
|
332
|
+
assertInputs = checkpointGuard(run, assertCurrent);
|
|
333
|
+
await persistUndoOwnerMarker(runId); run = assertInputs();
|
|
285
334
|
const result = await callPageBridge('applyOperations', {
|
|
286
335
|
operations: selectedOperations, deadlineAt, reviewOwnerToken: reviewOwners.get(runId)?.token,
|
|
287
336
|
baseFiles: run.undoBaseFiles || [],
|
|
@@ -289,7 +338,7 @@ async function executeUndo(runId) {
|
|
|
289
338
|
// Undo remains bound to its original project, including after navigation.
|
|
290
339
|
runProjectId: getRunProjectIdForWriteback(run)
|
|
291
340
|
});
|
|
292
|
-
|
|
341
|
+
run = assertInputs();
|
|
293
342
|
const undoApplied = isUndoResultEffectivelyApplied(run, result);
|
|
294
343
|
writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
|
|
295
344
|
appendUndoReviewingPolicyEvent(runId, result.reviewingPolicy);
|
|
@@ -321,6 +370,7 @@ async function executeUndo(runId) {
|
|
|
321
370
|
|
|
322
371
|
async function executeTrackedUndo(runId, run) {
|
|
323
372
|
const assertCurrent = undoOwner(run);
|
|
373
|
+
let assertInputs = checkpointGuard(run, assertCurrent);
|
|
324
374
|
const fileUndoOperations = Modules.UndoOperations.getFileUndoOperations(run);
|
|
325
375
|
const textRun = Modules.UndoOperations.textUndoRun(run);
|
|
326
376
|
const trackedUndo = textRun.undoTrackedChanges.length > 0;
|
|
@@ -343,8 +393,9 @@ async function executeTrackedUndo(runId, run) {
|
|
|
343
393
|
return;
|
|
344
394
|
}
|
|
345
395
|
|
|
346
|
-
|
|
396
|
+
run = assertInputs();
|
|
347
397
|
const deadlineAt = await prepareUndoDispatch(runId);
|
|
398
|
+
run = assertInputs();
|
|
348
399
|
// Keep the review lifecycle until every pending part has settled.
|
|
349
400
|
// Its local lock stays active while verified recovery progress is persisted.
|
|
350
401
|
const lifecycleReject = (trackedUndo || fileUndoOperations.length > 0) && isTrackedChangeLifecycleRun(run);
|
|
@@ -363,7 +414,8 @@ async function executeTrackedUndo(runId, run) {
|
|
|
363
414
|
|
|
364
415
|
let result;
|
|
365
416
|
try {
|
|
366
|
-
|
|
417
|
+
assertInputs = checkpointGuard(run, assertCurrent);
|
|
418
|
+
await persistUndoOwnerMarker(runId); run = assertInputs();
|
|
367
419
|
const trackedUndoPostFilesAtDispatch = buildTrackedUndoPostFiles(textRun);
|
|
368
420
|
result = hasTextUndo ? await callPageBridge('rejectTrackedChanges', {
|
|
369
421
|
trackedChanges: textRun.undoTrackedChanges || [], deadlineAt, reviewOwnerToken: reviewOwners.get(runId)?.token,
|
|
@@ -375,12 +427,13 @@ async function executeTrackedUndo(runId, run) {
|
|
|
375
427
|
// The page-side project guard still refuses writes after navigation.
|
|
376
428
|
runProjectId: getRunProjectIdForWriteback(run)
|
|
377
429
|
}) : { ok: true, applied: [], skipped: [] };
|
|
378
|
-
|
|
430
|
+
run = assertInputs();
|
|
379
431
|
if (hasTextUndo) WritebackSettlement.attachUndoNotVerifiedFailure(textRun, result, {
|
|
380
432
|
buildFailure: buildContentFailure, postFiles: trackedUndoPostFilesAtDispatch });
|
|
381
433
|
if (fileUndoOperations.length && lifecycleReject && hasTextUndo) {
|
|
382
434
|
WritebackSettlement.attachUndoNotVerifiedFailure(textRun, result, { buildFailure: buildContentFailure, postFiles: trackedUndoPostFilesAtDispatch });
|
|
383
435
|
await applyTrackedChangeSettlement(runId, 'reject', { ...result, ok: false }, { assertCurrent });
|
|
436
|
+
run = assertCurrent(); assertInputs = checkpointGuard(run, assertCurrent);
|
|
384
437
|
}
|
|
385
438
|
if (fileUndoOperations.length) {
|
|
386
439
|
let createdResult;
|
|
@@ -399,7 +452,7 @@ async function executeTrackedUndo(runId, run) {
|
|
|
399
452
|
operation, result: { ok: false, code: 'undo_operation_failed', reason: error.message }
|
|
400
453
|
})) };
|
|
401
454
|
}
|
|
402
|
-
|
|
455
|
+
run = assertInputs();
|
|
403
456
|
result = Modules.UndoOperations.mergeFileUndoResult(result, createdResult, fileUndoOperations);
|
|
404
457
|
}
|
|
405
458
|
appendRunRecordEvent(runId, {
|
|
@@ -436,23 +489,24 @@ async function executeTrackedUndo(runId, run) {
|
|
|
436
489
|
}
|
|
437
490
|
|
|
438
491
|
function reviewOwner(run) {
|
|
439
|
-
const
|
|
440
|
-
|
|
492
|
+
const projectId = getCurrentProjectId(), accountScopeId = getAccountScopeId();
|
|
493
|
+
const assertRun = undoOwner(run);
|
|
441
494
|
const assert = () => {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
495
|
+
let live;
|
|
496
|
+
try { live = assertRun(); } catch (error) {
|
|
497
|
+
if (error.code === 'review_context_changed') error.message = 'The original review project, account or session changed.';
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
if (!projectId || !accountScopeId || live.runProjectId !== projectId
|
|
501
|
+
|| (live.runAccountScopeId && live.runAccountScopeId !== accountScopeId))
|
|
448
502
|
throw Object.assign(new Error('The original review project, account or session changed.'), { code: 'review_context_changed' });
|
|
449
|
-
return
|
|
503
|
+
return live;
|
|
450
504
|
};
|
|
451
|
-
assert(); return {
|
|
505
|
+
assert(); return { projectId, accountScopeId, assert };
|
|
452
506
|
}
|
|
453
507
|
|
|
454
508
|
async function executeAcceptRun(runId) {
|
|
455
|
-
|
|
509
|
+
let run = findRunRecord(runId);
|
|
456
510
|
if (!run || !isTrackedChangeLifecycleRun(run)) {
|
|
457
511
|
return;
|
|
458
512
|
}
|
|
@@ -471,6 +525,7 @@ async function executeAcceptRun(runId) {
|
|
|
471
525
|
}
|
|
472
526
|
|
|
473
527
|
const owner = reviewOwner(run);
|
|
528
|
+
const assertInputs = checkpointGuard(run, owner.assert);
|
|
474
529
|
trackedChangeInFlight.set(runId, 'accept');
|
|
475
530
|
refreshRunCardControls(runId);
|
|
476
531
|
appendRunRecordEvent(runId, {
|
|
@@ -481,6 +536,7 @@ async function executeAcceptRun(runId) {
|
|
|
481
536
|
|
|
482
537
|
try {
|
|
483
538
|
const deadlineAt = await prepareUndoDispatch(runId);
|
|
539
|
+
run = assertInputs();
|
|
484
540
|
const result = await callPageBridge('acceptTrackedChanges', { deadlineAt, reviewOwnerToken: reviewOwners.get(runId)?.token,
|
|
485
541
|
trackedChanges: run.undoTrackedChanges || [],
|
|
486
542
|
expectedFiles: run.undoExpectedFiles || [],
|
|
@@ -488,7 +544,7 @@ async function executeAcceptRun(runId) {
|
|
|
488
544
|
appliedOperations: Array.isArray(run.appliedOperations) ? run.appliedOperations : [],
|
|
489
545
|
runProjectId: owner.projectId, accountScopeId: owner.accountScopeId
|
|
490
546
|
});
|
|
491
|
-
|
|
547
|
+
run = assertInputs();
|
|
492
548
|
appendAcceptDiagnosticEvents(runId, Array.isArray(result.diagnostics) ? result.diagnostics : []);
|
|
493
549
|
appendRunRecordEvent(runId, {
|
|
494
550
|
title: tr('runAcceptTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
|
|
@@ -565,10 +621,13 @@ async function applyLegacyUndoSettlement(runId, undoStatus, result) {
|
|
|
565
621
|
})
|
|
566
622
|
);
|
|
567
623
|
await Modules.ScopedPersistenceCoordinator.applyReviewTransition({ getState, runId, transition,
|
|
568
|
-
legacyUndo: true, persist: flushQueuedSaveState, assertCurrent });
|
|
624
|
+
legacyUndo: true, persist: flushQueuedSaveState, assertCurrent, SessionState: Modules.SessionState });
|
|
569
625
|
refreshRunCardControls(runId);
|
|
570
626
|
}
|
|
571
|
-
|
|
627
|
+
// From the shared review lock until settlement, a replaced view would
|
|
628
|
+
// invalidate the action's ownership checks after Page effects.
|
|
629
|
+
const hasActiveReview = () => reviewOwners.size > 0;
|
|
630
|
+
return { recoverInterruptedUndoStatuses, undoRun, undoRunTrackedChanges, acceptRun, applyLegacyUndoSettlement, hasActiveReview };
|
|
572
631
|
}
|
|
573
632
|
return { create };
|
|
574
633
|
});
|