codex-overleaf-link 2.3.6 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -34
- package/README.zh-CN.md +546 -0
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +3 -0
- package/extension/src/content/activeTurnControl.js +1 -0
- package/extension/src/content/assetTransferBroker.js +2 -0
- package/extension/src/content/contentRuntime.js +122 -118
- package/extension/src/content/contextTray.js +4 -0
- package/extension/src/content/diagnosticsController.js +4 -0
- package/extension/src/content/diagnosticsPanel.js +128 -4
- package/extension/src/content/generated/content.bundle.js +1233 -550
- package/extension/src/content/generated/content.bundle.meta.json +4 -3
- package/extension/src/content/legacyGlobalRegistry.js +1 -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 +34 -30
- package/extension/src/content/otWarmMirrorController.js +20 -0
- package/extension/src/content/pageBridgeClient.js +2 -0
- package/extension/src/content/panelMaintenance.js +8 -4
- package/extension/src/content/recentProjects.js +354 -355
- package/extension/src/content/runResultActions.js +16 -1
- package/extension/src/content/runTimelineView.js +9 -11
- package/extension/src/content/scopedPersistenceCoordinator.js +2 -1
- package/extension/src/content/scopedPersistencePanelState.js +45 -2
- package/extension/src/content/sessionForkController.js +20 -11
- package/extension/src/content/sessionPersistence.js +4 -1
- package/extension/src/content/settingsPanel.js +16 -2
- package/extension/src/content/trackedChangeCaptureController.js +153 -0
- package/extension/src/content/writebackOrchestrator.js +31 -0
- package/extension/src/page/binaryAssetUploader.js +101 -29
- package/extension/src/page/overleafRealtimeObserver.js +4 -1
- package/extension/src/page/saveState.js +44 -1
- package/extension/src/page/textFileCreator.js +493 -0
- package/extension/src/page/trackedChangeCapture.js +342 -0
- package/extension/src/page/trackedChangesLifecycle.js +149 -150
- package/extension/src/page/treeOperations.js +40 -29
- package/extension/src/page/writebackRouter.js +95 -49
- package/extension/src/pageBridge.js +65 -25
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/i18n.js +94 -40
- package/extension/src/shared/pageRpcContract.js +2 -1
- package/extension/src/shared/sessionState.js +6 -2
- package/extension/src/shared/storageDb.js +11 -12
- package/extension/src/shared/storageRunActions.js +73 -1
- package/extension/src/shared/writebackSettlement.js +19 -5
- package/extension/styles/panel.css +254 -405
- package/native-host/src/codexSessionRunner.js +41 -38
- package/native-host/src/codexSessionTiming.js +68 -0
- package/native-host/src/readProgressGuard.js +84 -17
- package/native-host/src/taskRunnerRuntime.js +25 -0
- package/package.json +5 -1
|
@@ -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',
|
|
@@ -583,6 +582,7 @@
|
|
|
583
582
|
RUN_SNAPSHOT_ZIP_TIMEOUT_MS,
|
|
584
583
|
getState: () => state,
|
|
585
584
|
getCurrentRunView: () => currentRunView,
|
|
585
|
+
appendRunRecordEvent,
|
|
586
586
|
onMirrorRefreshSettled: runSettlementPersistence.settleMirrorRefresh
|
|
587
587
|
});
|
|
588
588
|
const {
|
|
@@ -790,6 +790,7 @@
|
|
|
790
790
|
},
|
|
791
791
|
saveState,
|
|
792
792
|
saveStateSoon,
|
|
793
|
+
onProjectFilesReady: refreshHistoricalProjectReferences,
|
|
793
794
|
updateActiveSession,
|
|
794
795
|
callPageBridge,
|
|
795
796
|
getCurrentProjectId,
|
|
@@ -1776,9 +1777,7 @@
|
|
|
1776
1777
|
return contextTrayController.setContextStatus(text);
|
|
1777
1778
|
}
|
|
1778
1779
|
|
|
1779
|
-
function resetContextProject() {
|
|
1780
|
-
return contextTrayController.resetContextProject();
|
|
1781
|
-
}
|
|
1780
|
+
function resetContextProject() { return contextTrayController.resetContextProject(); }
|
|
1782
1781
|
|
|
1783
1782
|
async function runTask(options = {}) {
|
|
1784
1783
|
// Barrier on a still-flying background mirror refresh from the previous
|
|
@@ -2485,14 +2484,15 @@
|
|
|
2485
2484
|
setRunning(false);
|
|
2486
2485
|
nativeChannel.clearActiveRequest();
|
|
2487
2486
|
stopRunElapsedTick();
|
|
2488
|
-
if (isExperimentalOtEnabled()) {
|
|
2489
|
-
await resumeOtWarmMirror('run-settled');
|
|
2490
|
-
}
|
|
2491
2487
|
await flushQueuedSaveState().catch(() => {});
|
|
2492
2488
|
currentRunView = null;
|
|
2493
2489
|
runCancellationRequested = false;
|
|
2494
2490
|
runCancellationController = null;
|
|
2495
2491
|
activeTurnControl.release(settlement.requestId);
|
|
2492
|
+
if (isExperimentalOtEnabled()) {
|
|
2493
|
+
try { await resumeOtWarmMirror('run-settled'); }
|
|
2494
|
+
catch (_error) { updateOtStatusDisplay('unavailable'); }
|
|
2495
|
+
}
|
|
2496
2496
|
if (settlement.requestId) {
|
|
2497
2497
|
activeTurnControl.acknowledge(settlement.requestId).catch(() => {});
|
|
2498
2498
|
}
|
|
@@ -5570,6 +5570,7 @@
|
|
|
5570
5570
|
title: session.title || '',
|
|
5571
5571
|
titleSource: session.titleSource || 'auto',
|
|
5572
5572
|
focusFiles: session.focusFiles || [],
|
|
5573
|
+
projectReferenceFiles: session.projectReferenceFiles || [],
|
|
5573
5574
|
codexThreadId: session.codexThreadId || '',
|
|
5574
5575
|
createdAt: session.createdAt,
|
|
5575
5576
|
updatedAt: session.updatedAt,
|
|
@@ -5665,7 +5666,7 @@
|
|
|
5665
5666
|
const urlProjectId = getCurrentProjectId();
|
|
5666
5667
|
const projectId = projectIdOverride || urlProjectId;
|
|
5667
5668
|
if (!projectId) {
|
|
5668
|
-
return;
|
|
5669
|
+
if (options?.reviewRunIds?.length) throw new Error('Review persistence lost its project.'); return;
|
|
5669
5670
|
}
|
|
5670
5671
|
// Read currentRunView through a defensive accessor so historical test
|
|
5671
5672
|
// harnesses that inline `saveState` without re-declaring the module-
|
|
@@ -5677,7 +5678,7 @@
|
|
|
5677
5678
|
'Skipped saveState: run is in flight on a different project than the current URL; settlement owns persistence.',
|
|
5678
5679
|
'已跳过 saveState:本次运行所属项目与当前 URL 不一致,写回由 settlement 负责。'
|
|
5679
5680
|
));
|
|
5680
|
-
return;
|
|
5681
|
+
if (options?.reviewRunIds?.length) throw new Error('Review persistence project changed.'); return;
|
|
5681
5682
|
}
|
|
5682
5683
|
const compactState = prepareStateForStorage(state, { onAggressive: notifyAggressiveCompactionOnce });
|
|
5683
5684
|
compactState.autoRecompile = state.autoRecompile;
|
|
@@ -5698,7 +5699,7 @@
|
|
|
5698
5699
|
state: stateSnapshot,
|
|
5699
5700
|
compactState: compactStateSnapshot,
|
|
5700
5701
|
projectId,
|
|
5701
|
-
deletedSessionIds: options?.deletedSessionIds,
|
|
5702
|
+
deletedSessionIds: options?.deletedSessionIds, reviewRunIds: options?.reviewRunIds,
|
|
5702
5703
|
Migration,
|
|
5703
5704
|
StorageDb,
|
|
5704
5705
|
SessionPersistence: Modules.SessionPersistence,
|
|
@@ -5726,7 +5727,7 @@
|
|
|
5726
5727
|
persistScopedState
|
|
5727
5728
|
);
|
|
5728
5729
|
if (!committed.ok && (
|
|
5729
|
-
options?.queueMutation
|
|
5730
|
+
options?.reviewRunIds?.length || options?.queueMutation
|
|
5730
5731
|
|| options?.queueMutations?.length
|
|
5731
5732
|
|| committed.reason !== 'stale_view'
|
|
5732
5733
|
)) {
|
|
@@ -5738,12 +5739,12 @@
|
|
|
5738
5739
|
} catch (error) {
|
|
5739
5740
|
if (
|
|
5740
5741
|
error?.code === 'account_scope_unavailable' &&
|
|
5741
|
-
!options?.queueMutation &&
|
|
5742
|
+
!options?.reviewRunIds?.length && !options?.queueMutation &&
|
|
5742
5743
|
!(Array.isArray(options?.queueMutations) && options.queueMutations.length)
|
|
5743
5744
|
) {
|
|
5744
5745
|
return;
|
|
5745
5746
|
}
|
|
5746
|
-
if (options?.queueMutation || options?.queueMutations?.length) {
|
|
5747
|
+
if (options?.reviewRunIds?.length || options?.queueMutation || options?.queueMutations?.length) {
|
|
5747
5748
|
throw error;
|
|
5748
5749
|
}
|
|
5749
5750
|
let fallbackError = null;
|
|
@@ -5868,6 +5869,7 @@
|
|
|
5868
5869
|
// here (e.g. fallback chrome.storage.local.set rethrew).
|
|
5869
5870
|
emitStorageQuotaFailure(error, null);
|
|
5870
5871
|
}
|
|
5872
|
+
if (persistenceOptions?.reviewRunIds?.length) throw error;
|
|
5871
5873
|
appendPlainLog(tx(`Failed to save session state: ${formatStateSaveError(error)}`, `保存会话状态失败:${formatStateSaveError(error)}`));
|
|
5872
5874
|
})
|
|
5873
5875
|
.finally(() => {
|
|
@@ -5922,6 +5924,7 @@
|
|
|
5922
5924
|
...(current || {}),
|
|
5923
5925
|
...incoming,
|
|
5924
5926
|
queueMutation: undefined,
|
|
5927
|
+
reviewRunIds: Array.from(new Set([...(current?.reviewRunIds || []), ...(incoming?.reviewRunIds || [])])),
|
|
5925
5928
|
queueMutations,
|
|
5926
5929
|
deletedSessionIds: Array.from(new Set([
|
|
5927
5930
|
...(current?.deletedSessionIds || []),
|
|
@@ -5977,15 +5980,7 @@
|
|
|
5977
5980
|
}
|
|
5978
5981
|
|
|
5979
5982
|
function getRenderedModelEntries() {
|
|
5980
|
-
return
|
|
5981
|
-
id: option.value,
|
|
5982
|
-
label: option.textContent,
|
|
5983
|
-
reasoningEfforts: (option.dataset.reasoningEfforts || '').split(',').filter(Boolean),
|
|
5984
|
-
defaultReasoningEffort: option.dataset.defaultReasoningEffort || '',
|
|
5985
|
-
reasoningPresentation: option.dataset.reasoningPresentation || '',
|
|
5986
|
-
speedTiers: (option.dataset.speedTiers || 'standard').split(',').filter(Boolean),
|
|
5987
|
-
defaultSpeedTier: option.dataset.defaultSpeedTier || 'standard'
|
|
5988
|
-
}));
|
|
5983
|
+
return Modules.ModelPickerSupport.getRenderedModelEntries(panel);
|
|
5989
5984
|
}
|
|
5990
5985
|
|
|
5991
5986
|
async function selectMode(mode) {
|
|
@@ -6123,6 +6118,7 @@
|
|
|
6123
6118
|
applySessionLabel();
|
|
6124
6119
|
renderSessionList();
|
|
6125
6120
|
renderRunHistory(options);
|
|
6121
|
+
getTrackedChangeCaptureController().schedule();
|
|
6126
6122
|
renderContextSelection();
|
|
6127
6123
|
renderContextSummary();
|
|
6128
6124
|
// v1.8.0 C4: refresh recovery — small attachments persisted in state are
|
|
@@ -6212,7 +6208,7 @@
|
|
|
6212
6208
|
undoExpectedFiles: [],
|
|
6213
6209
|
undoStatus: '',
|
|
6214
6210
|
queueItemId,
|
|
6215
|
-
nativeRequestId: '',
|
|
6211
|
+
nativeRequestId: '', codexThreadId: '',
|
|
6216
6212
|
codexTurnId: '',
|
|
6217
6213
|
nativeEventSeq: 0,
|
|
6218
6214
|
executionSnapshot: executionSnapshot
|
|
@@ -6379,7 +6375,7 @@
|
|
|
6379
6375
|
currentRunView.activeTurn = { threadId, turnId };
|
|
6380
6376
|
const record = findRunRecord(currentRunView.recordId, currentRunView.sessionId);
|
|
6381
6377
|
if (record) {
|
|
6382
|
-
record.codexTurnId = turnId;
|
|
6378
|
+
record.codexThreadId = threadId; record.codexTurnId = turnId;
|
|
6383
6379
|
record.nativeEventSeq = Math.max(Number(record.nativeEventSeq || 0), Number(journalSeq || 0));
|
|
6384
6380
|
}
|
|
6385
6381
|
if (threadId) {
|
|
@@ -6753,6 +6749,13 @@
|
|
|
6753
6749
|
return files;
|
|
6754
6750
|
}
|
|
6755
6751
|
|
|
6752
|
+
function refreshHistoricalProjectReferences(project) {
|
|
6753
|
+
if (!project?.id || String(project.id) !== getCurrentProjectId()) return;
|
|
6754
|
+
state = updateActiveSession(state, { projectReferenceFiles: captureProjectReferenceFiles(project) });
|
|
6755
|
+
saveStateSoon();
|
|
6756
|
+
if (!currentRunView && !trackedChangeInFlight.size) renderRunHistory({ preserveScroll: true });
|
|
6757
|
+
}
|
|
6758
|
+
|
|
6756
6759
|
function persistCurrentProjectReferenceFiles(project) {
|
|
6757
6760
|
const projectFiles = captureProjectReferenceFiles(project);
|
|
6758
6761
|
if (currentRunView) {
|
|
@@ -6884,6 +6887,7 @@
|
|
|
6884
6887
|
runProjectId: getRunProjectIdForWriteback(run)
|
|
6885
6888
|
});
|
|
6886
6889
|
const undoApplied = isUndoResultEffectivelyApplied(run, result);
|
|
6890
|
+
writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
|
|
6887
6891
|
appendUndoReviewingPolicyEvent(runId, result.reviewingPolicy);
|
|
6888
6892
|
appendRunRecordEvent(runId, {
|
|
6889
6893
|
title: tr('undoResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
|
|
@@ -6945,48 +6949,54 @@
|
|
|
6945
6949
|
detail: { [tr('detailWillUndo')]: trackedUndo ? formatTrackedChangeFiles(run.undoTrackedChanges) : formatTrackedUndoFiles(run) }
|
|
6946
6950
|
});
|
|
6947
6951
|
|
|
6948
|
-
let result;
|
|
6949
6952
|
try {
|
|
6950
|
-
|
|
6953
|
+
const trackedUndoPostFilesAtDispatch = buildTrackedUndoPostFiles(run);
|
|
6954
|
+
const result = await callPageBridge('rejectTrackedChanges', {
|
|
6951
6955
|
trackedChanges: run.undoTrackedChanges || [],
|
|
6952
6956
|
expectedFiles: run.undoExpectedFiles || [],
|
|
6953
|
-
postFiles:
|
|
6957
|
+
postFiles: trackedUndoPostFilesAtDispatch,
|
|
6958
|
+
untrackedUndo: run.executionSnapshot
|
|
6959
|
+
? run.executionSnapshot.requireReviewing === false && ['submitted', 'legacy-captured'].includes(run.executionSnapshot.source)
|
|
6960
|
+
: getTrackedChangeCaptureController().hasLegacyUntrackedCheckpoint(run),
|
|
6954
6961
|
// Welcome-panel + write-guard:
|
|
6955
6962
|
// bind the reject to the run's original project. If the user has
|
|
6956
6963
|
// navigated away the page-side guard refuses with
|
|
6957
6964
|
// `aborted_project_changed` and the document is left untouched.
|
|
6958
6965
|
runProjectId: getRunProjectIdForWriteback(run)
|
|
6959
6966
|
});
|
|
6967
|
+
writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
|
|
6968
|
+
appendRunRecordEvent(runId, {
|
|
6969
|
+
title: trackedUndo
|
|
6970
|
+
? tr('undoTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 })
|
|
6971
|
+
: tr('undoNativeResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
|
|
6972
|
+
status: result.skipped?.length ? 'failed' : 'completed',
|
|
6973
|
+
detail: {
|
|
6974
|
+
[trackedUndo ? tr('detailRejected') : tr('detailUndone')]: (result.applied || []).map(item => ({
|
|
6975
|
+
[tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
|
|
6976
|
+
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
|
|
6977
|
+
})),
|
|
6978
|
+
[tr('detailSkipped')]: (result.skipped || []).map(item => ({
|
|
6979
|
+
[tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
|
|
6980
|
+
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || '',
|
|
6981
|
+
[tr('detailReason')]: formatBridgeResultReason(item.result, item.trackedChange?.path)
|
|
6982
|
+
}))
|
|
6983
|
+
}
|
|
6984
|
+
});
|
|
6985
|
+
if (lifecycleReject) {
|
|
6986
|
+
WritebackSettlement.attachUndoNotVerifiedFailure(run, result, {
|
|
6987
|
+
buildFailure: buildContentFailure,
|
|
6988
|
+
postFiles: trackedUndoPostFilesAtDispatch
|
|
6989
|
+
});
|
|
6990
|
+
await applyTrackedChangeSettlement(runId, 'reject', result);
|
|
6991
|
+
return;
|
|
6992
|
+
}
|
|
6993
|
+
applyLegacyUndoSettlement(runId, result.skipped?.length ? 'partial' : 'applied', result);
|
|
6960
6994
|
} finally {
|
|
6961
6995
|
if (lifecycleReject) {
|
|
6962
6996
|
trackedChangeInFlight.delete(runId);
|
|
6997
|
+
refreshRunCardControls(runId);
|
|
6963
6998
|
}
|
|
6964
6999
|
}
|
|
6965
|
-
appendRunRecordEvent(runId, {
|
|
6966
|
-
title: trackedUndo
|
|
6967
|
-
? tr('undoTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 })
|
|
6968
|
-
: tr('undoNativeResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
|
|
6969
|
-
status: result.skipped?.length ? 'failed' : 'completed',
|
|
6970
|
-
detail: {
|
|
6971
|
-
[trackedUndo ? tr('detailRejected') : tr('detailUndone')]: (result.applied || []).map(item => ({
|
|
6972
|
-
[tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
|
|
6973
|
-
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
|
|
6974
|
-
})),
|
|
6975
|
-
[tr('detailSkipped')]: (result.skipped || []).map(item => ({
|
|
6976
|
-
[tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
|
|
6977
|
-
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || '',
|
|
6978
|
-
[tr('detailReason')]: formatBridgeResultReason(item.result, item.trackedChange?.path)
|
|
6979
|
-
}))
|
|
6980
|
-
}
|
|
6981
|
-
});
|
|
6982
|
-
if (lifecycleReject) {
|
|
6983
|
-
WritebackSettlement.attachUndoNotVerifiedFailure(run, result, {
|
|
6984
|
-
buildFailure: buildContentFailure
|
|
6985
|
-
});
|
|
6986
|
-
applyTrackedChangeSettlement(runId, 'reject', result);
|
|
6987
|
-
return;
|
|
6988
|
-
}
|
|
6989
|
-
applyLegacyUndoSettlement(runId, result.skipped?.length ? 'partial' : 'applied', result);
|
|
6990
7000
|
}
|
|
6991
7001
|
|
|
6992
7002
|
async function acceptRun(runId) {
|
|
@@ -7016,50 +7026,50 @@
|
|
|
7016
7026
|
detail: { [tr('detailAccepted')]: formatTrackedChangeFiles(run.undoTrackedChanges) }
|
|
7017
7027
|
});
|
|
7018
7028
|
|
|
7019
|
-
let result;
|
|
7020
7029
|
try {
|
|
7021
|
-
result = await callPageBridge('acceptTrackedChanges', {
|
|
7030
|
+
const result = await callPageBridge('acceptTrackedChanges', {
|
|
7022
7031
|
trackedChanges: run.undoTrackedChanges || [],
|
|
7023
7032
|
expectedFiles: run.undoExpectedFiles || [],
|
|
7024
7033
|
postFiles: buildTrackedUndoPostFiles(run),
|
|
7025
7034
|
appliedOperations: Array.isArray(run.appliedOperations) ? run.appliedOperations : [],
|
|
7026
7035
|
runProjectId: getRunProjectIdForWriteback(run)
|
|
7027
7036
|
});
|
|
7028
|
-
|
|
7029
|
-
trackedChangeInFlight.delete(runId);
|
|
7030
|
-
}
|
|
7031
|
-
appendAcceptDiagnosticEvents(runId, Array.isArray(result.diagnostics) ? result.diagnostics : []);
|
|
7032
|
-
appendRunRecordEvent(runId, {
|
|
7033
|
-
title: tr('runAcceptTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
|
|
7034
|
-
status: result.skipped?.length ? 'failed' : 'completed',
|
|
7035
|
-
detail: {
|
|
7036
|
-
[tr('detailAccepted')]: (result.applied || []).map(item => ({
|
|
7037
|
-
[tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
|
|
7038
|
-
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
|
|
7039
|
-
})),
|
|
7040
|
-
[tr('detailSkipped')]: (result.skipped || []).map(item => ({
|
|
7041
|
-
[tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
|
|
7042
|
-
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || '',
|
|
7043
|
-
[tr('detailReason')]: formatBridgeResultReason(item.result, item.trackedChange?.path)
|
|
7044
|
-
}))
|
|
7045
|
-
}
|
|
7046
|
-
});
|
|
7047
|
-
if (result.ok === false) {
|
|
7037
|
+
appendAcceptDiagnosticEvents(runId, Array.isArray(result.diagnostics) ? result.diagnostics : []);
|
|
7048
7038
|
appendRunRecordEvent(runId, {
|
|
7049
|
-
title: tr('
|
|
7050
|
-
status: 'failed',
|
|
7039
|
+
title: tr('runAcceptTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
|
|
7040
|
+
status: result.skipped?.length ? 'failed' : 'completed',
|
|
7051
7041
|
detail: {
|
|
7052
|
-
[tr('
|
|
7042
|
+
[tr('detailAccepted')]: (result.applied || []).map(item => ({
|
|
7043
|
+
[tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
|
|
7044
|
+
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
|
|
7045
|
+
})),
|
|
7046
|
+
[tr('detailSkipped')]: (result.skipped || []).map(item => ({
|
|
7047
|
+
[tr('detailFile')]: item.trackedChange?.path || tr('unknownFile'),
|
|
7048
|
+
[tr('detailRecord')]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || '',
|
|
7049
|
+
[tr('detailReason')]: formatBridgeResultReason(item.result, item.trackedChange?.path)
|
|
7050
|
+
}))
|
|
7053
7051
|
}
|
|
7054
7052
|
});
|
|
7055
|
-
if (
|
|
7056
|
-
|
|
7053
|
+
if (result.ok === false) {
|
|
7054
|
+
appendRunRecordEvent(runId, {
|
|
7055
|
+
title: tr('runAcceptTrackedFailed'),
|
|
7056
|
+
status: 'failed',
|
|
7057
|
+
detail: {
|
|
7058
|
+
[tr('detailReason')]: tr('runAcceptTrackedFailedReason')
|
|
7059
|
+
}
|
|
7060
|
+
});
|
|
7061
|
+
if (!WritebackSettlement.isSuccessfulTrackedChangeSettlement(result)) {
|
|
7062
|
+
refreshRunCardControls(runId);
|
|
7063
|
+
}
|
|
7057
7064
|
}
|
|
7065
|
+
WritebackSettlement.attachAcceptNotVerifiedFailure(run, result, {
|
|
7066
|
+
buildFailure: buildContentFailure
|
|
7067
|
+
});
|
|
7068
|
+
await applyTrackedChangeSettlement(runId, 'accept', result);
|
|
7069
|
+
} finally {
|
|
7070
|
+
trackedChangeInFlight.delete(runId);
|
|
7071
|
+
refreshRunCardControls(runId);
|
|
7058
7072
|
}
|
|
7059
|
-
WritebackSettlement.attachAcceptNotVerifiedFailure(run, result, {
|
|
7060
|
-
buildFailure: buildContentFailure
|
|
7061
|
-
});
|
|
7062
|
-
applyTrackedChangeSettlement(runId, 'accept', result);
|
|
7063
7073
|
}
|
|
7064
7074
|
|
|
7065
7075
|
function getRunProjectIdForWriteback(run) {
|
|
@@ -7103,7 +7113,7 @@
|
|
|
7103
7113
|
);
|
|
7104
7114
|
}
|
|
7105
7115
|
|
|
7106
|
-
function applyTrackedChangeSettlement(runId, kind, result) {
|
|
7116
|
+
async function applyTrackedChangeSettlement(runId, kind, result) {
|
|
7107
7117
|
const run = findRunRecord(runId);
|
|
7108
7118
|
const settlement = WritebackSettlement.settleTrackedChangeLifecycle({
|
|
7109
7119
|
kind,
|
|
@@ -7112,12 +7122,12 @@
|
|
|
7112
7122
|
failureReasons: FailureReasons
|
|
7113
7123
|
});
|
|
7114
7124
|
if (settlement.decision === 'blocked') {
|
|
7115
|
-
refreshRunCardControls(runId);
|
|
7116
7125
|
return;
|
|
7117
7126
|
}
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7127
|
+
await Modules.ScopedPersistenceCoordinator.applyReviewTransition({
|
|
7128
|
+
getState: () => state, runId, transition: WritebackSettlement.applySettlementTransition(run, settlement),
|
|
7129
|
+
persist: flushQueuedSaveState
|
|
7130
|
+
});
|
|
7121
7131
|
}
|
|
7122
7132
|
|
|
7123
7133
|
function getRunUndoCount(run) {
|
|
@@ -7195,6 +7205,25 @@
|
|
|
7195
7205
|
return WritebackSettlement.attachVerifiedContentToOperation(operation, result);
|
|
7196
7206
|
}
|
|
7197
7207
|
|
|
7208
|
+
let trackedChangeCaptureController = null;
|
|
7209
|
+
function getTrackedChangeCaptureController() {
|
|
7210
|
+
return trackedChangeCaptureController ||= Modules.TrackedChangeCaptureController.create({
|
|
7211
|
+
getRuns: () => (state.sessions || []).flatMap(session => session.runs || []),
|
|
7212
|
+
getProjectId: () => window.location.pathname.match(/^\/project\/([^/]+)/)?.[1] || '',
|
|
7213
|
+
buildExpectedFilesAfterOperations, normalizeTrackedChanges: normalizeApplyTrackedChanges, callPageBridge,
|
|
7214
|
+
persist: () => flushQueuedSaveState({ preserveRunActionPayload: true }),
|
|
7215
|
+
refresh: run => refreshRunCardControls(run.id),
|
|
7216
|
+
notify: (run, kind, detail) => appendRunRecordEvent(run.id, {
|
|
7217
|
+
title: {
|
|
7218
|
+
pending: tx('Files written; confirming tracked changes.', '文件已写入,正在确认留痕记录。'),
|
|
7219
|
+
observed: tx('Tracked changes confirmed; Accept is available.', '留痕记录已确认,可以接受改动。'),
|
|
7220
|
+
needs_review: tx('Tracked changes could not be safely attributed. Review them in Overleaf; Undo is preserved.', '留痕归属尚未确认,请在 Overleaf 审阅;原有撤销能力已保留。')
|
|
7221
|
+
}[kind], status: 'info', detail
|
|
7222
|
+
}),
|
|
7223
|
+
onError: error => appendPlainLog(tx('Tracked-change confirmation unavailable: ', '留痕确认暂不可用:') + error.message)
|
|
7224
|
+
});
|
|
7225
|
+
}
|
|
7226
|
+
|
|
7198
7227
|
function recordUndoFromApply(project, applyResult) {
|
|
7199
7228
|
const appliedEntries = getAppliedEntries(applyResult);
|
|
7200
7229
|
if (!currentRunView?.recordId || !appliedEntries.length) {
|
|
@@ -7238,6 +7267,7 @@
|
|
|
7238
7267
|
if (combinedTrackedChanges.length) {
|
|
7239
7268
|
record.trackedChangeStatus = 'pending';
|
|
7240
7269
|
}
|
|
7270
|
+
getTrackedChangeCaptureController().record(record, applyResult?.trackedChangeCaptures);
|
|
7241
7271
|
refreshRunCardControls(record.id);
|
|
7242
7272
|
if (combinedTrackedChanges.length) {
|
|
7243
7273
|
appendRunEvent({
|
|
@@ -7302,38 +7332,12 @@
|
|
|
7302
7332
|
return WritebackSettlement.normalizeApplyTrackedChanges(changes);
|
|
7303
7333
|
}
|
|
7304
7334
|
|
|
7305
|
-
function selectExpectedFilesForTrackedUndo(
|
|
7306
|
-
project,
|
|
7307
|
-
operations = [],
|
|
7308
|
-
trackedChanges = [],
|
|
7309
|
-
previousExpectedFiles = []
|
|
7310
|
-
) {
|
|
7311
|
-
return WritebackSettlement.selectExpectedFilesForTrackedUndo(
|
|
7312
|
-
project,
|
|
7313
|
-
operations,
|
|
7314
|
-
trackedChanges,
|
|
7315
|
-
previousExpectedFiles
|
|
7316
|
-
);
|
|
7335
|
+
function selectExpectedFilesForTrackedUndo(project, operations = [], trackedChanges = [], previousExpectedFiles = []) {
|
|
7336
|
+
return WritebackSettlement.selectExpectedFilesForTrackedUndo(project, operations, trackedChanges, previousExpectedFiles);
|
|
7317
7337
|
}
|
|
7318
7338
|
|
|
7319
7339
|
function buildTrackedUndoPostFiles(run) {
|
|
7320
|
-
|
|
7321
|
-
const appliedOperations = Array.isArray(run?.appliedOperations) ? run.appliedOperations : [];
|
|
7322
|
-
if (!expectedFiles.length || !appliedOperations.length) {
|
|
7323
|
-
return [];
|
|
7324
|
-
}
|
|
7325
|
-
|
|
7326
|
-
const postFilesByPath = buildExpectedFilesAfterOperations(
|
|
7327
|
-
{ files: expectedFiles },
|
|
7328
|
-
appliedOperations
|
|
7329
|
-
);
|
|
7330
|
-
const expectedPaths = new Set(expectedFiles.map(file => file?.path).filter(Boolean));
|
|
7331
|
-
return Array.from(postFilesByPath.entries())
|
|
7332
|
-
.filter(([path, content]) => expectedPaths.has(path) && typeof content === 'string')
|
|
7333
|
-
.map(([path, content]) => ({
|
|
7334
|
-
path,
|
|
7335
|
-
content
|
|
7336
|
-
}));
|
|
7340
|
+
return getTrackedChangeCaptureController().buildPostFiles(run);
|
|
7337
7341
|
}
|
|
7338
7342
|
|
|
7339
7343
|
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) {
|
|
@@ -641,6 +641,10 @@
|
|
|
641
641
|
lines.push(`CodeMirror: docLength=${view.docLength || 0}; dispatch=${Boolean(view.hasDispatch)}; source=${view.source || 'unknown'}`);
|
|
642
642
|
}
|
|
643
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'));
|
|
644
648
|
const projectDiagnostics = probe?.projectDiagnostics;
|
|
645
649
|
if (projectDiagnostics) {
|
|
646
650
|
lines.push(`Project records: ${projectDiagnostics.docRecordCount || 0}; roots=${(projectDiagnostics.internalRootKeys || []).slice(0, 6).join(', ') || 'none'}`);
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
container,
|
|
11
11
|
callbacks: options.callbacks || {},
|
|
12
12
|
i18n: options.i18n || {},
|
|
13
|
-
dismissInstalled: false
|
|
13
|
+
dismissInstalled: false,
|
|
14
|
+
cleanup: []
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
container.innerHTML = `
|
|
@@ -99,6 +100,11 @@
|
|
|
99
100
|
instance.callbacks.onBeforeOpen?.();
|
|
100
101
|
}
|
|
101
102
|
popover.hidden = !open;
|
|
103
|
+
if (open) {
|
|
104
|
+
openFloating(instance, popover, 300);
|
|
105
|
+
} else {
|
|
106
|
+
hideFloating(popover);
|
|
107
|
+
}
|
|
102
108
|
button.dataset.active = open ? 'true' : 'false';
|
|
103
109
|
button.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
104
110
|
}
|
|
@@ -110,7 +116,7 @@
|
|
|
110
116
|
if (!popover || !button) {
|
|
111
117
|
return;
|
|
112
118
|
}
|
|
113
|
-
popover
|
|
119
|
+
hideFloating(popover);
|
|
114
120
|
button.dataset.active = 'false';
|
|
115
121
|
button.setAttribute('aria-expanded', 'false');
|
|
116
122
|
}
|
|
@@ -119,7 +125,7 @@
|
|
|
119
125
|
const instance = target?._instance || target;
|
|
120
126
|
const result = instance?.container?.querySelector('[data-diagnostics-result]');
|
|
121
127
|
if (result) {
|
|
122
|
-
result
|
|
128
|
+
hideFloating(result);
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
|
|
@@ -181,6 +187,73 @@
|
|
|
181
187
|
details.open = false;
|
|
182
188
|
details.hidden = !technicalText;
|
|
183
189
|
technical.textContent = technicalText;
|
|
190
|
+
closeMenu(instance);
|
|
191
|
+
openFloating(instance, root, 560);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Keep DOM ownership (theme, locale and callbacks), but render above clipping
|
|
195
|
+
// ancestors where the browser supports the top layer. Fixed positioning is
|
|
196
|
+
// the viewport-based fallback for browsers without the Popover API.
|
|
197
|
+
function openFloating(instance, element, preferredWidth) {
|
|
198
|
+
if (element.isConnected === false) return;
|
|
199
|
+
element.hidden = false;
|
|
200
|
+
if (typeof element.showPopover === 'function') {
|
|
201
|
+
element.setAttribute('popover', 'manual');
|
|
202
|
+
try {
|
|
203
|
+
if (!element.matches(':popover-open')) element.showPopover();
|
|
204
|
+
} catch (_error) {
|
|
205
|
+
element.removeAttribute('popover');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
positionFloating(instance, element, preferredWidth);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function hideFloating(element) {
|
|
212
|
+
if (!element) return;
|
|
213
|
+
if (typeof element.hidePopover === 'function' && element.getAttribute('popover') === 'manual') {
|
|
214
|
+
try {
|
|
215
|
+
if (element.matches(':popover-open')) element.hidePopover();
|
|
216
|
+
} catch (_error) {
|
|
217
|
+
element.removeAttribute('popover');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
element.hidden = true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function positionFloating(instance, element, preferredWidth) {
|
|
224
|
+
if (!element || element.hidden) return;
|
|
225
|
+
const button = instance.container.querySelector('[data-diagnostics-menu]');
|
|
226
|
+
if (!button?.getBoundingClientRect || !element.getBoundingClientRect) return;
|
|
227
|
+
if (button.getClientRects && !button.getClientRects().length) {
|
|
228
|
+
closeMenu(instance);
|
|
229
|
+
closeResult(instance);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const doc = instance.container.ownerDocument || document;
|
|
233
|
+
const view = doc.defaultView || window;
|
|
234
|
+
const viewport = view.visualViewport;
|
|
235
|
+
const width = viewport?.width || view.innerWidth || doc.documentElement?.clientWidth;
|
|
236
|
+
const height = viewport?.height || view.innerHeight || doc.documentElement?.clientHeight;
|
|
237
|
+
if (!(width > 0 && height > 0)) return;
|
|
238
|
+
const margin = 12;
|
|
239
|
+
const gap = 8;
|
|
240
|
+
const leftEdge = (viewport?.offsetLeft || 0) + margin;
|
|
241
|
+
const topEdge = (viewport?.offsetTop || 0) + margin;
|
|
242
|
+
const rightEdge = leftEdge + Math.max(0, width - margin * 2);
|
|
243
|
+
const bottomEdge = topEdge + Math.max(0, height - margin * 2);
|
|
244
|
+
element.style.width = `${Math.min(preferredWidth, Math.max(0, width - margin * 2))}px`;
|
|
245
|
+
element.style.maxHeight = `${Math.min(640, Math.max(0, height - margin * 2))}px`;
|
|
246
|
+
const anchor = button.getBoundingClientRect();
|
|
247
|
+
const bounds = element.getBoundingClientRect();
|
|
248
|
+
const below = Math.max(0, bottomEdge - anchor.bottom - gap);
|
|
249
|
+
const above = Math.max(0, anchor.top - topEdge - gap);
|
|
250
|
+
const desiredTop = bounds.height > below && above > below
|
|
251
|
+
? anchor.top - gap - bounds.height
|
|
252
|
+
: anchor.bottom + gap;
|
|
253
|
+
element.style.left = `${Math.max(leftEdge, Math.min(anchor.right - bounds.width, rightEdge - bounds.width))}px`;
|
|
254
|
+
element.style.top = `${Math.max(topEdge, Math.min(desiredTop, bottomEdge - bounds.height))}px`;
|
|
255
|
+
element.style.right = 'auto';
|
|
256
|
+
element.style.bottom = 'auto';
|
|
184
257
|
}
|
|
185
258
|
|
|
186
259
|
function renderInstallCommand(instance, container, command) {
|
|
@@ -272,13 +345,60 @@
|
|
|
272
345
|
return;
|
|
273
346
|
}
|
|
274
347
|
instance.dismissInstalled = true;
|
|
275
|
-
|
|
348
|
+
const doc = instance.container.ownerDocument || document;
|
|
349
|
+
const view = doc.defaultView || window;
|
|
350
|
+
const listen = (target, type, handler, options) => {
|
|
351
|
+
if (!target?.addEventListener) return;
|
|
352
|
+
target.addEventListener(type, handler, options);
|
|
353
|
+
instance.cleanup.push(() => target.removeEventListener(type, handler, options));
|
|
354
|
+
};
|
|
355
|
+
listen(doc, 'click', event => {
|
|
276
356
|
const wrap = instance.container?.querySelector('.codex-diagnostics-wrap');
|
|
277
357
|
if (!wrap || wrap.contains(event.target)) {
|
|
278
358
|
return;
|
|
279
359
|
}
|
|
280
360
|
closeMenu(instance);
|
|
281
361
|
}, true);
|
|
362
|
+
listen(doc, 'keydown', event => {
|
|
363
|
+
if (event.key !== 'Escape') return;
|
|
364
|
+
const menu = instance.container.querySelector('[data-diagnostics-popover]');
|
|
365
|
+
const result = instance.container.querySelector('[data-diagnostics-result]');
|
|
366
|
+
if (menu && !menu.hidden) closeMenu(instance);
|
|
367
|
+
else if (result && !result.hidden) closeResult(instance);
|
|
368
|
+
else return;
|
|
369
|
+
event.preventDefault();
|
|
370
|
+
event.stopPropagation();
|
|
371
|
+
instance.container.querySelector('[data-diagnostics-menu]')?.focus();
|
|
372
|
+
}, true);
|
|
373
|
+
let frame = null;
|
|
374
|
+
const reposition = () => {
|
|
375
|
+
frame = null;
|
|
376
|
+
positionFloating(instance, instance.container.querySelector('[data-diagnostics-popover]'), 300);
|
|
377
|
+
positionFloating(instance, instance.container.querySelector('[data-diagnostics-result]'), 560);
|
|
378
|
+
};
|
|
379
|
+
const schedule = () => {
|
|
380
|
+
if (frame !== null) return;
|
|
381
|
+
if (view.requestAnimationFrame) frame = view.requestAnimationFrame(reposition);
|
|
382
|
+
else reposition();
|
|
383
|
+
};
|
|
384
|
+
listen(view, 'resize', schedule);
|
|
385
|
+
listen(doc, 'scroll', schedule, true);
|
|
386
|
+
listen(view.visualViewport, 'resize', schedule);
|
|
387
|
+
listen(view.visualViewport, 'scroll', schedule);
|
|
388
|
+
if (typeof view.ResizeObserver === 'function') {
|
|
389
|
+
const observer = new view.ResizeObserver(schedule);
|
|
390
|
+
for (const node of [
|
|
391
|
+
instance.container.closest('#codex-overleaf-panel') || instance.container,
|
|
392
|
+
instance.container.querySelector('[data-diagnostics-popover]'),
|
|
393
|
+
instance.container.querySelector('[data-diagnostics-result]')
|
|
394
|
+
]) {
|
|
395
|
+
if (node) observer.observe(node);
|
|
396
|
+
}
|
|
397
|
+
instance.cleanup.push(() => observer.disconnect());
|
|
398
|
+
}
|
|
399
|
+
instance.cleanup.push(() => {
|
|
400
|
+
if (frame !== null) view.cancelAnimationFrame?.(frame);
|
|
401
|
+
});
|
|
282
402
|
}
|
|
283
403
|
|
|
284
404
|
// Drive the trigger's health dot. `health` is one of ok / warn / fail /
|
|
@@ -450,6 +570,10 @@
|
|
|
450
570
|
}
|
|
451
571
|
|
|
452
572
|
function destroy(instance) {
|
|
573
|
+
closeMenu(instance);
|
|
574
|
+
closeResult(instance);
|
|
575
|
+
for (const cleanup of instance.cleanup || []) cleanup();
|
|
576
|
+
instance.cleanup = [];
|
|
453
577
|
instance.container.textContent = '';
|
|
454
578
|
}
|
|
455
579
|
|