codex-overleaf-link 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -392
- package/README.zh-CN.md +154 -393
- package/extension/bootstrap/background.js +52 -9
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/bootstrap/popup.html +1 -0
- package/extension/bootstrap/popupFallback.js +55 -0
- package/extension/bootstrap/updateStatus.js +1 -0
- package/extension/src/background.js +283 -55
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +129 -16
- package/extension/src/backgroundUpdateCoordinator.js +242 -29
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +171 -8
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +104 -47
- package/extension/src/content/composerPanel.js +110 -12
- package/extension/src/content/contentRuntime.js +1555 -926
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +48 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +11077 -3412
- package/extension/src/content/generated/content.bundle.meta.json +14 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +10 -0
- package/extension/src/content/localSkillsPanel.js +104 -14
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +47 -11
- package/extension/src/content/modelPicker.js +8 -4
- package/extension/src/content/nativeCompatibilityController.js +74 -30
- package/extension/src/content/otWarmMirror.js +5 -6
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +17 -4
- package/extension/src/content/panelMaintenance.js +46 -14
- package/extension/src/content/panelRenderer.js +57 -27
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +67 -6
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +73 -14
- package/extension/src/content/recentProjects.js +15 -8
- package/extension/src/content/runActivitySummary.js +160 -18
- package/extension/src/content/runCompletionMeta.js +532 -0
- package/extension/src/content/runController.js +39 -15
- package/extension/src/content/runFailureNotice.js +29 -13
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +157 -47
- package/extension/src/content/runRecoveryActions.js +147 -0
- package/extension/src/content/runResultActions.js +28 -143
- package/extension/src/content/runReviewController.js +633 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +367 -175
- package/extension/src/content/scopedPersistencePanelState.js +134 -20
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +153 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +50 -4
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +22 -5
- package/extension/src/content/settingsWorkbench.js +132 -89
- package/extension/src/content/sharedSessionViewSync.js +49 -11
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +118 -12
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +116 -94
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/overleafCapabilities.js +4 -1
- package/extension/src/page/ownedDirectoryCleanup.js +94 -0
- package/extension/src/page/referenceProjects.js +1 -0
- package/extension/src/page/reviewContext.js +113 -0
- package/extension/src/page/saveState.js +6 -4
- package/extension/src/page/snapshotRouter.js +84 -39
- package/extension/src/page/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +114 -26
- package/extension/src/page/trackedChangeCapture.js +12 -2
- package/extension/src/page/trackedChangeOwnership.js +19 -1
- package/extension/src/page/trackedChangeReplay.js +41 -21
- package/extension/src/page/trackedChangesLifecycle.js +217 -199
- package/extension/src/page/treeOperations.js +7 -10
- package/extension/src/page/updateSafetyProbe.js +41 -0
- package/extension/src/page/writebackReceiptJournal.js +2 -0
- package/extension/src/page/writebackRouter.js +103 -155
- package/extension/src/pageBridge.js +126 -69
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +373 -17
- package/extension/src/shared/auditRecords.js +4 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +129 -66
- package/extension/src/shared/composerAttachmentDraft.js +156 -0
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +349 -4
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +590 -24
- package/extension/src/shared/lineReferences.js +38 -31
- package/extension/src/shared/managedUpdateProjection.js +2 -2
- package/extension/src/shared/nativeRequestIdentity.js +1 -0
- package/extension/src/shared/pageRpcContract.js +5 -4
- package/extension/src/shared/panelIcons.js +39 -0
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/reviewing.js +130 -1
- package/extension/src/shared/runActivityModel.js +70 -4
- package/extension/src/shared/runExecutionSnapshot.js +13 -0
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +132 -28
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +118 -27
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +166 -52
- package/extension/src/shared/storageDb.js +90 -26
- package/extension/src/shared/storageMigration.js +78 -112
- package/extension/src/shared/storageRunActions.js +267 -30
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +253 -32
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +17 -3
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +58 -40
- package/extension/styles/panel.css +1123 -493
- package/native-host/src/anthropicBridgeRequest.js +4 -2
- package/native-host/src/anthropicBridgeResponse.js +6 -2
- package/native-host/src/anthropicMessagesBridge.js +24 -16
- package/native-host/src/bootstrapMigration.js +296 -0
- package/native-host/src/bootstrapMigrationContract.js +101 -0
- package/native-host/src/bootstrapUpdate.js +269 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +20 -1
- package/native-host/src/chatCompletionsBridge.js +48 -15
- package/native-host/src/codexConfigPaths.js +106 -0
- package/native-host/src/codexHome.js +66 -73
- package/native-host/src/codexHomeScope.js +124 -0
- package/native-host/src/codexInheritedAuth.js +90 -0
- package/native-host/src/codexLocalSkills.js +301 -0
- package/native-host/src/codexPromptAssembly.js +114 -127
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +234 -198
- package/native-host/src/codexSessionTiming.js +18 -8
- package/native-host/src/codexSkillIsolation.js +170 -0
- package/native-host/src/codexThreadFork.js +19 -3
- package/native-host/src/index.js +19 -7
- package/native-host/src/managedInstall.js +110 -35
- package/native-host/src/managedInstallRecovery.js +140 -0
- package/native-host/src/managedRuntimeEvidence.js +57 -0
- package/native-host/src/mirrorWorkspace.js +92 -22
- package/native-host/src/nativeDoctor.js +15 -0
- package/native-host/src/nativeHelperLifecycle.js +21 -0
- package/native-host/src/nativeHostRegistration.js +33 -0
- package/native-host/src/nativeRunLifecycle.js +71 -0
- package/native-host/src/processStopBarrier.js +42 -7
- package/native-host/src/projectPathPlatform.js +30 -0
- package/native-host/src/providerContextError.js +20 -0
- package/native-host/src/providerHttp.js +80 -0
- package/native-host/src/providerProfile.js +4 -2
- package/native-host/src/providerReasoning.js +2 -7
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/providerStore.js +72 -14
- package/native-host/src/responsesPassthroughBridge.js +266 -52
- package/native-host/src/runtimeInstaller.js +21 -1
- package/native-host/src/selectionScope.js +36 -8
- package/native-host/src/subagentAdmission.js +26 -0
- package/native-host/src/subagentBroker.js +79 -66
- package/native-host/src/subagentTelemetry.js +19 -10
- package/native-host/src/subagentWorkspacePath.js +5 -2
- package/native-host/src/taskRunnerRuntime.js +92 -57
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +409 -46
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +5 -8
- package/scripts/install-managed.mjs +1 -0
|
@@ -1564,15 +1564,6 @@
|
|
|
1564
1564
|
};
|
|
1565
1565
|
}
|
|
1566
1566
|
|
|
1567
|
-
function fileTreeMethodNames(type) {
|
|
1568
|
-
return {
|
|
1569
|
-
create: ['createDoc', 'createFile', 'addDoc', 'addFile'],
|
|
1570
|
-
rename: ['renameEntity', 'renameFile', 'renameDoc'],
|
|
1571
|
-
move: ['moveEntity', 'moveFile', 'moveDoc'],
|
|
1572
|
-
delete: ['deleteEntity', 'deleteFile', 'deleteDoc', 'removeEntity']
|
|
1573
|
-
}[type] || [];
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
1567
|
function resolveTimeoutMs(options = {}, fallback) {
|
|
1577
1568
|
const value = Number(options.timeoutMs);
|
|
1578
1569
|
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
@@ -1615,5 +1606,11 @@
|
|
|
1615
1606
|
return text;
|
|
1616
1607
|
}
|
|
1617
1608
|
|
|
1618
|
-
|
|
1609
|
+
function fileTreeMethodNames(type) {
|
|
1610
|
+
return {
|
|
1611
|
+
create: ['createDoc', 'createFile', 'addDoc', 'addFile'], rename: ['renameEntity', 'renameFile', 'renameDoc'],
|
|
1612
|
+
move: ['moveEntity', 'moveFile', 'moveDoc'], delete: ['deleteEntity', 'deleteFile', 'deleteDoc', 'removeEntity']
|
|
1613
|
+
}[type] || [];
|
|
1614
|
+
}
|
|
1615
|
+
return { create, fileTreeMethodNames };
|
|
1619
1616
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
(function installUpdateSafetyProbe(root) {
|
|
2
|
+
'use strict';
|
|
3
|
+
if (root.CodexOverleafUpdateSafetyProbe?.revision === '1') return;
|
|
4
|
+
const identities = new WeakMap();
|
|
5
|
+
function identity(value) {
|
|
6
|
+
if (!identities.has(value)) identities.set(value, crypto.randomUUID());
|
|
7
|
+
return identities.get(value);
|
|
8
|
+
}
|
|
9
|
+
// The same full CodeMirror document sources used by pageBridge. No DOM
|
|
10
|
+
// viewport text is eligible for the quiet-save fallback.
|
|
11
|
+
function editorView() {
|
|
12
|
+
const store = root.overleaf?.unstable?.store || root.Overleaf?.unstable?.store;
|
|
13
|
+
const get = key => { try { return store?.get?.(key) || {}; } catch (_) { return {}; } };
|
|
14
|
+
return [get('editor.view'), get('editor.view.camel'), get('editor').view,
|
|
15
|
+
root.overleaf?.unstable?.editorView, root.Overleaf?.editorView, root._ide?.editorView]
|
|
16
|
+
.find(value => value && typeof value.dispatch === 'function' && value.state?.doc
|
|
17
|
+
&& typeof value.state.doc.toString === 'function') || null;
|
|
18
|
+
}
|
|
19
|
+
function fileIdentity() {
|
|
20
|
+
const store = root.overleaf?.unstable?.store || root.Overleaf?.unstable?.store;
|
|
21
|
+
const get = key => { try { return store?.get?.(key); } catch (_) { return ''; } };
|
|
22
|
+
const selected = document.querySelector('[role="treeitem"][aria-selected="true"] [data-file-id]');
|
|
23
|
+
return [get('editor.open_doc_id'), get('editor.openDocId'), root._ide?.editor?.open_doc_id,
|
|
24
|
+
root._ide?.editor?.openDocId, selected?.getAttribute('data-file-id'),
|
|
25
|
+
get('editor.open_doc_name'), get('editor.openDocName')].find(value => typeof value === 'string' && value) || '';
|
|
26
|
+
}
|
|
27
|
+
async function probe(expectedUrl) {
|
|
28
|
+
const view = editorView(), doc = view?.state?.doc, file = fileIdentity();
|
|
29
|
+
if (location.href !== expectedUrl || !file || !root.CodexOverleafSaveState) return { ok: false, state: 'unavailable' };
|
|
30
|
+
const save = root.CodexOverleafSaveState.create({ window: root, document,
|
|
31
|
+
delay: ms => new Promise(resolve => setTimeout(resolve, ms)),
|
|
32
|
+
detectEditor: () => ({ ok: Boolean(view && editorView() === view && view.state.doc === doc) }),
|
|
33
|
+
getActiveFilePath: () => doc ? identity(doc) : '', readActiveEditorText: () => view?.state.doc.toString() || '' });
|
|
34
|
+
const result = await save.waitForSaveState({ deadlineMs: 1500, pollIntervalMs: 100,
|
|
35
|
+
requirePositiveSignal: true, allowQuietEditorFallback: Boolean(doc), quietFallbackMs: 1000 });
|
|
36
|
+
if (location.href !== expectedUrl || fileIdentity() !== file || editorView() !== view
|
|
37
|
+
|| (view && view.state.doc !== doc)) return { ok: false, state: 'unavailable' };
|
|
38
|
+
return { ok: result.ok === true, state: result.state, identity: file + ':' + (doc ? identity(doc) : 'positive-save-signal') };
|
|
39
|
+
}
|
|
40
|
+
root.CodexOverleafUpdateSafetyProbe = Object.freeze({ revision: '1', probe });
|
|
41
|
+
})(globalThis);
|
|
@@ -87,8 +87,10 @@
|
|
|
87
87
|
return applyOperations(params.operations || [], {
|
|
88
88
|
baseFiles: params.baseFiles || null,
|
|
89
89
|
reviewingPolicy: params.reviewingPolicy || '',
|
|
90
|
+
...(params.reviewOwnerToken ? { reviewOwnerToken: params.reviewOwnerToken } : {}),
|
|
90
91
|
requireReviewing: params.requireReviewing === true,
|
|
91
92
|
requireEditing: params.requireEditing === true,
|
|
93
|
+
retryCreates: params.retryCreates === true,
|
|
92
94
|
runProjectId: projectId,
|
|
93
95
|
...(params.deadlineAt == null ? {} : { deadlineAt: params.deadlineAt }),
|
|
94
96
|
onOperationResult
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
(function initCodexOverleafWritebackRouter(root, factory) {
|
|
2
2
|
if (typeof module === 'object' && module.exports) {
|
|
3
|
-
module.exports = factory();
|
|
3
|
+
module.exports = factory(require('./treeOperations'));
|
|
4
4
|
} else {
|
|
5
|
-
root.CodexOverleafWritebackRouter = factory();
|
|
5
|
+
root.CodexOverleafWritebackRouter = factory(root.CodexOverleafTreeOperations);
|
|
6
6
|
}
|
|
7
|
-
})(typeof window !== 'undefined' ? window : globalThis, function writebackRouterFactory() {
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function writebackRouterFactory(TreeOperations) {
|
|
8
8
|
'use strict';
|
|
9
|
+
const { fileTreeMethodNames } = TreeOperations;
|
|
9
10
|
|
|
10
11
|
function create(deps = {}) {
|
|
11
12
|
const window = deps.window || (typeof globalThis !== 'undefined' ? globalThis : {});
|
|
@@ -358,18 +359,20 @@
|
|
|
358
359
|
|
|
359
360
|
async function applyOperations(operations, options = {}) {
|
|
360
361
|
if (options.reviewingPolicy === 'no-trace-undo') {
|
|
361
|
-
|
|
362
|
+
const Context = window.CodexOverleafReviewContext || (typeof module === 'object' && module.exports ? require('./reviewContext') : null);
|
|
363
|
+
return Context.create({ window, now, readWriteCancellationSequence, getProjectId: () => treeOperations.getProjectId?.() }, options).run(
|
|
364
|
+
(scoped, input) => applyOperationsWithNoTraceUndo(operations, { ...input, reviewGuard: scoped.reviewGuard, reviewAwait: scoped.reviewAwait }));
|
|
362
365
|
}
|
|
363
366
|
const hasOperations = (operations || []).length > 0;
|
|
364
367
|
const trackReviewingChanges = options.requireReviewing === true && hasOperations;
|
|
365
368
|
let reviewingPolicy = null;
|
|
366
369
|
if (options.requireReviewing === true && hasOperations) {
|
|
367
|
-
const reviewing = await ensureReviewing({ waitMs: 1800 });
|
|
370
|
+
const reviewing = await ensureReviewing({ waitMs: 1800, runProjectId: options.runProjectId });
|
|
368
371
|
if (!reviewing.ok) {
|
|
369
372
|
return buildReviewingRequiredBlockedResult(operations, reviewing);
|
|
370
373
|
}
|
|
371
374
|
} else if (options.requireEditing === true && hasOperations) {
|
|
372
|
-
const editing = await ensureEditing({ waitMs: 1800 });
|
|
375
|
+
const editing = await ensureEditing({ waitMs: 1800, runProjectId: options.runProjectId });
|
|
373
376
|
if (!editing.ok) {
|
|
374
377
|
return buildEditingRequiredBlockedResult(operations, editing);
|
|
375
378
|
}
|
|
@@ -388,6 +391,7 @@
|
|
|
388
391
|
}
|
|
389
392
|
const result = await applyOperationsCore(operations, {
|
|
390
393
|
...options,
|
|
394
|
+
noTraceUndo: false,
|
|
391
395
|
trackReviewingChanges
|
|
392
396
|
});
|
|
393
397
|
return reviewingPolicy
|
|
@@ -398,10 +402,12 @@
|
|
|
398
402
|
: result;
|
|
399
403
|
}
|
|
400
404
|
|
|
401
|
-
async function confirmNoTraceUndoEditing() {
|
|
405
|
+
async function confirmNoTraceUndoEditing(options = {}) {
|
|
402
406
|
let changed = false;
|
|
403
407
|
let toggle = { ok: true, changed: false, enabled: false };
|
|
404
408
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
409
|
+
const blocked = options.reviewGuard?.();
|
|
410
|
+
if (blocked) return blocked;
|
|
405
411
|
if (!isEditingConfirmedForNoTraceUndo(getReviewingState({}))) {
|
|
406
412
|
toggle = await setReviewingEnabled(false, { waitMs: 1800 });
|
|
407
413
|
if (!toggle.ok && (!['editing_not_confirmed', 'reviewing_disable_failed'].includes(toggle.code)
|
|
@@ -418,8 +424,13 @@
|
|
|
418
424
|
reason: 'Overleaf Editing mode did not remain confirmed before undo; no undo write was started.' };
|
|
419
425
|
}
|
|
420
426
|
|
|
427
|
+
async function awaitReview(options, work) {
|
|
428
|
+
try { return options.reviewAwait ? await options.reviewAwait(work) : await work(); }
|
|
429
|
+
catch (error) { if (error?.reviewFailure) return error.reviewFailure; throw error; }
|
|
430
|
+
}
|
|
431
|
+
|
|
421
432
|
async function applyOperationsWithNoTraceUndo(operations, options = {}) {
|
|
422
|
-
const disabled = await confirmNoTraceUndoEditing();
|
|
433
|
+
const disabled = await awaitReview(options, () => confirmNoTraceUndoEditing(options));
|
|
423
434
|
if (!disabled.ok) return buildNoTraceUndoBlockedResult(operations, disabled);
|
|
424
435
|
const result = await applyOperationsCore(operations, {
|
|
425
436
|
...options, noTraceUndo: true, trackReviewingChanges: false
|
|
@@ -441,13 +452,7 @@
|
|
|
441
452
|
const safeBaseFiles = normalizeBaseFilesForSafety(options.baseFiles);
|
|
442
453
|
const baseFileLookup = window.CodexOverleafStaleGuard?.buildBaseFileLookup(safeBaseFiles);
|
|
443
454
|
const baseBinaryFileLookup = buildBaseBinaryFileLookup(safeBaseFiles);
|
|
444
|
-
//
|
|
445
|
-
// writeGuard once at entry, but each per-op step here can take 0.5-5s
|
|
446
|
-
// (openFileByPath, waitForActiveEditorText, save-state verification),
|
|
447
|
-
// so a multi-op write can span 10-30 seconds total. If the user SPA-
|
|
448
|
-
// navigates to a different project mid-flight, the remaining operations
|
|
449
|
-
// would land in the wrong project. Re-check the editor project before
|
|
450
|
-
// each op and short-circuit the rest of the queue when it diverges.
|
|
455
|
+
// Recheck project identity between awaited operations and before each write.
|
|
451
456
|
const runProjectId = typeof options.runProjectId === 'string' ? options.runProjectId : '';
|
|
452
457
|
const writeGuardSurface = window.CodexOverleafWriteGuard?.create({
|
|
453
458
|
window,
|
|
@@ -478,13 +483,17 @@
|
|
|
478
483
|
? await deps.beginTextCreateBatch(createCandidates, { isCurrent: () =>
|
|
479
484
|
treeOperations.getProjectId?.() === runProjectId && readWriteCancellationSequence() === cancelBaselineSequence }) : null;
|
|
480
485
|
const deferredCreates = [];
|
|
486
|
+
let haltRemainingOperations = false;
|
|
487
|
+
const skipTail = (operation, result) => operations.slice(operations.indexOf(operation) + 1)
|
|
488
|
+
.forEach(tail => skipped.push({ operation: normalizeOperationPaths(tail), result }));
|
|
481
489
|
const reopenQueue = [];
|
|
482
490
|
const deadlineFailure = () => Number.isFinite(options.deadlineAt) && now() >= options.deadlineAt
|
|
483
491
|
? { ok: false, code: 'writeback_deadline_exceeded', changedDocument: false,
|
|
484
492
|
reason: 'The undo time budget expired before this operation could start.' } : null;
|
|
485
|
-
const editOptions = () => ({ baseFileLookup, runProjectId,
|
|
493
|
+
const editOptions = () => ({ baseFileLookup, runProjectId, deadlineAt: options.deadlineAt,
|
|
494
|
+
reviewGuard: options.reviewGuard, reviewAwait: options.reviewAwait,
|
|
486
495
|
recheckWriteProject: writeGuardSurface ? () => {
|
|
487
|
-
const expired = deadlineFailure();
|
|
496
|
+
const expired = options.reviewGuard?.() || deadlineFailure();
|
|
488
497
|
return expired ? { applied: [], skipped: [{ operation: {}, result: expired }] }
|
|
489
498
|
: writeGuardSurface.runWriteGuard({ runProjectId });
|
|
490
499
|
} : null,
|
|
@@ -499,13 +508,15 @@
|
|
|
499
508
|
};
|
|
500
509
|
|
|
501
510
|
for (const rawOperation of operations) {
|
|
511
|
+
const reviewBlock = options.reviewGuard?.();
|
|
512
|
+
if (reviewBlock) {
|
|
513
|
+
skipped.push({ operation: normalizeOperationPaths(rawOperation), result: reviewBlock });
|
|
514
|
+
skipTail(rawOperation, reviewBlock); break;
|
|
515
|
+
}
|
|
502
516
|
// Stop undispatched work when cancellation arrives during an awaited operation.
|
|
503
517
|
if (readWriteCancellationSequence() !== cancelBaselineSequence) {
|
|
504
518
|
skipped.push({ operation: normalizeOperationPaths(rawOperation), result: cancelledSkipResult });
|
|
505
|
-
|
|
506
|
-
for (const tailOperation of remainingForCancel) {
|
|
507
|
-
skipped.push({ operation: normalizeOperationPaths(tailOperation), result: cancelledSkipResult });
|
|
508
|
-
}
|
|
519
|
+
skipTail(rawOperation, cancelledSkipResult);
|
|
509
520
|
break;
|
|
510
521
|
}
|
|
511
522
|
// Re-check project ownership before this operation and preserve every skipped tail entry.
|
|
@@ -515,10 +526,7 @@
|
|
|
515
526
|
// Keep the current path and the remaining operations attributable.
|
|
516
527
|
const guardFailure = guardBlock.skipped[0].result;
|
|
517
528
|
skipped.push({ operation: normalizeOperationPaths(rawOperation), result: guardFailure });
|
|
518
|
-
|
|
519
|
-
for (const tailOperation of remaining) {
|
|
520
|
-
skipped.push({ operation: normalizeOperationPaths(tailOperation), result: guardFailure });
|
|
521
|
-
}
|
|
529
|
+
skipTail(rawOperation, guardFailure);
|
|
522
530
|
break;
|
|
523
531
|
}
|
|
524
532
|
}
|
|
@@ -538,7 +546,7 @@
|
|
|
538
546
|
} else if (['binary-create', 'overwrite-binary'].includes(operation.type)) {
|
|
539
547
|
result = await applyBinaryAssetOperation(operation, { baseFileLookup, baseBinaryFileLookup });
|
|
540
548
|
} else if (['create', 'rename', 'move', 'delete'].includes(operation.type)) {
|
|
541
|
-
result = await applyFileTreeOperation(operation, { baseFileLookup, runProjectId, deadlineAt: options.deadlineAt,
|
|
549
|
+
result = await applyFileTreeOperation(operation, { baseFileLookup, runProjectId, deadlineAt: options.deadlineAt, reviewGuard: options.reviewGuard, noTraceUndo: options.noTraceUndo === true,
|
|
542
550
|
createBatch: createBatch?.targets.has(operation.path) ? createBatch : null,
|
|
543
551
|
retryCreates: options.retryCreates === true });
|
|
544
552
|
} else {
|
|
@@ -557,16 +565,21 @@
|
|
|
557
565
|
continue;
|
|
558
566
|
}
|
|
559
567
|
record(operation, result);
|
|
568
|
+
if (operation.type === 'move' && operation.moveProof && !result.ok) {
|
|
569
|
+
haltRemainingOperations = true;
|
|
570
|
+
skipTail(rawOperation, { ok: false, code: 'file_plan_not_completed', changedDocument: false,
|
|
571
|
+
reason: 'The file move did not finish; the remaining file plan was not started.' }); break;
|
|
572
|
+
}
|
|
560
573
|
}
|
|
561
574
|
|
|
562
575
|
// A file that could not be opened mid-batch usually opens once the
|
|
563
576
|
// previous document has settled: give untouched open failures one pass.
|
|
564
|
-
if (reopenQueue.length && readWriteCancellationSequence() === cancelBaselineSequence
|
|
577
|
+
if (!haltRemainingOperations && reopenQueue.length && readWriteCancellationSequence() === cancelBaselineSequence
|
|
565
578
|
&& !skipped.some(entry => entry.result?.code === 'aborted_project_changed')) {
|
|
566
579
|
await delay(deps.reopenRetryDelayMs ?? 1200);
|
|
567
580
|
}
|
|
568
581
|
for (const entry of reopenQueue) {
|
|
569
|
-
const blocked = readWriteCancellationSequence() !== cancelBaselineSequence
|
|
582
|
+
const blocked = options.reviewGuard?.() || haltRemainingOperations || readWriteCancellationSequence() !== cancelBaselineSequence
|
|
570
583
|
|| skipped.some(item => item.result?.code === 'aborted_project_changed')
|
|
571
584
|
|| Boolean(runProjectId && writeGuardSurface && await writeGuardSurface.runWriteGuard({ runProjectId }));
|
|
572
585
|
const result = blocked ? entry.result : await applyEditOperation(entry.operation, editOptions());
|
|
@@ -589,25 +602,26 @@
|
|
|
589
602
|
ok: skipped.length === 0,
|
|
590
603
|
applied,
|
|
591
604
|
skipped,
|
|
605
|
+
...(haltRemainingOperations ? { haltRemainingOperations: true } : {}),
|
|
592
606
|
trackedChanges: trackedChangeCaptures.some(capture => capture.state !== 'observed') ? [] : mergeTrackedChangeRefs(trackedChanges),
|
|
593
607
|
trackedChangeCaptures
|
|
594
608
|
};
|
|
595
609
|
}
|
|
596
610
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
&& result.failure.changedDocument === false;
|
|
600
|
-
}
|
|
611
|
+
const isUntouchedOpenFailure = result => result?.ok === false && result.failure?.code === 'target_file_open_failed'
|
|
612
|
+
&& result.failure.changedDocument === false;
|
|
601
613
|
|
|
602
614
|
async function applyEditOperation(operation, options = {}) {
|
|
615
|
+
const reviewBlock = options.reviewGuard?.();
|
|
616
|
+
if (reviewBlock) return reviewBlock;
|
|
603
617
|
const currentPath = getActiveFilePath();
|
|
604
|
-
const editorReady = await ensureEditorReadyForOperation(operation, options.baseFileLookup, currentPath);
|
|
618
|
+
const editorReady = await awaitReview(options, () => ensureEditorReadyForOperation(operation, options.baseFileLookup, currentPath));
|
|
605
619
|
if (!editorReady.ok) {
|
|
606
620
|
return editorReady;
|
|
607
621
|
}
|
|
608
622
|
|
|
609
623
|
if (options.noTraceUndo === true) {
|
|
610
|
-
const editing = await confirmNoTraceUndoEditing();
|
|
624
|
+
const editing = await awaitReview(options, () => confirmNoTraceUndoEditing(options));
|
|
611
625
|
if (!editing.ok) return buildNoTraceUndoBlockedResult([operation], editing).skipped[0].result;
|
|
612
626
|
}
|
|
613
627
|
const trackReviewingChanges = options.trackReviewingChanges === true;
|
|
@@ -625,7 +639,8 @@
|
|
|
625
639
|
options.baseFileLookup
|
|
626
640
|
) || { ok: true };
|
|
627
641
|
if (!freshness.ok) {
|
|
628
|
-
const ready = await waitForFreshEditorTextForOperation(operation, options.baseFileLookup, 1500);
|
|
642
|
+
const ready = await awaitReview(options, () => waitForFreshEditorTextForOperation(operation, options.baseFileLookup, 1500));
|
|
643
|
+
if (options.reviewGuard?.()) return options.reviewGuard();
|
|
629
644
|
if (!ready.ok) {
|
|
630
645
|
const decorated = attachWritebackDebug(freshness, 'stale_guard', operation, options.baseFileLookup, current, {
|
|
631
646
|
initialActivePath: currentPath,
|
|
@@ -661,12 +676,15 @@
|
|
|
661
676
|
return decorated;
|
|
662
677
|
}
|
|
663
678
|
current = ready.text;
|
|
664
|
-
freshness =
|
|
679
|
+
freshness = ready.freshness;
|
|
665
680
|
}
|
|
666
681
|
|
|
667
682
|
let nextContent = null;
|
|
683
|
+
let preparedPatches;
|
|
668
684
|
if (Array.isArray(operation.patches) && operation.patches.length) {
|
|
669
|
-
|
|
685
|
+
preparedPatches = freshness.preparedPatches || window.CodexOverleafStaleGuard?.prepareOperationPatches(operation, current, options.baseFileLookup)
|
|
686
|
+
|| { ok: true, patches: operation.patches };
|
|
687
|
+
const patched = preparedPatches.ok ? applyTextPatches(current, preparedPatches.patches) : preparedPatches;
|
|
670
688
|
if (!patched.ok) {
|
|
671
689
|
const decorated = attachWritebackDebug(patched, 'apply_text_patches', operation, options.baseFileLookup, current, {
|
|
672
690
|
initialActivePath: currentPath,
|
|
@@ -718,25 +736,31 @@
|
|
|
718
736
|
operationType: 'undo', changedDocument: false,
|
|
719
737
|
userMessage: 'The project identity guard is unavailable; the undo write was not applied.' }) };
|
|
720
738
|
if (blocked) return blocked.skipped?.[0]?.result || blocked;
|
|
721
|
-
if (getActiveFilePath() !== operation.path || readActiveEditorText() !== current) {
|
|
722
|
-
const reason = 'The editor changed while confirming Editing mode; the undo write was not applied.';
|
|
723
|
-
return { ok: false, code: 'stale_source_changed', reason,
|
|
724
|
-
failure: buildPageFailure('stale_source_changed', { file: operation.path,
|
|
725
|
-
operationType: 'undo', changedDocument: false, userMessage: reason }) };
|
|
726
|
-
}
|
|
727
739
|
if (!isEditingConfirmedForNoTraceUndo(getReviewingState({})))
|
|
728
740
|
return buildNoTraceUndoBlockedResult([operation], { ok: false, code: 'editing_not_confirmed',
|
|
729
741
|
reason: 'Overleaf left Editing mode before the undo write.' }).skipped[0].result;
|
|
730
742
|
}
|
|
731
743
|
|
|
744
|
+
const finalReviewBlock = options.reviewGuard?.();
|
|
745
|
+
if (finalReviewBlock) return finalReviewBlock;
|
|
746
|
+
// Read and dispatch synchronously against the same document. Navigation,
|
|
747
|
+
// freshness polling and Undo mode/project checks can all have awaited.
|
|
748
|
+
if (getActiveFilePath() !== operation.path || readActiveEditorText() !== current) {
|
|
749
|
+
const reason = 'The editor changed before the edit could be applied; the document was not overwritten.';
|
|
750
|
+
return { ok: false, code: 'stale_source_changed', reason,
|
|
751
|
+
failure: buildPageFailure('stale_source_changed', { file: operation.path,
|
|
752
|
+
operationType: options.noTraceUndo ? 'undo' : 'edit', changedDocument: false, userMessage: reason }) };
|
|
753
|
+
}
|
|
732
754
|
const result = Array.isArray(operation.patches) && operation.patches.length
|
|
733
|
-
? replaceActiveEditorPatches(
|
|
755
|
+
? replaceActiveEditorPatches(preparedPatches.patches, nextContent)
|
|
734
756
|
: replaceActiveEditorText(nextContent);
|
|
735
757
|
if (!result.ok) {
|
|
736
758
|
return result;
|
|
737
759
|
}
|
|
738
760
|
|
|
739
|
-
const
|
|
761
|
+
const verifyWaitMs = Math.min(deps.writeVerifyWaitMs ?? 2500, Number.isFinite(options.deadlineAt)
|
|
762
|
+
? Math.max(0, options.deadlineAt - now()) : Infinity);
|
|
763
|
+
const verified = await verifyActiveEditorText(nextContent, operation.path, verifyWaitMs, current);
|
|
740
764
|
if (!verified.ok) {
|
|
741
765
|
const decorated = attachWritebackDebug(verified, 'write_verification', operation, options.baseFileLookup, readActiveEditorText(), {
|
|
742
766
|
initialActivePath: currentPath,
|
|
@@ -776,7 +800,7 @@
|
|
|
776
800
|
// Slow batches are covered by request receipts; Accept replay retains
|
|
777
801
|
// its independent safety window proving that no new tracking was added.
|
|
778
802
|
const trackedDiff = trackReviewingChanges
|
|
779
|
-
? await captureTrackedWrite({ trackedBefore, captureBaseline, operation, beforeContent: current,
|
|
803
|
+
? await captureTrackedWrite({ trackedBefore, captureBaseline, operation: { ...operation, patches: preparedPatches?.patches }, beforeContent: current,
|
|
780
804
|
postContent: nextContent, runProjectId: options.runProjectId, waitMs: deps.trackCaptureWaitMs ?? 35000 })
|
|
781
805
|
: await waitForTrackedChangeDiff(trackedBefore, operationPaths, {
|
|
782
806
|
waitMs: 3600, intervalMs: 180, stopOnFirst: true
|
|
@@ -805,6 +829,7 @@
|
|
|
805
829
|
}),
|
|
806
830
|
verified: true,
|
|
807
831
|
verifiedContent: nextContent,
|
|
832
|
+
...(preparedPatches ? { appliedPatches: preparedPatches.patches } : {}),
|
|
808
833
|
trackedChanges: mergeTrackedChangeRefs(trackedChanges)
|
|
809
834
|
};
|
|
810
835
|
}
|
|
@@ -812,18 +837,18 @@
|
|
|
812
837
|
window.CodexOverleafStaleGuard?.updateExpectedFileContent(
|
|
813
838
|
options.baseFileLookup,
|
|
814
839
|
operation.path,
|
|
815
|
-
nextContent
|
|
840
|
+
preparedPatches?.sourceContent ?? nextContent
|
|
816
841
|
);
|
|
817
842
|
return {
|
|
818
843
|
...result,
|
|
819
844
|
verified: true,
|
|
820
845
|
verifiedContent: nextContent,
|
|
846
|
+
...(preparedPatches ? { appliedPatches: preparedPatches.patches } : {}),
|
|
821
847
|
trackedChangeCapture,
|
|
822
848
|
trackedChanges: mergeTrackedChangeRefs(trackedChanges)
|
|
823
849
|
};
|
|
824
850
|
}
|
|
825
851
|
|
|
826
|
-
|
|
827
852
|
async function verifyActiveEditorText(expected, filePath, waitMs = 1000, before) {
|
|
828
853
|
const deadline = Date.now() + waitMs;
|
|
829
854
|
let actual = readActiveEditorText();
|
|
@@ -949,35 +974,17 @@
|
|
|
949
974
|
lastText = readActiveEditorText();
|
|
950
975
|
const activeFileMatches = lastActivePath === filePath;
|
|
951
976
|
const identityChanged = Boolean(previousEditorIdentity) && activeEditorIdentityChanged(previousEditorIdentity);
|
|
952
|
-
const baseComparison = compareEditorTextToBase(filePath, lastText, baseFileLookup);
|
|
953
|
-
const baseMatches = baseComparison.matches;
|
|
954
977
|
const settledLongEnough = Date.now() - startedAt >= minSettleMs;
|
|
955
978
|
const contentChangedFromPrevious = !options.previousSignature
|
|
956
979
|
|| contentSignature(lastText) !== options.previousSignature;
|
|
957
|
-
//
|
|
958
|
-
//
|
|
959
|
-
//
|
|
960
|
-
//
|
|
961
|
-
// 1. editor identity visibly changed (fast path when it works);
|
|
962
|
-
// 2. the editor shows EXACTLY the target file's expected base —
|
|
963
|
-
// the strongest possible proof the right document is loaded;
|
|
964
|
-
// 3. same-file reopen settled with unchanged content;
|
|
965
|
-
// 4. base UNKNOWN only: the content moved off the previous document
|
|
966
|
-
// and the settle window elapsed. When the base IS known, content
|
|
967
|
-
// must match it (acceptor 2) — a weakly-anchored patch (e.g. an
|
|
968
|
-
// insert into an empty file, expected:'') would otherwise anchor
|
|
969
|
-
// trivially into a wrong-but-different document.
|
|
980
|
+
// Confirm navigation independently of source freshness. A stale target
|
|
981
|
+
// can be correctly open; identical baseline bytes alone cannot prove a
|
|
982
|
+
// switch. Content movement remains the fallback for editor lifecycles
|
|
983
|
+
// whose document identity cannot be observed reliably.
|
|
970
984
|
const switchConfirmed = identityChanged
|
|
971
|
-
||
|
|
972
|
-
|| (
|
|
973
|
-
|
|
974
|
-
&& contentSignature(lastText) === options.previousSignature
|
|
975
|
-
&& settledLongEnough)
|
|
976
|
-
|| (!baseComparison.known && contentChangedFromPrevious && settledLongEnough);
|
|
977
|
-
const contentNoLongerLooksLikePreviousDocument = contentChangedFromPrevious
|
|
978
|
-
|| baseMatches
|
|
979
|
-
|| options.initialActivePath === filePath;
|
|
980
|
-
if (activeFileMatches && switchConfirmed && settledLongEnough && contentNoLongerLooksLikePreviousDocument) {
|
|
985
|
+
|| options.initialActivePath === filePath
|
|
986
|
+
|| (contentChangedFromPrevious && settledLongEnough);
|
|
987
|
+
if (activeFileMatches && switchConfirmed && settledLongEnough) {
|
|
981
988
|
return {
|
|
982
989
|
ok: true,
|
|
983
990
|
text: lastText
|
|
@@ -1053,47 +1060,6 @@
|
|
|
1053
1060
|
};
|
|
1054
1061
|
}
|
|
1055
1062
|
|
|
1056
|
-
function editorContentMatchesBase(filePath, content, baseFileLookup) {
|
|
1057
|
-
return compareEditorTextToBase(filePath, content, baseFileLookup).matches;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
function compareEditorTextToBase(filePath, content, baseFileLookup) {
|
|
1061
|
-
const normalizedPath = normalizeSafeProjectPath(filePath);
|
|
1062
|
-
const current = normalizeEditorTextForComparison(content);
|
|
1063
|
-
if (!baseFileLookup || !normalizedPath || !baseFileLookup.has(normalizedPath)) {
|
|
1064
|
-
return {
|
|
1065
|
-
known: false,
|
|
1066
|
-
hasContent: false,
|
|
1067
|
-
matches: false,
|
|
1068
|
-
baseLength: null,
|
|
1069
|
-
currentLength: current.length
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
|
-
const base = normalizeEditorTextForComparison(baseFileLookup.get(normalizedPath));
|
|
1073
|
-
const matches = current === base;
|
|
1074
|
-
if (!matches && base.trim().length > 0) {
|
|
1075
|
-
console.warn('[codex-overleaf] editorContentMatchesBase MISMATCH', filePath,
|
|
1076
|
-
'base.length:', base.length, 'current.length:', current.length,
|
|
1077
|
-
'base[0..80]:', JSON.stringify(base.slice(0, 80)),
|
|
1078
|
-
'current[0..80]:', JSON.stringify(current.slice(0, 80)));
|
|
1079
|
-
}
|
|
1080
|
-
return {
|
|
1081
|
-
known: true,
|
|
1082
|
-
hasContent: base.trim().length > 0,
|
|
1083
|
-
matches,
|
|
1084
|
-
baseLength: base.length,
|
|
1085
|
-
currentLength: current.length
|
|
1086
|
-
};
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
function hasBaseFileContent(filePath, baseFileLookup) {
|
|
1090
|
-
if (!baseFileLookup || !filePath || !baseFileLookup.has(filePath)) {
|
|
1091
|
-
return false;
|
|
1092
|
-
}
|
|
1093
|
-
const content = baseFileLookup.get(filePath);
|
|
1094
|
-
return typeof content === 'string' && content.trim().length > 0;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
1063
|
function normalizeEditorTextForComparison(value) {
|
|
1098
1064
|
const normalized = String(value ?? '').replace(/\r\n/g, '\n');
|
|
1099
1065
|
return normalized.trim().length === 0 ? '' : normalized;
|
|
@@ -1115,7 +1081,8 @@
|
|
|
1115
1081
|
if (freshness.ok) {
|
|
1116
1082
|
return {
|
|
1117
1083
|
ok: true,
|
|
1118
|
-
text
|
|
1084
|
+
text,
|
|
1085
|
+
freshness
|
|
1119
1086
|
};
|
|
1120
1087
|
}
|
|
1121
1088
|
await delay(60);
|
|
@@ -1149,7 +1116,6 @@
|
|
|
1149
1116
|
};
|
|
1150
1117
|
}
|
|
1151
1118
|
|
|
1152
|
-
|
|
1153
1119
|
async function applyBinaryAssetOperation(operation, options = {}) {
|
|
1154
1120
|
const freshness = await checkBinaryAssetFreshness(operation, options);
|
|
1155
1121
|
if (!freshness.ok) {
|
|
@@ -1319,9 +1285,7 @@
|
|
|
1319
1285
|
return baseFile.size == null || currentSize == null || baseFile.size === currentSize;
|
|
1320
1286
|
}
|
|
1321
1287
|
|
|
1322
|
-
|
|
1323
|
-
return String(value || '').replace(/\s+/g, '');
|
|
1324
|
-
}
|
|
1288
|
+
const normalizeBase64 = value => String(value || '').replace(/\s+/g, '');
|
|
1325
1289
|
|
|
1326
1290
|
function decodeBase64Bytes(contentBase64) {
|
|
1327
1291
|
try {
|
|
@@ -1372,6 +1336,17 @@
|
|
|
1372
1336
|
}
|
|
1373
1337
|
const initialProjectId = treeOperations.getProjectId?.();
|
|
1374
1338
|
const initialCancellationSequence = readWriteCancellationSequence();
|
|
1339
|
+
if (operation.type === 'move' && operation.moveProof) {
|
|
1340
|
+
if (options.noTraceUndo !== true) return { ok: false, code: 'file_tree_controls_unavailable', changedDocument: false,
|
|
1341
|
+
reason: 'Native file moves are available only to undo a previously recorded move.' };
|
|
1342
|
+
if (typeof deps.moveFile !== 'function') return { ok: false, code: 'file_move_unavailable', changedDocument: false,
|
|
1343
|
+
reason: 'Verified native file moves are unavailable. Reload the extension and Overleaf.' };
|
|
1344
|
+
const result = await deps.moveFile(operation, { noTraceUndo: true, runProjectId: options.runProjectId, deadlineAt: options.deadlineAt,
|
|
1345
|
+
isCurrent: () => treeOperations.getProjectId?.() === initialProjectId
|
|
1346
|
+
&& readWriteCancellationSequence() === initialCancellationSequence && !options.reviewGuard?.() });
|
|
1347
|
+
if (result.ok) recordFileTreeOperationSuccess(operation, options.baseFileLookup);
|
|
1348
|
+
return result;
|
|
1349
|
+
}
|
|
1375
1350
|
if (operation.type === 'create' && options.createBatch && typeof deps.createTextFile === 'function') {
|
|
1376
1351
|
if (options.baseFileLookup?.has(operation.path) && options.retryCreates !== true) {
|
|
1377
1352
|
return checkFileTreeOperationFreshness(operation, options.baseFileLookup);
|
|
@@ -1379,13 +1354,14 @@
|
|
|
1379
1354
|
return deps.createTextFile(operation, { batch: options.createBatch,
|
|
1380
1355
|
allowExistingMatches: options.retryCreates === true,
|
|
1381
1356
|
isCurrent: () => treeOperations.getProjectId?.() === initialProjectId
|
|
1382
|
-
&& readWriteCancellationSequence() === initialCancellationSequence });
|
|
1357
|
+
&& readWriteCancellationSequence() === initialCancellationSequence && !options.reviewGuard?.() });
|
|
1383
1358
|
}
|
|
1384
1359
|
const freshness = await checkFileTreeOperationFreshness(operation, options.baseFileLookup);
|
|
1385
1360
|
if (!freshness.ok) {
|
|
1386
1361
|
return freshness;
|
|
1387
1362
|
}
|
|
1388
1363
|
|
|
1364
|
+
const ownerBlocked = options.reviewGuard?.(); if (ownerBlocked) return ownerBlocked;
|
|
1389
1365
|
const manager = findFileTreeManager();
|
|
1390
1366
|
const methodNames = fileTreeMethodNames(operation.type);
|
|
1391
1367
|
|
|
@@ -1432,7 +1408,7 @@
|
|
|
1432
1408
|
const nativeOperation = { create: deps.createTextFile, delete: deps.deleteTextFile }[operation.type];
|
|
1433
1409
|
if (typeof nativeOperation === 'function' && (operation.type === 'delete' || typeof operation.content === 'string')) {
|
|
1434
1410
|
const isCurrent = () => Boolean(initialProjectId) && treeOperations.getProjectId?.() === initialProjectId
|
|
1435
|
-
&& readWriteCancellationSequence() === initialCancellationSequence;
|
|
1411
|
+
&& readWriteCancellationSequence() === initialCancellationSequence && !options.reviewGuard?.();
|
|
1436
1412
|
const created = await nativeOperation(operation, { isCurrent, expectedContent: options.baseFileLookup?.get(operation.path) });
|
|
1437
1413
|
if (!created.ok) return created;
|
|
1438
1414
|
if (!isCurrent() || created.verified !== true || created.verification !== 'overleaf-zip') {
|
|
@@ -1456,7 +1432,7 @@
|
|
|
1456
1432
|
const text = proof?.v === 1 && proof.kind === 'text' && typeof expected === 'string';
|
|
1457
1433
|
const binary = proof?.v === 1 && proof.kind === 'binary' && /^[a-f0-9]{64}$/i.test(proof.sha256 || '');
|
|
1458
1434
|
const isCurrent = () => Boolean(projectId) && projectId === options.runProjectId
|
|
1459
|
-
&& treeOperations.getProjectId?.() === projectId && readWriteCancellationSequence() === cancellation;
|
|
1435
|
+
&& treeOperations.getProjectId?.() === projectId && readWriteCancellationSequence() === cancellation && !options.reviewGuard?.();
|
|
1460
1436
|
if (!isCurrent() || (!text && !binary) || typeof deps.deleteTextFile !== 'function') {
|
|
1461
1437
|
return { ok: false, code: 'undo_operation_failed', changedDocument: false,
|
|
1462
1438
|
reason: 'Verified created-file deletion is unavailable for this run.' };
|
|
@@ -1653,15 +1629,6 @@
|
|
|
1653
1629
|
}
|
|
1654
1630
|
}
|
|
1655
1631
|
|
|
1656
|
-
function fileTreeMethodNames(type) {
|
|
1657
|
-
return {
|
|
1658
|
-
create: ['createDoc', 'createFile', 'addDoc', 'addFile'],
|
|
1659
|
-
rename: ['renameEntity', 'renameFile', 'renameDoc'],
|
|
1660
|
-
move: ['moveEntity', 'moveFile', 'moveDoc'],
|
|
1661
|
-
delete: ['deleteEntity', 'deleteFile', 'deleteDoc', 'removeEntity']
|
|
1662
|
-
}[type] || [];
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
1632
|
function normalizeOperationPaths(operation) {
|
|
1666
1633
|
if (!operation || typeof operation !== 'object') {
|
|
1667
1634
|
return operation;
|
|
@@ -1734,10 +1701,6 @@
|
|
|
1734
1701
|
}
|
|
1735
1702
|
|
|
1736
1703
|
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
1704
|
function normalizeTrackedChangeRefs(refs = []) {
|
|
1742
1705
|
const seen = new Set();
|
|
1743
1706
|
const normalized = [];
|
|
@@ -1796,23 +1759,12 @@
|
|
|
1796
1759
|
|
|
1797
1760
|
async function applyOperationsForBridge(operationsOrOptions, options = {}) {
|
|
1798
1761
|
if (Array.isArray(operationsOrOptions)) {
|
|
1799
|
-
//
|
|
1800
|
-
// also runs on the array-shaped entry. T2 only guarded the
|
|
1801
|
-
// payload-object branch; if any future caller goes straight to the
|
|
1802
|
-
// array entry without `options.runProjectId`, the router would have
|
|
1803
|
-
// silently dispatched. Mirror the same `editor_project_id_unavailable`
|
|
1804
|
-
// failure shape the payload branch and the page-side guard already
|
|
1805
|
-
// emit.
|
|
1762
|
+
// Both public call shapes require the original project identity.
|
|
1806
1763
|
const writeGuardBlock = checkWritebackRunProjectId(options);
|
|
1807
1764
|
if (writeGuardBlock) return writeGuardBlock;
|
|
1808
1765
|
return applyOperations(operationsOrOptions, options);
|
|
1809
1766
|
}
|
|
1810
1767
|
const payload = operationsOrOptions || {};
|
|
1811
|
-
// Welcome-panel + write-guard: defense-in-depth.
|
|
1812
|
-
// The pageBridge wrapper already runs the runProjectId guard, but a
|
|
1813
|
-
// future caller could reach the router directly. A missing or empty
|
|
1814
|
-
// `runProjectId` on the payload-shaped call blocks the write with the
|
|
1815
|
-
// same `editor_project_id_unavailable` shape the page-side guard uses.
|
|
1816
1768
|
const writeGuardBlock = checkWritebackRunProjectId(payload);
|
|
1817
1769
|
if (writeGuardBlock) return writeGuardBlock;
|
|
1818
1770
|
return applyOperations(payload.operations || [], payload);
|
|
@@ -1821,10 +1773,7 @@
|
|
|
1821
1773
|
function checkWritebackRunProjectId(params) {
|
|
1822
1774
|
const runProjectId = params && typeof params.runProjectId === 'string' ? params.runProjectId : '';
|
|
1823
1775
|
if (runProjectId) return null;
|
|
1824
|
-
// operation
|
|
1825
|
-
// dispatch; matches the abortDispatchResult shape in pageBridge.js and
|
|
1826
|
-
// avoids crashing downstream audit / transcript code that reads
|
|
1827
|
-
// operation.path without a null guard.
|
|
1776
|
+
// Keep operation.path safe for downstream readers of batch-level failures.
|
|
1828
1777
|
return {
|
|
1829
1778
|
ok: false,
|
|
1830
1779
|
applied: [],
|
|
@@ -1892,6 +1841,7 @@
|
|
|
1892
1841
|
ok: false,
|
|
1893
1842
|
code: reviewing?.code || 'reviewing_not_enabled',
|
|
1894
1843
|
reason: reviewing?.reason || 'Overleaf Reviewing/Track Changes was not confirmed before writing. Codex did not change this file.',
|
|
1844
|
+
evidence: reviewing?.evidence, failure: reviewing?.failure,
|
|
1895
1845
|
reviewing: reviewing?.reviewing || null
|
|
1896
1846
|
}
|
|
1897
1847
|
})),
|
|
@@ -1916,9 +1866,7 @@
|
|
|
1916
1866
|
};
|
|
1917
1867
|
}
|
|
1918
1868
|
|
|
1919
|
-
// Tracked
|
|
1920
|
-
// v1.8.0 (structural-debt phase 7, #117). Loaded just before this file
|
|
1921
|
-
// in the manifest's page-script list; the Node test path requires it.
|
|
1869
|
+
// Tracked lifecycle is loaded by the Page script list or required by Node tests.
|
|
1922
1870
|
const trackedChangesLifecycleModule = (typeof window !== 'undefined' && window.CodexOverleafTrackedChangesLifecycle)
|
|
1923
1871
|
|| (typeof module === 'object' && module.exports ? require('./trackedChangesLifecycle.js') : null)
|
|
1924
1872
|
|| (typeof globalThis !== 'undefined' ? globalThis.CodexOverleafTrackedChangesLifecycle : null);
|
|
@@ -1930,7 +1878,7 @@
|
|
|
1930
1878
|
captureTrackedWrite, reconcileTrackedChangeCapture,
|
|
1931
1879
|
waitForTrackedChangeDiff
|
|
1932
1880
|
} = trackedChangesLifecycleModule.create({
|
|
1933
|
-
window, confirmReviewWriteback: deps.confirmReviewWriteback,
|
|
1881
|
+
window, now, readWriteCancellationSequence, confirmReviewWriteback: deps.confirmReviewWriteback,
|
|
1934
1882
|
checkWritebackRunProjectId,
|
|
1935
1883
|
buildNoTraceUndoBlockedResult,
|
|
1936
1884
|
buildReviewingRequiredBlockedResult,
|