codex-overleaf-link 2.3.5 → 2.4.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 +63 -31
- package/README.zh-CN.md +543 -0
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +7 -0
- package/extension/src/backgroundUpdateCoordinator.js +88 -4
- package/extension/src/content/activeTurnControl.js +1 -0
- package/extension/src/content/assetTransferBroker.js +2 -0
- package/extension/src/content/contentRuntime.js +77 -57
- package/extension/src/content/contextTray.js +4 -0
- package/extension/src/content/diagnosticsController.js +17 -3
- package/extension/src/content/diagnosticsPanel.js +129 -5
- package/extension/src/content/generated/content.bundle.js +1422 -348
- package/extension/src/content/generated/content.bundle.meta.json +7 -3
- package/extension/src/content/legacyGlobalRegistry.js +3 -0
- package/extension/src/content/modelPicker.js +21 -9
- package/extension/src/content/modelPickerSupport.js +38 -1
- package/extension/src/content/nativeCompatibilityController.js +4 -1
- package/extension/src/content/otWarmMirror.js +126 -92
- package/extension/src/content/otWarmMirrorController.js +122 -1
- package/extension/src/content/pageBridgeClient.js +2 -0
- package/extension/src/content/panelMaintenance.js +26 -15
- package/extension/src/content/providerSettingsCoordinator.js +1 -0
- package/extension/src/content/runFailureNotice.js +104 -0
- package/extension/src/content/runResultActions.js +16 -1
- package/extension/src/content/runScrollLayout.js +124 -0
- package/extension/src/content/runTimelineView.js +37 -11
- package/extension/src/content/sessionForkController.js +20 -11
- package/extension/src/content/settingsPanel.js +28 -11
- package/extension/src/content/trackedChangeCaptureController.js +153 -0
- package/extension/src/content/updateNotice.js +24 -4
- package/extension/src/content/writebackOrchestrator.js +31 -0
- package/extension/src/page/binaryAssetUploader.js +101 -29
- package/extension/src/page/overleafRealtimeObserver.js +120 -25
- package/extension/src/page/textFileCreator.js +493 -0
- package/extension/src/page/trackedChangeCapture.js +342 -0
- package/extension/src/page/trackedChangesLifecycle.js +145 -149
- package/extension/src/page/treeOperations.js +30 -2
- package/extension/src/page/writebackRouter.js +94 -48
- package/extension/src/pageBridge.js +62 -25
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/i18n.js +77 -37
- package/extension/src/shared/managedUpdateProjection.js +1 -1
- package/extension/src/shared/otText.js +0 -1
- package/extension/src/shared/pageRpcContract.js +2 -1
- package/extension/src/shared/sessionState.js +23 -23
- package/extension/src/shared/sessionTitle.js +120 -0
- package/extension/src/shared/storageDb.js +10 -12
- package/extension/src/shared/storageRunActions.js +29 -1
- package/extension/src/shared/updateRuntimeIdentity.js +32 -0
- package/extension/src/shared/writebackSettlement.js +19 -5
- package/extension/styles/panel.css +128 -24
- package/native-host/src/mirrorWorkspace.js +20 -1
- package/native-host/src/releaseProxy.js +177 -0
- package/native-host/src/releaseTransport.js +154 -0
- package/native-host/src/taskRunnerRuntime.js +25 -0
- package/native-host/src/updateManager.js +40 -39
- package/package.json +5 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const UPDATE_STATE_KEY = 'codex-overleaf-managed-update-state-v1';
|
|
5
5
|
const CONSENT_STATE_KEY = 'codex-overleaf-update-consent-v1';
|
|
6
6
|
const UPDATE_RELOAD_TABS_KEY = 'codex-overleaf-managed-update-tabs-v1';
|
|
7
|
+
const MANUAL_RELOAD_TABS_KEY = 'codex-overleaf-manual-runtime-reload-v1';
|
|
7
8
|
const CHECK_ALARM = 'codex-overleaf-consent-update-check';
|
|
8
9
|
const IDLE_ALARM = 'codex-overleaf-consent-update-idle';
|
|
9
10
|
const WATCHDOG_ALARM = 'codex-overleaf-consent-update-watchdog';
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
const CHECK_INTERVAL_MINUTES = 24 * 60;
|
|
13
14
|
const SNOOZE_MS = 24 * 60 * 60 * 1000;
|
|
14
15
|
const CANDIDATE_MAX_AGE_MS = 5 * 60 * 1000;
|
|
15
|
-
const CHECK_REQUEST_TIMEOUT_MS =
|
|
16
|
+
const CHECK_REQUEST_TIMEOUT_MS = 55 * 1000;
|
|
16
17
|
const ACTIVATION_TIMEOUT_MS = 20 * 1000;
|
|
17
18
|
const ACTIVATION_POLL_MS = 250;
|
|
18
19
|
const FAST_IDLE_RETRY_MS = 4000;
|
|
@@ -31,7 +32,8 @@
|
|
|
31
32
|
'codex-overleaf/consent-update-check',
|
|
32
33
|
'codex-overleaf/consent-update-install',
|
|
33
34
|
'codex-overleaf/consent-update-later',
|
|
34
|
-
'codex-overleaf/consent-update-dismiss'
|
|
35
|
+
'codex-overleaf/consent-update-dismiss',
|
|
36
|
+
'codex-overleaf/consent-update-reload'
|
|
35
37
|
]);
|
|
36
38
|
|
|
37
39
|
const policy = root.CodexOverleafUpdateConsent;
|
|
@@ -41,6 +43,10 @@
|
|
|
41
43
|
let policyTail = Promise.resolve();
|
|
42
44
|
let idleRetryTimer = null;
|
|
43
45
|
let activationPromise = null;
|
|
46
|
+
let runtimeStatus = null;
|
|
47
|
+
let runtimeStatusAt = 0;
|
|
48
|
+
let runtimeStatusRead = null;
|
|
49
|
+
let runtimeReloadMessage = '';
|
|
44
50
|
|
|
45
51
|
function init(options = {}) {
|
|
46
52
|
if (initialized || !policy || !revocation) return;
|
|
@@ -92,6 +98,7 @@
|
|
|
92
98
|
await reconcileRecoveryState();
|
|
93
99
|
await settleTerminalConsent();
|
|
94
100
|
await armLegacyGuard();
|
|
101
|
+
await finishManualRuntimeReload();
|
|
95
102
|
await publishView();
|
|
96
103
|
const updateState = await getUpdateState();
|
|
97
104
|
if (['staged', 'waiting_for_idle'].includes(updateState.state)) {
|
|
@@ -129,6 +136,8 @@
|
|
|
129
136
|
return postponeUpdate();
|
|
130
137
|
case 'codex-overleaf/consent-update-dismiss':
|
|
131
138
|
return dismissCompletedUpdate();
|
|
139
|
+
case 'codex-overleaf/consent-update-reload':
|
|
140
|
+
return reloadInstalledRuntime();
|
|
132
141
|
default:
|
|
133
142
|
throw codedError('unknown_update_action', 'Unknown update action.');
|
|
134
143
|
}
|
|
@@ -648,11 +657,86 @@
|
|
|
648
657
|
}
|
|
649
658
|
|
|
650
659
|
async function getView() {
|
|
651
|
-
|
|
652
|
-
|
|
660
|
+
let [state, consent] = await Promise.all([getUpdateState(), getConsentState()]);
|
|
661
|
+
const busy = state.state === 'checking' || policy.isExecutionState(state.state) || consent.authorizationId;
|
|
662
|
+
const runtime = busy ? { state: 'transaction_active' } : await readRuntimeStatus();
|
|
663
|
+
if (runtime.state === 'aligned' && ['failed', 'rolled_back'].includes(state.state) &&
|
|
664
|
+
policy.compareStableVersions(state.latestVersion || state.currentVersion, runtime.installedVersion) <= 0) {
|
|
665
|
+
state = await setUpdateState({ ...state, state: 'idle', currentVersion: runtime.installedVersion,
|
|
666
|
+
latestVersion: runtime.installedVersion, code: '', message: '', transactionId: '', blocker: '', blockers: [] });
|
|
667
|
+
}
|
|
668
|
+
const view = policy.deriveViewModel(state, consent, {
|
|
653
669
|
currentVersion: currentVersion(),
|
|
654
670
|
now: Date.now()
|
|
655
671
|
});
|
|
672
|
+
view.runtime = runtime;
|
|
673
|
+
const pending = (await chrome.storage.local.get(MANUAL_RELOAD_TABS_KEY))?.[MANUAL_RELOAD_TABS_KEY];
|
|
674
|
+
const pagesPending = runtime.state === 'aligned' && pending?.targetVersion === runtime.installedVersion && pending.tabIds?.length;
|
|
675
|
+
if (runtime.state === 'reload_required' || pagesPending) {
|
|
676
|
+
view.showPanel = true;
|
|
677
|
+
view.state = { ...state, state: pagesPending ? 'reload_tabs_required' : 'reload_required',
|
|
678
|
+
currentVersion: runtime.extensionVersion, latestVersion: runtime.installedVersion,
|
|
679
|
+
code: 'update_reload_required', message: runtimeReloadMessage };
|
|
680
|
+
view.badge = { text: 'UP', color: '#3578bd' };
|
|
681
|
+
}
|
|
682
|
+
return view;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
async function readRuntimeStatus(force = false) {
|
|
686
|
+
if (!root.CodexOverleafUpdateRuntimeIdentity) return { state: 'unknown' };
|
|
687
|
+
if (!force && runtimeStatus && Date.now() - runtimeStatusAt < 5000) return runtimeStatus;
|
|
688
|
+
if (runtimeStatusRead) return runtimeStatusRead;
|
|
689
|
+
runtimeStatusRead = withTimeout(requestNative('update.status'), 4000,
|
|
690
|
+
codedError('update_status_timeout', 'Installed version inspection timed out.')).then(status => {
|
|
691
|
+
runtimeStatusAt = Date.now();
|
|
692
|
+
runtimeStatus = root.CodexOverleafUpdateRuntimeIdentity.inspectInstalledRuntime(status, {
|
|
693
|
+
extensionVersion: chrome.runtime.getManifest().version, runtimeVersion: currentVersion()
|
|
694
|
+
});
|
|
695
|
+
return runtimeStatus;
|
|
696
|
+
}).catch(() => ({ state: 'unknown' })).finally(() => { runtimeStatusRead = null; });
|
|
697
|
+
return runtimeStatusRead;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
async function requireReloadSafePoint() {
|
|
701
|
+
const tabs = await chrome.tabs.query({ url: OVERLEAF_MATCHES });
|
|
702
|
+
const probes = await Promise.all(tabs.filter(isUsableEditorTab).map(tab => probeTabIdle(tab.id)));
|
|
703
|
+
const nativeGate = await requestNative('update.canApply').then(result => ({ ok: true, result }))
|
|
704
|
+
.catch(error => ({ ok: false, error: safeError(error) }));
|
|
705
|
+
const blockers = root.CodexOverleafUpdateStatus?.collectBlockers(probes, nativeGate) || ['busy'];
|
|
706
|
+
if (nativeBridge?.getPendingState?.().executionRequests > 0) blockers.push('background_execution_pending');
|
|
707
|
+
if (blockers.length) {
|
|
708
|
+
runtimeReloadMessage = 'Reload is waiting for Overleaf to be saved and idle (' + blockers.join(', ') + ').';
|
|
709
|
+
throw codedError('update_reload_busy', runtimeReloadMessage);
|
|
710
|
+
}
|
|
711
|
+
runtimeReloadMessage = '';
|
|
712
|
+
return tabs.filter(tab => Number.isInteger(tab.id) && !tab.discarded && tab.status !== 'unloaded');
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
async function reloadInstalledRuntime() {
|
|
716
|
+
const runtime = await readRuntimeStatus(true);
|
|
717
|
+
if (runtime.state === 'aligned') { await finishManualRuntimeReload(); return getView(); }
|
|
718
|
+
if (runtime.state !== 'reload_required') throw codedError('update_reload_unavailable', 'No verified installed update is awaiting reload.');
|
|
719
|
+
const tabs = await requireReloadSafePoint();
|
|
720
|
+
await chrome.storage.local.set({ [MANUAL_RELOAD_TABS_KEY]: {
|
|
721
|
+
targetVersion: runtime.installedVersion, tabIds: tabs.map(tab => tab.id)
|
|
722
|
+
} });
|
|
723
|
+
setTimeout(() => chrome.runtime.reload(), 0);
|
|
724
|
+
return getView();
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
async function finishManualRuntimeReload() {
|
|
728
|
+
const pending = (await chrome.storage.local.get(MANUAL_RELOAD_TABS_KEY))?.[MANUAL_RELOAD_TABS_KEY];
|
|
729
|
+
if (!pending || !Array.isArray(pending.tabIds)) return;
|
|
730
|
+
const runtime = await readRuntimeStatus(true);
|
|
731
|
+
if (runtime.state !== 'aligned' || runtime.installedVersion !== pending.targetVersion) return;
|
|
732
|
+
let tabs;
|
|
733
|
+
try { tabs = await requireReloadSafePoint(); } catch (_error) { return; }
|
|
734
|
+
const remaining = [];
|
|
735
|
+
for (const tab of tabs.filter(tab => pending.tabIds.includes(tab.id))) {
|
|
736
|
+
try { await chrome.tabs.reload(tab.id); } catch (_error) { remaining.push(tab.id); }
|
|
737
|
+
}
|
|
738
|
+
if (remaining.length) await chrome.storage.local.set({ [MANUAL_RELOAD_TABS_KEY]: { ...pending, tabIds: remaining } });
|
|
739
|
+
else await chrome.storage.local.remove(MANUAL_RELOAD_TABS_KEY);
|
|
656
740
|
}
|
|
657
741
|
|
|
658
742
|
async function publishView() {
|
|
@@ -123,6 +123,7 @@
|
|
|
123
123
|
const raw = entry?.event || {};
|
|
124
124
|
if (raw.type === 'codex.turn.bound') {
|
|
125
125
|
session.codexThreadId = String(raw.detail?.threadId || session.codexThreadId || '');
|
|
126
|
+
record.codexThreadId = String(raw.detail?.threadId || record.codexThreadId || '');
|
|
126
127
|
record.codexTurnId = String(raw.detail?.turnId || record.codexTurnId || '');
|
|
127
128
|
record.nativeEventSeq = sequence;
|
|
128
129
|
continue;
|
|
@@ -139,8 +139,10 @@
|
|
|
139
139
|
// recordUndoFromApply classify a successful Track write as legacy Undo,
|
|
140
140
|
// which hides Accept and prevents the lifecycle from surviving reload.
|
|
141
141
|
merged.trackedChanges.push(...(Array.isArray(result?.trackedChanges) ? result.trackedChanges : []));
|
|
142
|
+
if (result?.trackedChangeCaptures?.length) (merged.trackedChangeCaptures ||= []).push(...result.trackedChangeCaptures);
|
|
142
143
|
if (result?.ok !== true) merged.ok = false;
|
|
143
144
|
}
|
|
145
|
+
if (merged.trackedChangeCaptures?.some(capture => capture.state !== 'observed')) merged.trackedChanges = [];
|
|
144
146
|
if (merged.skipped.length) merged.ok = false;
|
|
145
147
|
return merged;
|
|
146
148
|
}
|
|
@@ -51,8 +51,7 @@
|
|
|
51
51
|
'codex.providers.delete',
|
|
52
52
|
'task.run',
|
|
53
53
|
'mirror.sync',
|
|
54
|
-
'mirror.patchFiles',
|
|
55
|
-
'mirror.confirmWriteback',
|
|
54
|
+
'mirror.patchFiles', 'mirror.confirmWriteback', 'mirror.invalidate',
|
|
56
55
|
'mirror.scanSensitive',
|
|
57
56
|
'codex.history.clearPlugin',
|
|
58
57
|
'skills.list',
|
|
@@ -418,6 +417,8 @@
|
|
|
418
417
|
const runTimelineView = Modules.RunTimelineView.create({
|
|
419
418
|
RunGuidanceView: Modules.RunGuidanceView,
|
|
420
419
|
RunResultActions: runResultActions,
|
|
420
|
+
RunScrollLayout: Modules.RunScrollLayout,
|
|
421
|
+
RunFailureNotice: Modules.RunFailureNotice,
|
|
421
422
|
tr,
|
|
422
423
|
tx,
|
|
423
424
|
getLocale,
|
|
@@ -581,6 +582,7 @@
|
|
|
581
582
|
RUN_SNAPSHOT_ZIP_TIMEOUT_MS,
|
|
582
583
|
getState: () => state,
|
|
583
584
|
getCurrentRunView: () => currentRunView,
|
|
585
|
+
appendRunRecordEvent,
|
|
584
586
|
onMirrorRefreshSettled: runSettlementPersistence.settleMirrorRefresh
|
|
585
587
|
});
|
|
586
588
|
const {
|
|
@@ -788,6 +790,7 @@
|
|
|
788
790
|
},
|
|
789
791
|
saveState,
|
|
790
792
|
saveStateSoon,
|
|
793
|
+
onProjectFilesReady: refreshHistoricalProjectReferences,
|
|
791
794
|
updateActiveSession,
|
|
792
795
|
callPageBridge,
|
|
793
796
|
getCurrentProjectId,
|
|
@@ -835,6 +838,7 @@
|
|
|
835
838
|
});
|
|
836
839
|
|
|
837
840
|
let panel = null;
|
|
841
|
+
let probeStatusSnapshot = null;
|
|
838
842
|
let panelRendererInstance = null;
|
|
839
843
|
let sessionPanelInstance = null;
|
|
840
844
|
let settingsPanelInstance = null;
|
|
@@ -1031,7 +1035,7 @@
|
|
|
1031
1035
|
providerSettingsCoordinator.syncSessionProvider().catch(() => {});
|
|
1032
1036
|
syncOtWarmMirrorController().catch(error => {
|
|
1033
1037
|
updateOtStatusDisplay('unavailable');
|
|
1034
|
-
appendPlainLog(tx(`Failed to sync
|
|
1038
|
+
appendPlainLog(tx(`Failed to sync OT warm mirror: ${error.message}`, `同步 OT 预热镜像失败:${error.message}`));
|
|
1035
1039
|
});
|
|
1036
1040
|
await refreshProbe({ quiet: true });
|
|
1037
1041
|
}
|
|
@@ -1274,6 +1278,7 @@
|
|
|
1274
1278
|
onProvidersOpen: () => providerSettingsCoordinator.open(),
|
|
1275
1279
|
onClearAllHistory: () => clearAllHistoryWithConfirm(),
|
|
1276
1280
|
onHistoryOpen: () => renderAuditHistoryPanel(),
|
|
1281
|
+
onStorageOpen: () => refreshStorageUsageSummary(),
|
|
1277
1282
|
onHistoryFilter: () => applyAuditHistoryFilter(),
|
|
1278
1283
|
// Experimental OT mirror: a single visible switch whose click is
|
|
1279
1284
|
// intercepted so the confirm-before-enable flow still runs.
|
|
@@ -1444,9 +1449,6 @@
|
|
|
1444
1449
|
function renderAttachmentPreviewList(attachments = [], container, options = {}) {
|
|
1445
1450
|
return composerAttachmentController.renderAttachmentPreviewList(attachments, container, options);
|
|
1446
1451
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
1452
|
function applyLocaleToPanel() {
|
|
1451
1453
|
if (!panel) {
|
|
1452
1454
|
return;
|
|
@@ -1535,6 +1537,17 @@
|
|
|
1535
1537
|
updateModelDisplay();
|
|
1536
1538
|
renderSessionList();
|
|
1537
1539
|
renderContextSelection();
|
|
1540
|
+
const probeStatus = panel.querySelector('[data-probe-status]');
|
|
1541
|
+
if (probeStatus?.dataset.refreshing === 'true') probeStatus.textContent = tr('refreshProbeLoading');
|
|
1542
|
+
else if (probeStatus && probeStatusSnapshot?.projectId === getCurrentProjectId()) {
|
|
1543
|
+
const { failed, probe, userInitiated } = probeStatusSnapshot;
|
|
1544
|
+
const text = failed ? tr('refreshProbeFailed') : formatProbeStatusBar(probe);
|
|
1545
|
+
probeStatus.textContent = userInitiated && !failed ? tr('refreshProbeDone', { status: text }) : text;
|
|
1546
|
+
if (!failed) updateExistingProbeNotice(probe);
|
|
1547
|
+
}
|
|
1548
|
+
updateSkillsEntrySummary();
|
|
1549
|
+
providerSettingsCoordinator.renderSummary();
|
|
1550
|
+
if (settingsPanelInstance?.container?.querySelector('[data-storage-card]')?.open) refreshStorageUsageSummary();
|
|
1538
1551
|
}
|
|
1539
1552
|
|
|
1540
1553
|
function setElementTitleAndAria(selector, title, ariaLabel) {
|
|
@@ -1764,9 +1777,7 @@
|
|
|
1764
1777
|
return contextTrayController.setContextStatus(text);
|
|
1765
1778
|
}
|
|
1766
1779
|
|
|
1767
|
-
function resetContextProject() {
|
|
1768
|
-
return contextTrayController.resetContextProject();
|
|
1769
|
-
}
|
|
1780
|
+
function resetContextProject() { return contextTrayController.resetContextProject(); }
|
|
1770
1781
|
|
|
1771
1782
|
async function runTask(options = {}) {
|
|
1772
1783
|
// Barrier on a still-flying background mirror refresh from the previous
|
|
@@ -2393,7 +2404,7 @@
|
|
|
2393
2404
|
}
|
|
2394
2405
|
|
|
2395
2406
|
await saveState();
|
|
2396
|
-
applyStateToPanel();
|
|
2407
|
+
applyStateToPanel({ preserveScroll: true });
|
|
2397
2408
|
} catch (persistenceError) {
|
|
2398
2409
|
appendRunEvent({
|
|
2399
2410
|
title: tx('Codex result was generated, but saving local session history failed.', 'Codex 结果已生成,但保存本地会话记录失败。'),
|
|
@@ -2473,14 +2484,15 @@
|
|
|
2473
2484
|
setRunning(false);
|
|
2474
2485
|
nativeChannel.clearActiveRequest();
|
|
2475
2486
|
stopRunElapsedTick();
|
|
2476
|
-
if (isExperimentalOtEnabled()) {
|
|
2477
|
-
await resumeOtWarmMirror('run-settled');
|
|
2478
|
-
}
|
|
2479
2487
|
await flushQueuedSaveState().catch(() => {});
|
|
2480
2488
|
currentRunView = null;
|
|
2481
2489
|
runCancellationRequested = false;
|
|
2482
2490
|
runCancellationController = null;
|
|
2483
2491
|
activeTurnControl.release(settlement.requestId);
|
|
2492
|
+
if (isExperimentalOtEnabled()) {
|
|
2493
|
+
try { await resumeOtWarmMirror('run-settled'); }
|
|
2494
|
+
catch (_error) { updateOtStatusDisplay('unavailable'); }
|
|
2495
|
+
}
|
|
2484
2496
|
if (settlement.requestId) {
|
|
2485
2497
|
activeTurnControl.acknowledge(settlement.requestId).catch(() => {});
|
|
2486
2498
|
}
|
|
@@ -4943,6 +4955,7 @@
|
|
|
4943
4955
|
}
|
|
4944
4956
|
|
|
4945
4957
|
async function refreshProbe(options = {}) {
|
|
4958
|
+
const projectId = getCurrentProjectId();
|
|
4946
4959
|
const userInitiated = options.userInitiated === true;
|
|
4947
4960
|
if (userInitiated) {
|
|
4948
4961
|
setRefreshProbeLoading(true);
|
|
@@ -4958,6 +4971,8 @@
|
|
|
4958
4971
|
const probe = await callPageBridge('probe', {
|
|
4959
4972
|
manualOverride: state?.requireReviewing === false
|
|
4960
4973
|
});
|
|
4974
|
+
if (projectId !== getCurrentProjectId()) return null;
|
|
4975
|
+
probeStatusSnapshot = { projectId, probe, userInitiated };
|
|
4961
4976
|
const status = panel?.querySelector('[data-probe-status]');
|
|
4962
4977
|
if (status) {
|
|
4963
4978
|
status.textContent = userInitiated
|
|
@@ -4976,6 +4991,8 @@
|
|
|
4976
4991
|
if (!userInitiated) {
|
|
4977
4992
|
throw error;
|
|
4978
4993
|
}
|
|
4994
|
+
if (projectId !== getCurrentProjectId()) return null;
|
|
4995
|
+
probeStatusSnapshot = { projectId, failed: true };
|
|
4979
4996
|
const status = panel?.querySelector('[data-probe-status]');
|
|
4980
4997
|
if (status) {
|
|
4981
4998
|
status.textContent = tr('refreshProbeFailed');
|
|
@@ -5553,6 +5570,7 @@
|
|
|
5553
5570
|
title: session.title || '',
|
|
5554
5571
|
titleSource: session.titleSource || 'auto',
|
|
5555
5572
|
focusFiles: session.focusFiles || [],
|
|
5573
|
+
projectReferenceFiles: session.projectReferenceFiles || [],
|
|
5556
5574
|
codexThreadId: session.codexThreadId || '',
|
|
5557
5575
|
createdAt: session.createdAt,
|
|
5558
5576
|
updatedAt: session.updatedAt,
|
|
@@ -5960,15 +5978,7 @@
|
|
|
5960
5978
|
}
|
|
5961
5979
|
|
|
5962
5980
|
function getRenderedModelEntries() {
|
|
5963
|
-
return
|
|
5964
|
-
id: option.value,
|
|
5965
|
-
label: option.textContent,
|
|
5966
|
-
reasoningEfforts: (option.dataset.reasoningEfforts || '').split(',').filter(Boolean),
|
|
5967
|
-
defaultReasoningEffort: option.dataset.defaultReasoningEffort || '',
|
|
5968
|
-
reasoningPresentation: option.dataset.reasoningPresentation || '',
|
|
5969
|
-
speedTiers: (option.dataset.speedTiers || 'standard').split(',').filter(Boolean),
|
|
5970
|
-
defaultSpeedTier: option.dataset.defaultSpeedTier || 'standard'
|
|
5971
|
-
}));
|
|
5981
|
+
return Modules.ModelPickerSupport.getRenderedModelEntries(panel);
|
|
5972
5982
|
}
|
|
5973
5983
|
|
|
5974
5984
|
async function selectMode(mode) {
|
|
@@ -6069,7 +6079,7 @@
|
|
|
6069
6079
|
saveStateSoon();
|
|
6070
6080
|
}
|
|
6071
6081
|
|
|
6072
|
-
function applyStateToPanel() {
|
|
6082
|
+
function applyStateToPanel(options = {}) {
|
|
6073
6083
|
applyPanelTheme(getThemePreference());
|
|
6074
6084
|
if ((state.providerId || 'builtin') === 'builtin' && !modelSelectHasOption(state.model)) {
|
|
6075
6085
|
renderModelOptions(getModelCatalog().FALLBACK_MODELS, state.model);
|
|
@@ -6105,7 +6115,8 @@
|
|
|
6105
6115
|
syncModeControls();
|
|
6106
6116
|
applySessionLabel();
|
|
6107
6117
|
renderSessionList();
|
|
6108
|
-
renderRunHistory();
|
|
6118
|
+
renderRunHistory(options);
|
|
6119
|
+
getTrackedChangeCaptureController().schedule();
|
|
6109
6120
|
renderContextSelection();
|
|
6110
6121
|
renderContextSummary();
|
|
6111
6122
|
// v1.8.0 C4: refresh recovery — small attachments persisted in state are
|
|
@@ -6195,7 +6206,7 @@
|
|
|
6195
6206
|
undoExpectedFiles: [],
|
|
6196
6207
|
undoStatus: '',
|
|
6197
6208
|
queueItemId,
|
|
6198
|
-
nativeRequestId: '',
|
|
6209
|
+
nativeRequestId: '', codexThreadId: '',
|
|
6199
6210
|
codexTurnId: '',
|
|
6200
6211
|
nativeEventSeq: 0,
|
|
6201
6212
|
executionSnapshot: executionSnapshot
|
|
@@ -6362,7 +6373,7 @@
|
|
|
6362
6373
|
currentRunView.activeTurn = { threadId, turnId };
|
|
6363
6374
|
const record = findRunRecord(currentRunView.recordId, currentRunView.sessionId);
|
|
6364
6375
|
if (record) {
|
|
6365
|
-
record.codexTurnId = turnId;
|
|
6376
|
+
record.codexThreadId = threadId; record.codexTurnId = turnId;
|
|
6366
6377
|
record.nativeEventSeq = Math.max(Number(record.nativeEventSeq || 0), Number(journalSeq || 0));
|
|
6367
6378
|
}
|
|
6368
6379
|
if (threadId) {
|
|
@@ -6736,6 +6747,13 @@
|
|
|
6736
6747
|
return files;
|
|
6737
6748
|
}
|
|
6738
6749
|
|
|
6750
|
+
function refreshHistoricalProjectReferences(project) {
|
|
6751
|
+
if (!project?.id || String(project.id) !== getCurrentProjectId()) return;
|
|
6752
|
+
state = updateActiveSession(state, { projectReferenceFiles: captureProjectReferenceFiles(project) });
|
|
6753
|
+
saveStateSoon();
|
|
6754
|
+
if (!currentRunView && !trackedChangeInFlight.size) renderRunHistory({ preserveScroll: true });
|
|
6755
|
+
}
|
|
6756
|
+
|
|
6739
6757
|
function persistCurrentProjectReferenceFiles(project) {
|
|
6740
6758
|
const projectFiles = captureProjectReferenceFiles(project);
|
|
6741
6759
|
if (currentRunView) {
|
|
@@ -6867,6 +6885,7 @@
|
|
|
6867
6885
|
runProjectId: getRunProjectIdForWriteback(run)
|
|
6868
6886
|
});
|
|
6869
6887
|
const undoApplied = isUndoResultEffectivelyApplied(run, result);
|
|
6888
|
+
writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
|
|
6870
6889
|
appendUndoReviewingPolicyEvent(runId, result.reviewingPolicy);
|
|
6871
6890
|
appendRunRecordEvent(runId, {
|
|
6872
6891
|
title: tr('undoResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
|
|
@@ -6929,11 +6948,16 @@
|
|
|
6929
6948
|
});
|
|
6930
6949
|
|
|
6931
6950
|
let result;
|
|
6951
|
+
let trackedUndoPostFilesAtDispatch;
|
|
6932
6952
|
try {
|
|
6953
|
+
trackedUndoPostFilesAtDispatch = buildTrackedUndoPostFiles(run);
|
|
6933
6954
|
result = await callPageBridge('rejectTrackedChanges', {
|
|
6934
6955
|
trackedChanges: run.undoTrackedChanges || [],
|
|
6935
6956
|
expectedFiles: run.undoExpectedFiles || [],
|
|
6936
|
-
postFiles:
|
|
6957
|
+
postFiles: trackedUndoPostFilesAtDispatch,
|
|
6958
|
+
untrackedUndo: run.executionSnapshot
|
|
6959
|
+
? run.executionSnapshot.requireReviewing === false && ['submitted', 'legacy-captured'].includes(run.executionSnapshot.source)
|
|
6960
|
+
: getTrackedChangeCaptureController().hasLegacyUntrackedCheckpoint(run),
|
|
6937
6961
|
// Welcome-panel + write-guard:
|
|
6938
6962
|
// bind the reject to the run's original project. If the user has
|
|
6939
6963
|
// navigated away the page-side guard refuses with
|
|
@@ -6945,6 +6969,7 @@
|
|
|
6945
6969
|
trackedChangeInFlight.delete(runId);
|
|
6946
6970
|
}
|
|
6947
6971
|
}
|
|
6972
|
+
writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
|
|
6948
6973
|
appendRunRecordEvent(runId, {
|
|
6949
6974
|
title: trackedUndo
|
|
6950
6975
|
? tr('undoTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 })
|
|
@@ -6964,7 +6989,8 @@
|
|
|
6964
6989
|
});
|
|
6965
6990
|
if (lifecycleReject) {
|
|
6966
6991
|
WritebackSettlement.attachUndoNotVerifiedFailure(run, result, {
|
|
6967
|
-
buildFailure: buildContentFailure
|
|
6992
|
+
buildFailure: buildContentFailure,
|
|
6993
|
+
postFiles: trackedUndoPostFilesAtDispatch
|
|
6968
6994
|
});
|
|
6969
6995
|
applyTrackedChangeSettlement(runId, 'reject', result);
|
|
6970
6996
|
return;
|
|
@@ -7178,6 +7204,25 @@
|
|
|
7178
7204
|
return WritebackSettlement.attachVerifiedContentToOperation(operation, result);
|
|
7179
7205
|
}
|
|
7180
7206
|
|
|
7207
|
+
let trackedChangeCaptureController = null;
|
|
7208
|
+
function getTrackedChangeCaptureController() {
|
|
7209
|
+
return trackedChangeCaptureController ||= Modules.TrackedChangeCaptureController.create({
|
|
7210
|
+
getRuns: () => (state.sessions || []).flatMap(session => session.runs || []),
|
|
7211
|
+
getProjectId: () => window.location.pathname.match(/^\/project\/([^/]+)/)?.[1] || '',
|
|
7212
|
+
buildExpectedFilesAfterOperations, normalizeTrackedChanges: normalizeApplyTrackedChanges, callPageBridge,
|
|
7213
|
+
persist: () => flushQueuedSaveState({ preserveRunActionPayload: true }),
|
|
7214
|
+
refresh: run => refreshRunCardControls(run.id),
|
|
7215
|
+
notify: (run, kind, detail) => appendRunRecordEvent(run.id, {
|
|
7216
|
+
title: {
|
|
7217
|
+
pending: tx('Files written; confirming tracked changes.', '文件已写入,正在确认留痕记录。'),
|
|
7218
|
+
observed: tx('Tracked changes confirmed; Accept is available.', '留痕记录已确认,可以接受改动。'),
|
|
7219
|
+
needs_review: tx('Tracked changes could not be safely attributed. Review them in Overleaf; Undo is preserved.', '留痕归属尚未确认,请在 Overleaf 审阅;原有撤销能力已保留。')
|
|
7220
|
+
}[kind], status: 'info', detail
|
|
7221
|
+
}),
|
|
7222
|
+
onError: error => appendPlainLog(tx('Tracked-change confirmation unavailable: ', '留痕确认暂不可用:') + error.message)
|
|
7223
|
+
});
|
|
7224
|
+
}
|
|
7225
|
+
|
|
7181
7226
|
function recordUndoFromApply(project, applyResult) {
|
|
7182
7227
|
const appliedEntries = getAppliedEntries(applyResult);
|
|
7183
7228
|
if (!currentRunView?.recordId || !appliedEntries.length) {
|
|
@@ -7221,6 +7266,7 @@
|
|
|
7221
7266
|
if (combinedTrackedChanges.length) {
|
|
7222
7267
|
record.trackedChangeStatus = 'pending';
|
|
7223
7268
|
}
|
|
7269
|
+
getTrackedChangeCaptureController().record(record, applyResult?.trackedChangeCaptures);
|
|
7224
7270
|
refreshRunCardControls(record.id);
|
|
7225
7271
|
if (combinedTrackedChanges.length) {
|
|
7226
7272
|
appendRunEvent({
|
|
@@ -7285,38 +7331,12 @@
|
|
|
7285
7331
|
return WritebackSettlement.normalizeApplyTrackedChanges(changes);
|
|
7286
7332
|
}
|
|
7287
7333
|
|
|
7288
|
-
function selectExpectedFilesForTrackedUndo(
|
|
7289
|
-
project,
|
|
7290
|
-
operations = [],
|
|
7291
|
-
trackedChanges = [],
|
|
7292
|
-
previousExpectedFiles = []
|
|
7293
|
-
) {
|
|
7294
|
-
return WritebackSettlement.selectExpectedFilesForTrackedUndo(
|
|
7295
|
-
project,
|
|
7296
|
-
operations,
|
|
7297
|
-
trackedChanges,
|
|
7298
|
-
previousExpectedFiles
|
|
7299
|
-
);
|
|
7334
|
+
function selectExpectedFilesForTrackedUndo(project, operations = [], trackedChanges = [], previousExpectedFiles = []) {
|
|
7335
|
+
return WritebackSettlement.selectExpectedFilesForTrackedUndo(project, operations, trackedChanges, previousExpectedFiles);
|
|
7300
7336
|
}
|
|
7301
7337
|
|
|
7302
7338
|
function buildTrackedUndoPostFiles(run) {
|
|
7303
|
-
|
|
7304
|
-
const appliedOperations = Array.isArray(run?.appliedOperations) ? run.appliedOperations : [];
|
|
7305
|
-
if (!expectedFiles.length || !appliedOperations.length) {
|
|
7306
|
-
return [];
|
|
7307
|
-
}
|
|
7308
|
-
|
|
7309
|
-
const postFilesByPath = buildExpectedFilesAfterOperations(
|
|
7310
|
-
{ files: expectedFiles },
|
|
7311
|
-
appliedOperations
|
|
7312
|
-
);
|
|
7313
|
-
const expectedPaths = new Set(expectedFiles.map(file => file?.path).filter(Boolean));
|
|
7314
|
-
return Array.from(postFilesByPath.entries())
|
|
7315
|
-
.filter(([path, content]) => expectedPaths.has(path) && typeof content === 'string')
|
|
7316
|
-
.map(([path, content]) => ({
|
|
7317
|
-
path,
|
|
7318
|
-
content
|
|
7319
|
-
}));
|
|
7339
|
+
return getTrackedChangeCaptureController().buildPostFiles(run);
|
|
7320
7340
|
}
|
|
7321
7341
|
|
|
7322
7342
|
function hasTrackedEditorUndo(run) {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
setState = () => {},
|
|
23
23
|
saveState = null,
|
|
24
24
|
saveStateSoon = () => {},
|
|
25
|
+
onProjectFilesReady = () => {},
|
|
25
26
|
updateActiveSession = defaultUpdateActiveSession,
|
|
26
27
|
callPageBridge = () => Promise.resolve({ ok: false }),
|
|
27
28
|
getCurrentProjectId = () => '',
|
|
@@ -201,6 +202,7 @@
|
|
|
201
202
|
renderContextFiles(contextProject);
|
|
202
203
|
if (isExactContextFileListProject(contextProject)) {
|
|
203
204
|
setContextSyncState('exact-ready');
|
|
205
|
+
onProjectFilesReady(contextProject);
|
|
204
206
|
} else if (contextSyncState !== 'partial') {
|
|
205
207
|
setContextSyncState('exact-loading');
|
|
206
208
|
void enhanceContextFilesFromExactSnapshot({ loadId: contextLoadId });
|
|
@@ -224,6 +226,7 @@
|
|
|
224
226
|
contextProject = project;
|
|
225
227
|
setContextSyncState(isExactContextFileListProject(project) ? 'exact-ready' : 'exact-loading');
|
|
226
228
|
renderContextFiles(project);
|
|
229
|
+
if (isExactContextFileListProject(project)) onProjectFilesReady(project);
|
|
227
230
|
if (!isExactContextFileListProject(project)) {
|
|
228
231
|
void enhanceContextFilesFromExactSnapshot({
|
|
229
232
|
force: Boolean(options.force),
|
|
@@ -292,6 +295,7 @@
|
|
|
292
295
|
contextProject = project;
|
|
293
296
|
setContextSyncState('exact-ready');
|
|
294
297
|
renderContextFiles(project);
|
|
298
|
+
onProjectFilesReady(project);
|
|
295
299
|
} catch (_error) {
|
|
296
300
|
// The page file tree remains usable when the optional exact ZIP list is unavailable.
|
|
297
301
|
if (loadId === contextLoadId) {
|
|
@@ -359,7 +359,7 @@
|
|
|
359
359
|
if (!options.collectOnly) {
|
|
360
360
|
showDiagnosticsLoading(tr('diagnosticsOtTitle'), tr('diagnosticsLoading'));
|
|
361
361
|
}
|
|
362
|
-
// OT warm mirror is
|
|
362
|
+
// OT warm mirror is opt-in and off by default. When it is disabled
|
|
363
363
|
// there is nothing live to probe, so report a clean, healthy "disabled"
|
|
364
364
|
// state instead of querying (then warning about) absent OT metadata — or
|
|
365
365
|
// degrading to the generic "could not run" error if a probe throws. A
|
|
@@ -374,6 +374,14 @@
|
|
|
374
374
|
nextStep: '',
|
|
375
375
|
technical: ''
|
|
376
376
|
};
|
|
377
|
+
const mirrorState = getOtWarmMirrorState?.() || {};
|
|
378
|
+
if (mirrorState.failureActive && mirrorState.lastFailure) {
|
|
379
|
+
const failure = mirrorState.lastFailure;
|
|
380
|
+
disabledResult.status = 'warning';
|
|
381
|
+
disabledResult.summary = tr(otWarmMirrorController.getFailureMessageKey(failure.code));
|
|
382
|
+
disabledResult.nextStep = tr('otFailureNextStep');
|
|
383
|
+
disabledResult.technical = `lastErrorCode: ${failure.code}\nphase: ${failure.phase}\nfailedAt: ${new Date(failure.at).toISOString()}`;
|
|
384
|
+
}
|
|
377
385
|
if (options.collectOnly) {
|
|
378
386
|
return disabledResult;
|
|
379
387
|
}
|
|
@@ -434,6 +442,7 @@
|
|
|
434
442
|
const queuedEventCount = normalizeOtDiagnosticsCount(otStatus?.queuedEventCount);
|
|
435
443
|
const lastEventAt = formatOtDiagnosticValue(otStatus?.lastEventAt, tr('noneValue'));
|
|
436
444
|
const warmMirrorState = getOtWarmMirrorState() || {};
|
|
445
|
+
const failureActive = warmMirrorState.failureActive === true;
|
|
437
446
|
const lastOtPatchAt = formatOtDiagnosticValue(mirrorStatus?.lastOtPatchAt || warmMirrorState.lastPatchAt, tr('noneValue'));
|
|
438
447
|
const lastOtErrorCode = formatOtDiagnosticValue(mirrorStatus?.lastOtErrorCode || warmMirrorState.lastErrorCode, tr('noneValue'));
|
|
439
448
|
const lastErrorCode = formatOtDiagnosticValue(otStatus?.lastErrorCode || otStatus?.reason || otStatus?.error, tr('noneValue'));
|
|
@@ -445,13 +454,14 @@
|
|
|
445
454
|
const bridgeFailed = otStatus?.ok === false;
|
|
446
455
|
const focusFiles = getActiveFocusFiles();
|
|
447
456
|
const otWarmStart = otWarmMirrorController?.canUseOtWarmStart?.({ enabled, focusFiles, mirrorStatus }) || { ok: false };
|
|
448
|
-
const fallback = !enabled || statusValue !== 'observing' || bridgeFailed || otWarmStart?.ok !== true;
|
|
457
|
+
const fallback = !enabled || failureActive || statusValue !== 'observing' || bridgeFailed || otWarmStart?.ok !== true;
|
|
449
458
|
|
|
450
459
|
return {
|
|
451
460
|
title: tr('diagnosticsOtTitle'),
|
|
452
461
|
subtitle: tr('diagnosticsOtSubtitle'),
|
|
453
462
|
status: enabled && !fallback && !bridgeFailed ? 'completed' : (enabled || bridgeFailed ? 'warning' : 'completed'),
|
|
454
|
-
summary: tr(
|
|
463
|
+
summary: tr(failureActive ? otWarmMirrorController.getFailureMessageKey(warmMirrorState.lastErrorCode)
|
|
464
|
+
: enabled ? 'diagnosticsOtSummaryEnabled' : 'diagnosticsOtSummaryDisabled'),
|
|
455
465
|
bullets: [
|
|
456
466
|
`${tr('otStatus')}: ${formatOtStatusLabel(statusValue)}`,
|
|
457
467
|
`${tr('otFreshFiles')}: ${otFreshFileCount}`,
|
|
@@ -631,6 +641,10 @@
|
|
|
631
641
|
lines.push(`CodeMirror: docLength=${view.docLength || 0}; dispatch=${Boolean(view.hasDispatch)}; source=${view.source || 'unknown'}`);
|
|
632
642
|
}
|
|
633
643
|
}
|
|
644
|
+
const capture = probe?.trackedChangeCapture;
|
|
645
|
+
if (capture) lines.push(tx('Tracked ledger: ', '留痕记录:')
|
|
646
|
+
+ 'source=' + capture.source + '; ready=' + capture.ready + '; count=' + capture.count
|
|
647
|
+
+ '; doc=' + (capture.docId || 'none') + '; adapter=' + capture.adapter + '; reason=' + (capture.reason || 'none'));
|
|
634
648
|
const projectDiagnostics = probe?.projectDiagnostics;
|
|
635
649
|
if (projectDiagnostics) {
|
|
636
650
|
lines.push(`Project records: ${projectDiagnostics.docRecordCount || 0}; roots=${(projectDiagnostics.internalRootKeys || []).slice(0, 6).join(', ') || 'none'}`);
|