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
|
@@ -392,54 +392,45 @@
|
|
|
392
392
|
: result;
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
-
async function
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
395
|
+
async function confirmNoTraceUndoEditing() {
|
|
396
|
+
let changed = false;
|
|
397
|
+
let toggle = { ok: true, changed: false, enabled: false };
|
|
398
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
399
|
+
if (!isEditingConfirmedForNoTraceUndo(getReviewingState({}))) {
|
|
400
|
+
toggle = await setReviewingEnabled(false, { waitMs: 1800 });
|
|
401
|
+
if (!toggle.ok) return toggle;
|
|
402
|
+
changed = changed || toggle.changed === true;
|
|
403
|
+
}
|
|
404
|
+
await delay(180);
|
|
405
|
+
if (!isEditingConfirmedForNoTraceUndo(getReviewingState({}))) continue;
|
|
406
|
+
await delay(180);
|
|
407
|
+
if (isEditingConfirmedForNoTraceUndo(getReviewingState({})))
|
|
408
|
+
return { ...toggle, ok: true, changed, enabled: false };
|
|
409
|
+
}
|
|
410
|
+
return { ok: false, code: 'editing_not_confirmed',
|
|
411
|
+
reason: 'Overleaf Editing mode did not remain confirmed before undo; no undo write was started.' };
|
|
412
|
+
}
|
|
414
413
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
414
|
+
async function applyOperationsWithNoTraceUndo(operations, options = {}) {
|
|
415
|
+
const disabled = await confirmNoTraceUndoEditing();
|
|
416
|
+
if (!disabled.ok) return buildNoTraceUndoBlockedResult(operations, disabled);
|
|
417
|
+
const result = await applyOperationsCore(operations, {
|
|
418
|
+
...options, noTraceUndo: true, trackReviewingChanges: false
|
|
419
|
+
});
|
|
420
|
+
const reviewingPolicy = { policy: 'no-trace-undo', disabled: disabled.changed === true, restored: false,
|
|
421
|
+
reason: disabled.changed ? 'left_editing_after_undo' : 'editing_already_confirmed' };
|
|
422
|
+
if (disabled.changed) {
|
|
423
|
+
reviewingPolicy.leftEditing = true;
|
|
424
|
+
reviewingPolicy.disable = summarizeReviewingToggleResult(disabled);
|
|
424
425
|
}
|
|
425
|
-
|
|
426
|
-
return {
|
|
427
|
-
...result,
|
|
428
|
-
reviewingPolicy: {
|
|
429
|
-
policy: 'no-trace-undo',
|
|
430
|
-
disabled: true,
|
|
431
|
-
restored: false,
|
|
432
|
-
leftEditing: true,
|
|
433
|
-
reason: 'left_editing_after_undo',
|
|
434
|
-
disable: summarizeReviewingToggleResult(disabled)
|
|
435
|
-
}
|
|
436
|
-
};
|
|
426
|
+
return { ...result, reviewingPolicy };
|
|
437
427
|
}
|
|
438
428
|
|
|
439
429
|
async function applyOperationsCore(operations, options = {}) {
|
|
440
430
|
const applied = [];
|
|
441
431
|
const skipped = [];
|
|
442
432
|
const trackedChanges = [];
|
|
433
|
+
const trackedChangeCaptures = [];
|
|
443
434
|
const safeBaseFiles = normalizeBaseFilesForSafety(options.baseFiles);
|
|
444
435
|
const baseFileLookup = window.CodexOverleafStaleGuard?.buildBaseFileLookup(safeBaseFiles);
|
|
445
436
|
const baseBinaryFileLookup = buildBaseBinaryFileLookup(safeBaseFiles);
|
|
@@ -574,8 +565,10 @@
|
|
|
574
565
|
let raceResult;
|
|
575
566
|
if (operation.type === 'edit') {
|
|
576
567
|
raceResult = await raceOpAgainstCancellation(applyEditOperation(operation, {
|
|
577
|
-
baseFileLookup,
|
|
578
|
-
|
|
568
|
+
baseFileLookup, runProjectId,
|
|
569
|
+
recheckWriteProject: writeGuardSurface ? () => writeGuardSurface.runWriteGuard({ runProjectId }) : null,
|
|
570
|
+
trackReviewingChanges: options.trackReviewingChanges === true,
|
|
571
|
+
noTraceUndo: options.noTraceUndo === true
|
|
579
572
|
}));
|
|
580
573
|
} else if (['binary-create', 'overwrite-binary'].includes(operation.type)) {
|
|
581
574
|
raceResult = await raceOpAgainstCancellation(applyBinaryAssetOperation(operation, { baseFileLookup, baseBinaryFileLookup }));
|
|
@@ -604,6 +597,7 @@
|
|
|
604
597
|
break;
|
|
605
598
|
}
|
|
606
599
|
const result = raceResult;
|
|
600
|
+
if (result.trackedChangeCapture) trackedChangeCaptures.push(result.trackedChangeCapture);
|
|
607
601
|
if (operation.type === 'edit' && result.ok && Array.isArray(result.trackedChanges)) {
|
|
608
602
|
trackedChanges.push(...result.trackedChanges);
|
|
609
603
|
}
|
|
@@ -618,7 +612,8 @@
|
|
|
618
612
|
ok: skipped.length === 0,
|
|
619
613
|
applied,
|
|
620
614
|
skipped,
|
|
621
|
-
trackedChanges: mergeTrackedChangeRefs(trackedChanges)
|
|
615
|
+
trackedChanges: trackedChangeCaptures.some(capture => capture.state !== 'observed') ? [] : mergeTrackedChangeRefs(trackedChanges),
|
|
616
|
+
trackedChangeCaptures
|
|
622
617
|
};
|
|
623
618
|
}
|
|
624
619
|
|
|
@@ -653,12 +648,17 @@
|
|
|
653
648
|
return editorReady;
|
|
654
649
|
}
|
|
655
650
|
|
|
651
|
+
if (options.noTraceUndo === true) {
|
|
652
|
+
const editing = await confirmNoTraceUndoEditing();
|
|
653
|
+
if (!editing.ok) return buildNoTraceUndoBlockedResult([operation], editing).skipped[0].result;
|
|
654
|
+
}
|
|
656
655
|
const trackReviewingChanges = options.trackReviewingChanges === true;
|
|
657
656
|
const forbidTrackedChanges = options.forbidTrackedChanges === true;
|
|
658
657
|
const observeTrackedChanges = trackReviewingChanges || forbidTrackedChanges;
|
|
659
658
|
const operationPaths = collectOperationPaths([operation]);
|
|
659
|
+
const captureBaseline = trackReviewingChanges ? prepareTrackedChangeCapture(operation.path) : null;
|
|
660
660
|
const trackedBefore = observeTrackedChanges
|
|
661
|
-
? collectTrackedChangeRefsForPaths(operationPaths)
|
|
661
|
+
? (captureBaseline?.refs || collectTrackedChangeRefsForPaths(operationPaths))
|
|
662
662
|
: [];
|
|
663
663
|
let current = editorReady.text;
|
|
664
664
|
let freshness = window.CodexOverleafStaleGuard?.checkOperationFreshness(
|
|
@@ -752,6 +752,25 @@
|
|
|
752
752
|
};
|
|
753
753
|
}
|
|
754
754
|
|
|
755
|
+
if (options.noTraceUndo === true) {
|
|
756
|
+
const blocked = typeof options.recheckWriteProject === 'function'
|
|
757
|
+
? await options.recheckWriteProject()
|
|
758
|
+
: { ok: false, code: 'editor_project_id_unavailable',
|
|
759
|
+
failure: buildPageFailure('editor_project_id_unavailable', { file: operation.path,
|
|
760
|
+
operationType: 'undo', changedDocument: false,
|
|
761
|
+
userMessage: 'The project identity guard is unavailable; the undo write was not applied.' }) };
|
|
762
|
+
if (blocked) return blocked.skipped?.[0]?.result || blocked;
|
|
763
|
+
if (getActiveFilePath() !== operation.path || readActiveEditorText() !== current) {
|
|
764
|
+
const reason = 'The editor changed while confirming Editing mode; the undo write was not applied.';
|
|
765
|
+
return { ok: false, code: 'stale_source_changed', reason,
|
|
766
|
+
failure: buildPageFailure('stale_source_changed', { file: operation.path,
|
|
767
|
+
operationType: 'undo', changedDocument: false, userMessage: reason }) };
|
|
768
|
+
}
|
|
769
|
+
if (!isEditingConfirmedForNoTraceUndo(getReviewingState({})))
|
|
770
|
+
return buildNoTraceUndoBlockedResult([operation], { ok: false, code: 'editing_not_confirmed',
|
|
771
|
+
reason: 'Overleaf left Editing mode before the undo write.' }).skipped[0].result;
|
|
772
|
+
}
|
|
773
|
+
|
|
755
774
|
const result = Array.isArray(operation.patches) && operation.patches.length
|
|
756
775
|
? replaceActiveEditorPatches(operation.patches, nextContent)
|
|
757
776
|
: replaceActiveEditorText(nextContent);
|
|
@@ -784,6 +803,7 @@
|
|
|
784
803
|
return decorated;
|
|
785
804
|
}
|
|
786
805
|
const trackedChanges = [];
|
|
806
|
+
let trackedChangeCapture = null;
|
|
787
807
|
if (observeTrackedChanges) {
|
|
788
808
|
// Overleaf renders review markers asynchronously after the editor text
|
|
789
809
|
// has already changed. A single 120 ms snapshot intermittently missed
|
|
@@ -791,10 +811,13 @@
|
|
|
791
811
|
// Poll both paths: normal Reviewing writes get a shorter capture window,
|
|
792
812
|
// while Accept replay keeps the longer safety window used to prove that
|
|
793
813
|
// it did not create fresh tracked changes.
|
|
794
|
-
const trackedDiff =
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
814
|
+
const trackedDiff = trackReviewingChanges
|
|
815
|
+
? await captureTrackedWrite({ trackedBefore, captureBaseline, operation, beforeContent: current,
|
|
816
|
+
postContent: nextContent, runProjectId: options.runProjectId })
|
|
817
|
+
: await waitForTrackedChangeDiff(trackedBefore, operationPaths, {
|
|
818
|
+
waitMs: 3600, intervalMs: 180, stopOnFirst: true
|
|
819
|
+
});
|
|
820
|
+
trackedChangeCapture = trackedDiff.capture || null;
|
|
798
821
|
trackedChanges.push(...trackedDiff.trackedChanges);
|
|
799
822
|
if (forbidTrackedChanges && trackedChanges.length > 0) {
|
|
800
823
|
return {
|
|
@@ -831,6 +854,7 @@
|
|
|
831
854
|
...result,
|
|
832
855
|
verified: true,
|
|
833
856
|
verifiedContent: nextContent,
|
|
857
|
+
trackedChangeCapture,
|
|
834
858
|
trackedChanges: mergeTrackedChangeRefs(trackedChanges)
|
|
835
859
|
};
|
|
836
860
|
}
|
|
@@ -1352,6 +1376,8 @@
|
|
|
1352
1376
|
}
|
|
1353
1377
|
|
|
1354
1378
|
async function applyFileTreeOperation(operation, options = {}) {
|
|
1379
|
+
const initialProjectId = treeOperations.getProjectId?.();
|
|
1380
|
+
const initialCancellationSequence = readWriteCancellationSequence();
|
|
1355
1381
|
const freshness = await checkFileTreeOperationFreshness(operation, options.baseFileLookup);
|
|
1356
1382
|
if (!freshness.ok) {
|
|
1357
1383
|
return freshness;
|
|
@@ -1400,8 +1426,21 @@
|
|
|
1400
1426
|
}
|
|
1401
1427
|
}
|
|
1402
1428
|
|
|
1429
|
+
const nativeOperation = { create: deps.createTextFile, delete: deps.deleteTextFile }[operation.type];
|
|
1430
|
+
if (typeof nativeOperation === 'function' && (operation.type === 'delete' || typeof operation.content === 'string')) {
|
|
1431
|
+
const isCurrent = () => Boolean(initialProjectId) && treeOperations.getProjectId?.() === initialProjectId
|
|
1432
|
+
&& readWriteCancellationSequence() === initialCancellationSequence;
|
|
1433
|
+
const created = await nativeOperation(operation, { isCurrent, expectedContent: options.baseFileLookup?.get(operation.path) });
|
|
1434
|
+
if (!created.ok) return created;
|
|
1435
|
+
if (!isCurrent() || created.verified !== true || created.verification !== 'overleaf-zip') {
|
|
1436
|
+
return { ...fileTreeVerificationFailed(operation, 'Server receipt is missing or stale.'), changedDocument: true };
|
|
1437
|
+
}
|
|
1438
|
+
recordFileTreeOperationSuccess(operation, options.baseFileLookup);
|
|
1439
|
+
return { ok: true, method: created.method, verified: true };
|
|
1440
|
+
}
|
|
1403
1441
|
return {
|
|
1404
1442
|
ok: false,
|
|
1443
|
+
code: 'file_tree_controls_unavailable',
|
|
1405
1444
|
reason: 'No supported Overleaf file-tree method was detected'
|
|
1406
1445
|
};
|
|
1407
1446
|
}
|
|
@@ -1847,9 +1886,11 @@
|
|
|
1847
1886
|
acceptTrackedChanges,
|
|
1848
1887
|
rejectTrackedChanges,
|
|
1849
1888
|
collectTrackedChangeRefsForPaths,
|
|
1889
|
+
prepareTrackedChangeCapture, getTrackedChangeCaptureStatus,
|
|
1890
|
+
captureTrackedWrite, reconcileTrackedChangeCapture,
|
|
1850
1891
|
waitForTrackedChangeDiff
|
|
1851
1892
|
} = trackedChangesLifecycleModule.create({
|
|
1852
|
-
window,
|
|
1893
|
+
window, confirmReviewWriteback: deps.confirmReviewWriteback,
|
|
1853
1894
|
checkWritebackRunProjectId,
|
|
1854
1895
|
buildNoTraceUndoBlockedResult,
|
|
1855
1896
|
buildReviewingRequiredBlockedResult,
|
|
@@ -1868,6 +1909,9 @@
|
|
|
1868
1909
|
buildPageFailure,
|
|
1869
1910
|
delay,
|
|
1870
1911
|
getActiveFilePath,
|
|
1912
|
+
getProjectId: () => treeOperations.getProjectId?.() || '',
|
|
1913
|
+
getCodeMirrorEditorView: deps.getCodeMirrorEditorView,
|
|
1914
|
+
getTrackedChangeDocumentId: deps.getTrackedChangeDocumentId,
|
|
1871
1915
|
getReviewingState,
|
|
1872
1916
|
isEditorUndoControl,
|
|
1873
1917
|
isInsideCodexPanel,
|
|
@@ -1885,6 +1929,8 @@
|
|
|
1885
1929
|
|
|
1886
1930
|
return {
|
|
1887
1931
|
applyOperations: applyOperationsForBridge,
|
|
1932
|
+
getTrackedChangeCaptureStatus,
|
|
1933
|
+
reconcileTrackedChangeCapture,
|
|
1888
1934
|
rejectTrackedChanges,
|
|
1889
1935
|
acceptTrackedChanges,
|
|
1890
1936
|
verifySaveState
|
|
@@ -67,7 +67,14 @@
|
|
|
67
67
|
treeOperations,
|
|
68
68
|
window
|
|
69
69
|
});
|
|
70
|
+
const textFileCreator = requirePageModule('CodexOverleafTextFileCreator').create({
|
|
71
|
+
window, document, treeOperations, snapshotRouter,
|
|
72
|
+
collectElements, ensureEditing, ensureReviewing, readActiveEditorText, readWriteCancellationSequence,
|
|
73
|
+
uploadHelpers: requirePageModule('CodexOverleafBinaryAssetUploader')
|
|
74
|
+
});
|
|
70
75
|
binaryAssetUploader = requirePageModule('CodexOverleafBinaryAssetUploader').create({
|
|
76
|
+
prepareUploadParent: textFileCreator.prepareUploadParent,
|
|
77
|
+
findFolderNode: textFileCreator.findFolderNode,
|
|
71
78
|
document,
|
|
72
79
|
snapshotRouter,
|
|
73
80
|
treeOperations,
|
|
@@ -86,6 +93,11 @@
|
|
|
86
93
|
window
|
|
87
94
|
});
|
|
88
95
|
writebackRouter = requirePageModule('CodexOverleafWritebackRouter').create({
|
|
96
|
+
confirmReviewWriteback: (params, result) => requirePageModule('CodexOverleafSaveState').confirmReviewWriteback({
|
|
97
|
+
params, result, getProjectId, delay, readSnapshot: options => snapshotRouter.fetchProjectZipSnapshot(options)
|
|
98
|
+
}),
|
|
99
|
+
createTextFile: textFileCreator.createFile,
|
|
100
|
+
deleteTextFile: textFileCreator.deleteFile,
|
|
89
101
|
activeEditorIdentityChanged,
|
|
90
102
|
clickNode,
|
|
91
103
|
collectElements,
|
|
@@ -93,8 +105,14 @@
|
|
|
93
105
|
compileBridge,
|
|
94
106
|
delay,
|
|
95
107
|
diagnosticsRevision: PAGE_BRIDGE_INSTALL_REVISION,
|
|
96
|
-
|
|
97
|
-
|
|
108
|
+
getCodeMirrorEditorView,
|
|
109
|
+
getTrackedChangeDocumentId: filePath => {
|
|
110
|
+
const ids = new Set(collectDocRecords({ includeWindowGlobals: true })
|
|
111
|
+
.filter(record => record.path === filePath && typeof record.id === 'string' && record.id).map(record => record.id));
|
|
112
|
+
return ids.size === 1 ? Array.from(ids)[0] : '';
|
|
113
|
+
},
|
|
114
|
+
ensureEditing: params => textFileCreator.ensureWriteMode(false, params),
|
|
115
|
+
ensureReviewing: params => textFileCreator.ensureWriteMode(true, params),
|
|
98
116
|
// Cross-world cancel: the router's per-op loop polls this each iteration
|
|
99
117
|
// and aborts remaining ops when the value bumps. content-side cancel
|
|
100
118
|
// invokes pageBridge.cancelActiveWrite which calls bumpWriteCancellationSequence.
|
|
@@ -205,20 +223,28 @@
|
|
|
205
223
|
return { ok: true };
|
|
206
224
|
},
|
|
207
225
|
createCheckpoint: params => createCheckpoint(params.label),
|
|
208
|
-
ensureReviewing,
|
|
209
|
-
ensureEditing,
|
|
210
|
-
applyOperations: params =>
|
|
226
|
+
ensureReviewing: params => textFileCreator.ensureWriteMode(true, params),
|
|
227
|
+
ensureEditing: params => textFileCreator.ensureWriteMode(false, params),
|
|
228
|
+
applyOperations: async params => {
|
|
229
|
+
if (params.reviewingPolicy === 'no-trace-undo') {
|
|
230
|
+
const prepared = await textFileCreator.prepareEditor(params);
|
|
231
|
+
if (prepared.ok !== true) return { applied: [], changedDocument: false,
|
|
232
|
+
skipped: (params.operations || []).map(operation => ({ operation, result: prepared })) };
|
|
233
|
+
}
|
|
234
|
+
return applyOperations(params.operations || [], {
|
|
211
235
|
baseFiles: params.baseFiles || null,
|
|
212
236
|
reviewingPolicy: params.reviewingPolicy || '',
|
|
213
237
|
requireReviewing: params.requireReviewing === true,
|
|
214
238
|
requireEditing: params.requireEditing === true,
|
|
215
239
|
runProjectId: typeof params.runProjectId === 'string' ? params.runProjectId : ''
|
|
216
|
-
})
|
|
240
|
+
});
|
|
241
|
+
},
|
|
217
242
|
binaryUploadBegin: params => binaryAssetUploader.begin(params),
|
|
218
243
|
binaryUploadAppend: params => binaryAssetUploader.append(params),
|
|
219
244
|
binaryUploadCommit: params => binaryAssetUploader.commit(params),
|
|
220
245
|
binaryUploadAbort: params => binaryAssetUploader.abort(params),
|
|
221
246
|
jumpToPosition,
|
|
247
|
+
reconcileTrackedChangeCapture: params => writebackRouter.reconcileTrackedChangeCapture(params),
|
|
222
248
|
rejectTrackedChanges,
|
|
223
249
|
acceptTrackedChanges,
|
|
224
250
|
triggerCompile: params => compileBridge.triggerCompile(params),
|
|
@@ -430,6 +456,7 @@
|
|
|
430
456
|
editor,
|
|
431
457
|
capabilities: pageRpcContract.withCapabilityReport(collectPageCapabilities(editor)),
|
|
432
458
|
editorDiagnostics: buildEditorDiagnostics(editor),
|
|
459
|
+
trackedChangeCapture: writebackRouter.getTrackedChangeCaptureStatus?.(),
|
|
433
460
|
projectDiagnostics: {
|
|
434
461
|
internalRootKeys: collectInternalRootKeys(),
|
|
435
462
|
docRecordCount: docRecords.length,
|
|
@@ -559,40 +586,26 @@
|
|
|
559
586
|
reason: 'jumpToPosition requires a non-empty file path'
|
|
560
587
|
};
|
|
561
588
|
}
|
|
562
|
-
if (!projectPathExists(filePath)) {
|
|
563
|
-
return {
|
|
564
|
-
ok: false,
|
|
565
|
-
code: 'path_not_found',
|
|
566
|
-
reason: `Could not find ${filePath} in the Overleaf project`,
|
|
567
|
-
path: filePath,
|
|
568
|
-
failure: buildPageBridgeFailure('target_file_not_found', {
|
|
569
|
-
file: filePath,
|
|
570
|
-
operationType: 'jump',
|
|
571
|
-
changedDocument: false,
|
|
572
|
-
userMessage: `Codex could not find ${filePath} in this Overleaf project.`,
|
|
573
|
-
evidence: { originalCode: 'path_not_found', writeStarted: false }
|
|
574
|
-
})
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
|
|
578
589
|
const navigating = getActiveFilePath() !== filePath;
|
|
579
590
|
const previousEditorIdentity = navigating ? getActiveEditorIdentity() : null;
|
|
580
591
|
|
|
581
592
|
if (navigating) {
|
|
582
593
|
const opened = await openFileByPath(filePath);
|
|
583
594
|
if (!opened.ok) {
|
|
595
|
+
const missing = !projectPathExists(filePath);
|
|
596
|
+
const code = missing ? 'path_not_found' : 'file_open_failed';
|
|
584
597
|
return {
|
|
585
598
|
ok: false,
|
|
586
|
-
code
|
|
599
|
+
code,
|
|
587
600
|
reason: opened.reason || `Could not open ${filePath}`,
|
|
588
601
|
path: filePath,
|
|
589
|
-
failure: buildPageBridgeFailure('target_file_open_failed', {
|
|
602
|
+
failure: buildPageBridgeFailure(missing ? 'target_file_not_found' : 'target_file_open_failed', {
|
|
590
603
|
file: filePath,
|
|
591
604
|
operationType: 'jump',
|
|
592
605
|
changedDocument: false,
|
|
593
606
|
userMessage: `Codex could not open ${filePath} in Overleaf.`,
|
|
594
607
|
technicalMessage: opened.reason || '',
|
|
595
|
-
evidence: { originalCode:
|
|
608
|
+
evidence: { originalCode: code, writeStarted: false }
|
|
596
609
|
})
|
|
597
610
|
};
|
|
598
611
|
}
|
|
@@ -830,10 +843,35 @@
|
|
|
830
843
|
return current.node !== previous.node;
|
|
831
844
|
}
|
|
832
845
|
|
|
846
|
+
function readCurrentEditorMode() {
|
|
847
|
+
const selector = '.toolbar-editor .review-mode-switcher > .review-mode-switcher-toggle-button';
|
|
848
|
+
const controls = uniqueNodes(collectElements(selector, 16)).filter(node =>
|
|
849
|
+
String(node.className || '').split(/\s+/).includes('review-mode-switcher-toggle-button')
|
|
850
|
+
&& node.ownerDocument === document && !isInsideCodexPanel(node));
|
|
851
|
+
if (!controls.length) return '';
|
|
852
|
+
const modes = controls.filter(node => {
|
|
853
|
+
const style = window.getComputedStyle?.(node);
|
|
854
|
+
return node.isConnected !== false && !node.hidden && !node.closest?.('[hidden],[aria-hidden="true"]')
|
|
855
|
+
&& (node.getClientRects?.().length || 0) > 0 && style?.visibility !== 'hidden'
|
|
856
|
+
&& style?.display !== 'none' && style?.opacity !== '0';
|
|
857
|
+
}).map(node => {
|
|
858
|
+
const classes = String(node.className || '').split(/\s+/);
|
|
859
|
+
const values = [node.getAttribute('aria-label'),
|
|
860
|
+
node.querySelector?.('.review-mode-switcher-toggle-label')?.textContent]
|
|
861
|
+
.map(value => normalizeReviewingSignalText(value).toLowerCase())
|
|
862
|
+
.filter(value => value === 'editing' || value === 'reviewing');
|
|
863
|
+
for (const mode of ['editing', 'reviewing']) if (classes.includes(mode)) values.push(mode);
|
|
864
|
+
return values.length && values.every(mode => mode === values[0]) ? values[0] : 'unknown';
|
|
865
|
+
});
|
|
866
|
+
return modes.length && modes.every(mode => mode === modes[0]) ? modes[0] : 'unknown';
|
|
867
|
+
}
|
|
868
|
+
|
|
833
869
|
function getReviewingState(params = {}) {
|
|
834
870
|
const signals = collectReviewingSignals({ ...params, manualOverride: false });
|
|
871
|
+
const editorMode = readCurrentEditorMode();
|
|
835
872
|
return {
|
|
836
873
|
signals,
|
|
874
|
+
...(editorMode ? { editorMode } : {}),
|
|
837
875
|
reviewing: window.CodexOverleafReviewing.detectReviewingFromSignals(signals)
|
|
838
876
|
};
|
|
839
877
|
}
|
|
@@ -986,6 +1024,7 @@
|
|
|
986
1024
|
}
|
|
987
1025
|
|
|
988
1026
|
function isReviewingConfirmedForWrite(state = {}) {
|
|
1027
|
+
if (state.editorMode) return state.editorMode === 'reviewing';
|
|
989
1028
|
const reviewing = state.reviewing || {};
|
|
990
1029
|
if (!reviewing.ok || reviewing.status === 'manual-override') {
|
|
991
1030
|
return false;
|
|
@@ -1015,6 +1054,7 @@
|
|
|
1015
1054
|
}
|
|
1016
1055
|
|
|
1017
1056
|
function isEditingConfirmedForNoTraceUndo(state = {}) {
|
|
1057
|
+
if (state.editorMode) return state.editorMode === 'editing';
|
|
1018
1058
|
if (isReviewingConfirmedForWrite(state)) {
|
|
1019
1059
|
return false;
|
|
1020
1060
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
const MIN_NATIVE_VERSION = '1.0.0';
|
|
13
13
|
const MIN_COMPATIBLE_NATIVE_VERSION = '1.0.0';
|
|
14
14
|
const MIN_COMPATIBLE_EXTENSION_VERSION = '1.0.0';
|
|
15
|
-
const BUILD_TARGET_VERSION = '2.
|
|
15
|
+
const BUILD_TARGET_VERSION = '2.4.1';
|
|
16
16
|
const DEFAULT_CHROME_EXTENSION_ID = 'illdpneeeopfffmiepaejglgmhpmdhdc';
|
|
17
17
|
const REQUIRED_CAPABILITIES = Object.freeze([
|
|
18
18
|
'bridgePing',
|
|
@@ -508,14 +508,39 @@
|
|
|
508
508
|
failureReason_editor_project_id_unavailable_next: 'Refresh the Overleaf tab and retry; if it persists, reload the extension.',
|
|
509
509
|
// Welcome-panel + write-guard: Recent-projects
|
|
510
510
|
// variant copy. Spec §5.3–§5.5, §5.8, §5.10 are the source of truth.
|
|
511
|
-
recentProjects_welcome: '
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
511
|
+
recentProjects_welcome: 'Continue working',
|
|
512
|
+
recentProjects_focusEyebrow: 'Last conversation',
|
|
513
|
+
recentProjects_historyHeading: 'Other recent conversations',
|
|
514
|
+
recentProjects_historyHint: 'Select to continue',
|
|
515
|
+
recentProjects_projectConversations: "View this project's conversations",
|
|
516
|
+
recentProjects_allProjects: 'All projects',
|
|
517
|
+
recentProjects_showAllConversations: 'View all {count} conversations',
|
|
518
|
+
recentProjects_unstarted: 'Not started \u00b7 {count}',
|
|
519
|
+
recentProjects_unstartedHint: 'Empty conversations are kept here, without deleting their records.',
|
|
520
|
+
recentProjects_noStarted: 'No conversation has been started yet. Empty conversations are available below.',
|
|
521
|
+
recentProjects_savedDraft: 'Saved draft, ready to continue.',
|
|
522
|
+
recentProjects_attention: 'Needs attention',
|
|
523
|
+
recentProjects_unavailableRecords: 'Records with unavailable project links',
|
|
524
|
+
recentProjects_loadingHistory: 'Loading conversations...',
|
|
525
|
+
recentProjects_loadFailed: 'Conversation history could not be read. Please retry.',
|
|
526
|
+
recentProjects_continueHint: 'Open this conversation without automatically starting a task.',
|
|
527
|
+
recentProjects_more: 'More options',
|
|
528
|
+
recentProjects_continue: 'Continue',
|
|
529
|
+
recentProjects_openProject: 'Open project',
|
|
530
|
+
recentProjects_loadingConversation: 'Loading conversation...',
|
|
531
|
+
recentProjects_noConversation: 'No saved conversation',
|
|
532
|
+
recentProjects_noRuns: 'No runs yet',
|
|
533
|
+
recentProjects_previewUnavailable: 'Conversation unavailable',
|
|
534
|
+
recentProjects_previewUnavailableHint: 'Saved conversation could not be loaded.',
|
|
535
|
+
recentProjects_conversationGone: 'This conversation is no longer available. Choose another conversation from the refreshed list.',
|
|
536
|
+
recentProjects_continueFailed: 'Could not open the conversation. Please try again.',
|
|
537
|
+
recentProjects_welcome_subtitle: 'Conversations saved in this browser',
|
|
538
|
+
recentProjects_empty: 'No conversations saved in this browser yet. Open an Overleaf project and start a Codex conversation.',
|
|
539
|
+
recentProjects_sessions_empty: 'No saved conversations in this project',
|
|
540
|
+
recentProjects_sessions_toggle: "Show or manage this project's conversations",
|
|
541
|
+
recentProjects_sessions_action: 'All conversations',
|
|
542
|
+
recentProjects_sessions_action_open: 'Hide conversations',
|
|
543
|
+
recentProjects_clearProject: 'Delete this project\'s conversations',
|
|
519
544
|
recentProjects_clearProject_title: 'Delete all Codex sessions for {project}?',
|
|
520
545
|
recentProjects_clearProject_message: 'This permanently deletes all {count} plugin sessions, run history, and matching plugin-local Codex thread history for {project}. It will not modify Overleaf files or global Codex history.',
|
|
521
546
|
recentProjects_clearProject_confirm: 'Delete all',
|
|
@@ -531,15 +556,17 @@
|
|
|
531
556
|
recentProjects_degraded: 'Sign in to Overleaf to see your recent Codex projects.',
|
|
532
557
|
recentProjects_row_projectLinkUnavailable: 'Project link unavailable.',
|
|
533
558
|
recentProjects_settings_entry: 'Settings & Diagnostics',
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
559
|
+
recentProjects_settingsTitle: 'General Settings',
|
|
560
|
+
recentProjects_settingsSubtitle: 'Manage Codex preferences and local history across all projects.',
|
|
561
|
+
recentProjects_badge_pending: 'Last run pending',
|
|
562
|
+
recentProjects_badge_accepted: 'Last changes accepted',
|
|
563
|
+
recentProjects_badge_rejected: 'Last changes undone',
|
|
564
|
+
recentProjects_badge_needs_review: 'Last run needs review',
|
|
565
|
+
recentProjects_badge_running: 'Last recorded as running',
|
|
566
|
+
recentProjects_badge_completed: 'Last run completed',
|
|
567
|
+
recentProjects_badge_failed: 'Last run failed',
|
|
568
|
+
recentProjects_badge_interrupted: 'Last run may have been interrupted',
|
|
569
|
+
recentProjects_badge_stale: 'Last run status unconfirmed',
|
|
543
570
|
recentProjects_unnamed: 'Project \u00b7 {prefix}',
|
|
544
571
|
recentProjects_zombieRunningNote: 'Marked as running, but it may be a leftover from an interrupted run.',
|
|
545
572
|
recentProjects_zombieDeleteConfirm: 'This session is marked as running. If no other tab is actually running it, you can delete it anyway.',
|
|
@@ -547,9 +574,9 @@
|
|
|
547
574
|
recentProjects_degraded_hint: 'Could not identify the Overleaf account on this page, so the project list is hidden to avoid mixing accounts. This usually resolves on a reload.',
|
|
548
575
|
recentProjects_sessionsCount: '{count} session(s)',
|
|
549
576
|
recentProjects_welcome_stats: '{projects} projects \u00b7 {sessions} Codex sessions',
|
|
550
|
-
recentProjects_badge_background_completed: 'completed in background',
|
|
551
|
-
recentProjects_badge_needs_review_after_navigation: '
|
|
552
|
-
recentProjects_badge_abandoned_after_navigation: '
|
|
577
|
+
recentProjects_badge_background_completed: 'Last run completed in background',
|
|
578
|
+
recentProjects_badge_needs_review_after_navigation: 'Last run needs review',
|
|
579
|
+
recentProjects_badge_abandoned_after_navigation: 'Last run stopped after navigation'
|
|
553
580
|
},
|
|
554
581
|
zh: {
|
|
555
582
|
resizePanel: '拖动调整 Codex 面板宽度,双击恢复默认宽度',
|
|
@@ -1047,14 +1074,39 @@
|
|
|
1047
1074
|
failureReason_editor_project_id_unavailable_next: '请刷新 Overleaf 页面后重试;如果仍然失败,请重新加载扩展。',
|
|
1048
1075
|
// Welcome-panel + write-guard: Recent-projects
|
|
1049
1076
|
// variant copy. 见 spec §5.3–§5.5、§5.8、§5.10。
|
|
1050
|
-
recentProjects_welcome: '
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1077
|
+
recentProjects_welcome: '继续工作',
|
|
1078
|
+
recentProjects_focusEyebrow: '上次对话',
|
|
1079
|
+
recentProjects_historyHeading: '其他最近对话',
|
|
1080
|
+
recentProjects_historyHint: '点击条目继续',
|
|
1081
|
+
recentProjects_projectConversations: '查看该项目的全部对话',
|
|
1082
|
+
recentProjects_allProjects: '所有项目',
|
|
1083
|
+
recentProjects_showAllConversations: '查看全部 {count} 段对话',
|
|
1084
|
+
recentProjects_unstarted: '未开始 · {count}',
|
|
1085
|
+
recentProjects_unstartedHint: '空白对话收纳在这里,记录不会被删除。',
|
|
1086
|
+
recentProjects_noStarted: '尚无已开始的对话,空白对话可在下方展开。',
|
|
1087
|
+
recentProjects_savedDraft: '草稿已保存,可继续补充。',
|
|
1088
|
+
recentProjects_attention: '需要查看',
|
|
1089
|
+
recentProjects_unavailableRecords: '项目链接不可用的记录',
|
|
1090
|
+
recentProjects_loadingHistory: '正在加载对话…',
|
|
1091
|
+
recentProjects_loadFailed: '暂时无法读取对话历史,请重试。',
|
|
1092
|
+
recentProjects_continueHint: '仅打开这段对话,不会自动启动任务。',
|
|
1093
|
+
recentProjects_more: '更多选项',
|
|
1094
|
+
recentProjects_continue: '继续',
|
|
1095
|
+
recentProjects_openProject: '打开项目',
|
|
1096
|
+
recentProjects_loadingConversation: '正在加载对话…',
|
|
1097
|
+
recentProjects_noConversation: '暂无保存的对话',
|
|
1098
|
+
recentProjects_noRuns: '尚未运行任务',
|
|
1099
|
+
recentProjects_previewUnavailable: '暂时无法读取对话',
|
|
1100
|
+
recentProjects_previewUnavailableHint: '已保存的对话暂时无法读取。',
|
|
1101
|
+
recentProjects_conversationGone: '该对话已不可用,请从更新后的列表中选择其他对话。',
|
|
1102
|
+
recentProjects_continueFailed: '暂时无法打开对话,请重试。',
|
|
1103
|
+
recentProjects_welcome_subtitle: '当前浏览器保存的对话',
|
|
1104
|
+
recentProjects_empty: '当前浏览器还没有保存的对话。打开一个 Overleaf 项目,与 Codex 开始对话即可。',
|
|
1105
|
+
recentProjects_sessions_empty: '该项目还没有保存的对话',
|
|
1106
|
+
recentProjects_sessions_toggle: '查看或管理该项目的对话',
|
|
1107
|
+
recentProjects_sessions_action: '全部对话',
|
|
1108
|
+
recentProjects_sessions_action_open: '收起对话',
|
|
1109
|
+
recentProjects_clearProject: '删除该项目的全部对话',
|
|
1058
1110
|
recentProjects_clearProject_title: '删除 {project} 的全部 Codex 会话?',
|
|
1059
1111
|
recentProjects_clearProject_message: '这会永久删除 {project} 的全部 {count} 个插件会话、运行历史及对应的插件本地 Codex 线程历史。不会修改 Overleaf 文件或全局 Codex 历史。',
|
|
1060
1112
|
recentProjects_clearProject_confirm: '全部删除',
|
|
@@ -1070,15 +1122,17 @@
|
|
|
1070
1122
|
recentProjects_degraded: '请登录 Overleaf 后查看你最近的 Codex 项目。',
|
|
1071
1123
|
recentProjects_row_projectLinkUnavailable: '项目链接不可用。',
|
|
1072
1124
|
recentProjects_settings_entry: '设置与诊断',
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1125
|
+
recentProjects_settingsTitle: '通用设置',
|
|
1126
|
+
recentProjects_settingsSubtitle: '管理适用于所有项目的 Codex 偏好设置与本地历史记录。',
|
|
1127
|
+
recentProjects_badge_pending: '上次运行待处理',
|
|
1128
|
+
recentProjects_badge_accepted: '上次修改已接受',
|
|
1129
|
+
recentProjects_badge_rejected: '上次修改已撤回',
|
|
1130
|
+
recentProjects_badge_needs_review: '上次运行需要检查',
|
|
1131
|
+
recentProjects_badge_running: '上次记录为运行中',
|
|
1132
|
+
recentProjects_badge_completed: '上次运行已完成',
|
|
1133
|
+
recentProjects_badge_failed: '上次运行失败',
|
|
1134
|
+
recentProjects_badge_interrupted: '上次运行可能已中断',
|
|
1135
|
+
recentProjects_badge_stale: '上次运行状态未确认',
|
|
1082
1136
|
recentProjects_unnamed: '项目 · {prefix}',
|
|
1083
1137
|
recentProjects_zombieRunningNote: '标记为运行中,但可能是被中断后的残留。',
|
|
1084
1138
|
recentProjects_zombieDeleteConfirm: '该会话标记为运行中。如果确认没有其他标签页在运行它,可以直接删除。',
|
|
@@ -1086,9 +1140,9 @@
|
|
|
1086
1140
|
recentProjects_degraded_hint: '无法识别本页的 Overleaf 账号,为避免账号间串数据已隐藏项目列表。通常刷新即可恢复。',
|
|
1087
1141
|
recentProjects_sessionsCount: '{count} 个会话',
|
|
1088
1142
|
recentProjects_welcome_stats: '{projects} 个项目 · {sessions} 个 Codex 会话',
|
|
1089
|
-
recentProjects_badge_background_completed: '
|
|
1090
|
-
recentProjects_badge_needs_review_after_navigation: '
|
|
1091
|
-
recentProjects_badge_abandoned_after_navigation: '
|
|
1143
|
+
recentProjects_badge_background_completed: '上次运行已在后台完成',
|
|
1144
|
+
recentProjects_badge_needs_review_after_navigation: '上次运行需要检查',
|
|
1145
|
+
recentProjects_badge_abandoned_after_navigation: '上次运行已在离开页面后停止'
|
|
1092
1146
|
}
|
|
1093
1147
|
};
|
|
1094
1148
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
})(typeof window !== 'undefined' ? window : (typeof globalThis !== 'undefined' ? globalThis : this), function () {
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
-
var REVISION = '2026-
|
|
8
|
+
var REVISION = '2026-09-14-native-text-upload-v28';
|
|
9
9
|
var METHODS = freezeCatalog({
|
|
10
10
|
initializeCapability: entry('bridge.initialize', 'control', 'none', 'default', false, 'none'),
|
|
11
11
|
probe: entry('bridge.probe', 'read', 'none', 'default', false, 'safe'),
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
binaryUploadCommit: entry('asset.write', 'document', 'required', 'writeback', true, 'no_retry'),
|
|
23
23
|
binaryUploadAbort: entry('asset.stage', 'cache', 'none', 'default', false, 'idempotent'),
|
|
24
24
|
jumpToPosition: entry('editor.navigate', 'navigation', 'required', 'default', false, 'idempotent'),
|
|
25
|
+
reconcileTrackedChangeCapture: entry('tracked_changes.read', 'read', 'required', 'default', false, 'safe'),
|
|
25
26
|
rejectTrackedChanges: entry('tracked_changes.reject', 'document', 'required', 'lifecycle', true, 'no_retry'),
|
|
26
27
|
acceptTrackedChanges: entry('tracked_changes.accept', 'document', 'required', 'lifecycle', true, 'no_retry'),
|
|
27
28
|
triggerCompile: entry('compile.trigger', 'page_state', 'required', 'compile', false, 'no_retry'),
|