codex-overleaf-link 2.5.0 → 2.5.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 +38 -26
- package/README.zh-CN.md +39 -27
- 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 +206 -53
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +74 -14
- package/extension/src/backgroundUpdateCoordinator.js +118 -21
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +8 -2
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +93 -45
- package/extension/src/content/composerPanel.js +18 -0
- package/extension/src/content/contentRuntime.js +661 -676
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +30 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +6879 -2786
- package/extension/src/content/generated/content.bundle.meta.json +11 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +6 -0
- package/extension/src/content/localSkillsPanel.js +102 -12
- package/extension/src/content/markdownText.js +5 -6
- package/extension/src/content/modelPicker.js +7 -4
- package/extension/src/content/nativeCompatibilityController.js +45 -20
- package/extension/src/content/otWarmMirror.js +4 -5
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +15 -2
- package/extension/src/content/panelMaintenance.js +40 -12
- package/extension/src/content/panelRenderer.js +6 -0
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +39 -2
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +71 -13
- package/extension/src/content/recentProjects.js +11 -5
- package/extension/src/content/runActivitySummary.js +40 -6
- package/extension/src/content/runCompletionMeta.js +344 -0
- package/extension/src/content/runController.js +37 -14
- package/extension/src/content/runFailureNotice.js +21 -12
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runQueueScheduler.js +148 -42
- package/extension/src/content/runRecoveryActions.js +146 -0
- package/extension/src/content/runResultActions.js +8 -125
- package/extension/src/content/runReviewController.js +574 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +146 -153
- package/extension/src/content/scopedPersistencePanelState.js +51 -6
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +140 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +1 -1
- package/extension/src/content/settingsPanel.js +7 -3
- package/extension/src/content/settingsWorkbench.js +5 -0
- package/extension/src/content/sharedSessionViewSync.js +45 -8
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +60 -8
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +82 -73
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/fileMover.js +256 -0
- 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/textFileCreator.js +105 -25
- 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 +80 -79
- package/extension/src/pageBridge.js +19 -3
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +23 -0
- package/extension/src/shared/auditRecords.js +2 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +79 -57
- package/extension/src/shared/composerAttachmentDraft.js +151 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +309 -3
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +298 -2
- 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/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +72 -27
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +37 -14
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +118 -13
- package/extension/src/shared/storageDb.js +58 -26
- package/extension/src/shared/storageMigration.js +71 -111
- package/extension/src/shared/storageRunActions.js +209 -26
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +136 -13
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +1 -1
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +38 -38
- package/extension/styles/panel.css +19 -5
- 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 +233 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeResponse.js +10 -0
- package/native-host/src/chatCompletionsBridge.js +21 -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 +31 -12
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +110 -183
- 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/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 +77 -54
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +297 -39
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +1 -1
- 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,7 +359,9 @@
|
|
|
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;
|
|
@@ -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,
|
|
@@ -665,8 +680,11 @@
|
|
|
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 = 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,
|
|
@@ -729,14 +747,18 @@
|
|
|
729
747
|
reason: 'Overleaf left Editing mode before the undo write.' }).skipped[0].result;
|
|
730
748
|
}
|
|
731
749
|
|
|
750
|
+
const finalReviewBlock = options.reviewGuard?.();
|
|
751
|
+
if (finalReviewBlock) return finalReviewBlock;
|
|
732
752
|
const result = Array.isArray(operation.patches) && operation.patches.length
|
|
733
|
-
? replaceActiveEditorPatches(
|
|
753
|
+
? replaceActiveEditorPatches(preparedPatches.patches, nextContent)
|
|
734
754
|
: replaceActiveEditorText(nextContent);
|
|
735
755
|
if (!result.ok) {
|
|
736
756
|
return result;
|
|
737
757
|
}
|
|
738
758
|
|
|
739
|
-
const
|
|
759
|
+
const verifyWaitMs = Math.min(deps.writeVerifyWaitMs ?? 2500, Number.isFinite(options.deadlineAt)
|
|
760
|
+
? Math.max(0, options.deadlineAt - now()) : Infinity);
|
|
761
|
+
const verified = await verifyActiveEditorText(nextContent, operation.path, verifyWaitMs, current);
|
|
740
762
|
if (!verified.ok) {
|
|
741
763
|
const decorated = attachWritebackDebug(verified, 'write_verification', operation, options.baseFileLookup, readActiveEditorText(), {
|
|
742
764
|
initialActivePath: currentPath,
|
|
@@ -776,7 +798,7 @@
|
|
|
776
798
|
// Slow batches are covered by request receipts; Accept replay retains
|
|
777
799
|
// its independent safety window proving that no new tracking was added.
|
|
778
800
|
const trackedDiff = trackReviewingChanges
|
|
779
|
-
? await captureTrackedWrite({ trackedBefore, captureBaseline, operation, beforeContent: current,
|
|
801
|
+
? await captureTrackedWrite({ trackedBefore, captureBaseline, operation: { ...operation, patches: preparedPatches?.patches }, beforeContent: current,
|
|
780
802
|
postContent: nextContent, runProjectId: options.runProjectId, waitMs: deps.trackCaptureWaitMs ?? 35000 })
|
|
781
803
|
: await waitForTrackedChangeDiff(trackedBefore, operationPaths, {
|
|
782
804
|
waitMs: 3600, intervalMs: 180, stopOnFirst: true
|
|
@@ -812,7 +834,7 @@
|
|
|
812
834
|
window.CodexOverleafStaleGuard?.updateExpectedFileContent(
|
|
813
835
|
options.baseFileLookup,
|
|
814
836
|
operation.path,
|
|
815
|
-
nextContent
|
|
837
|
+
preparedPatches?.sourceContent ?? nextContent
|
|
816
838
|
);
|
|
817
839
|
return {
|
|
818
840
|
...result,
|
|
@@ -823,7 +845,6 @@
|
|
|
823
845
|
};
|
|
824
846
|
}
|
|
825
847
|
|
|
826
|
-
|
|
827
848
|
async function verifyActiveEditorText(expected, filePath, waitMs = 1000, before) {
|
|
828
849
|
const deadline = Date.now() + waitMs;
|
|
829
850
|
let actual = readActiveEditorText();
|
|
@@ -1149,7 +1170,6 @@
|
|
|
1149
1170
|
};
|
|
1150
1171
|
}
|
|
1151
1172
|
|
|
1152
|
-
|
|
1153
1173
|
async function applyBinaryAssetOperation(operation, options = {}) {
|
|
1154
1174
|
const freshness = await checkBinaryAssetFreshness(operation, options);
|
|
1155
1175
|
if (!freshness.ok) {
|
|
@@ -1319,9 +1339,7 @@
|
|
|
1319
1339
|
return baseFile.size == null || currentSize == null || baseFile.size === currentSize;
|
|
1320
1340
|
}
|
|
1321
1341
|
|
|
1322
|
-
|
|
1323
|
-
return String(value || '').replace(/\s+/g, '');
|
|
1324
|
-
}
|
|
1342
|
+
const normalizeBase64 = value => String(value || '').replace(/\s+/g, '');
|
|
1325
1343
|
|
|
1326
1344
|
function decodeBase64Bytes(contentBase64) {
|
|
1327
1345
|
try {
|
|
@@ -1372,6 +1390,17 @@
|
|
|
1372
1390
|
}
|
|
1373
1391
|
const initialProjectId = treeOperations.getProjectId?.();
|
|
1374
1392
|
const initialCancellationSequence = readWriteCancellationSequence();
|
|
1393
|
+
if (operation.type === 'move' && operation.moveProof) {
|
|
1394
|
+
if (options.noTraceUndo !== true) return { ok: false, code: 'file_tree_controls_unavailable', changedDocument: false,
|
|
1395
|
+
reason: 'Native file moves are available only to undo a previously recorded move.' };
|
|
1396
|
+
if (typeof deps.moveFile !== 'function') return { ok: false, code: 'file_move_unavailable', changedDocument: false,
|
|
1397
|
+
reason: 'Verified native file moves are unavailable. Reload the extension and Overleaf.' };
|
|
1398
|
+
const result = await deps.moveFile(operation, { noTraceUndo: true, runProjectId: options.runProjectId, deadlineAt: options.deadlineAt,
|
|
1399
|
+
isCurrent: () => treeOperations.getProjectId?.() === initialProjectId
|
|
1400
|
+
&& readWriteCancellationSequence() === initialCancellationSequence && !options.reviewGuard?.() });
|
|
1401
|
+
if (result.ok) recordFileTreeOperationSuccess(operation, options.baseFileLookup);
|
|
1402
|
+
return result;
|
|
1403
|
+
}
|
|
1375
1404
|
if (operation.type === 'create' && options.createBatch && typeof deps.createTextFile === 'function') {
|
|
1376
1405
|
if (options.baseFileLookup?.has(operation.path) && options.retryCreates !== true) {
|
|
1377
1406
|
return checkFileTreeOperationFreshness(operation, options.baseFileLookup);
|
|
@@ -1379,13 +1408,14 @@
|
|
|
1379
1408
|
return deps.createTextFile(operation, { batch: options.createBatch,
|
|
1380
1409
|
allowExistingMatches: options.retryCreates === true,
|
|
1381
1410
|
isCurrent: () => treeOperations.getProjectId?.() === initialProjectId
|
|
1382
|
-
&& readWriteCancellationSequence() === initialCancellationSequence });
|
|
1411
|
+
&& readWriteCancellationSequence() === initialCancellationSequence && !options.reviewGuard?.() });
|
|
1383
1412
|
}
|
|
1384
1413
|
const freshness = await checkFileTreeOperationFreshness(operation, options.baseFileLookup);
|
|
1385
1414
|
if (!freshness.ok) {
|
|
1386
1415
|
return freshness;
|
|
1387
1416
|
}
|
|
1388
1417
|
|
|
1418
|
+
const ownerBlocked = options.reviewGuard?.(); if (ownerBlocked) return ownerBlocked;
|
|
1389
1419
|
const manager = findFileTreeManager();
|
|
1390
1420
|
const methodNames = fileTreeMethodNames(operation.type);
|
|
1391
1421
|
|
|
@@ -1432,7 +1462,7 @@
|
|
|
1432
1462
|
const nativeOperation = { create: deps.createTextFile, delete: deps.deleteTextFile }[operation.type];
|
|
1433
1463
|
if (typeof nativeOperation === 'function' && (operation.type === 'delete' || typeof operation.content === 'string')) {
|
|
1434
1464
|
const isCurrent = () => Boolean(initialProjectId) && treeOperations.getProjectId?.() === initialProjectId
|
|
1435
|
-
&& readWriteCancellationSequence() === initialCancellationSequence;
|
|
1465
|
+
&& readWriteCancellationSequence() === initialCancellationSequence && !options.reviewGuard?.();
|
|
1436
1466
|
const created = await nativeOperation(operation, { isCurrent, expectedContent: options.baseFileLookup?.get(operation.path) });
|
|
1437
1467
|
if (!created.ok) return created;
|
|
1438
1468
|
if (!isCurrent() || created.verified !== true || created.verification !== 'overleaf-zip') {
|
|
@@ -1456,7 +1486,7 @@
|
|
|
1456
1486
|
const text = proof?.v === 1 && proof.kind === 'text' && typeof expected === 'string';
|
|
1457
1487
|
const binary = proof?.v === 1 && proof.kind === 'binary' && /^[a-f0-9]{64}$/i.test(proof.sha256 || '');
|
|
1458
1488
|
const isCurrent = () => Boolean(projectId) && projectId === options.runProjectId
|
|
1459
|
-
&& treeOperations.getProjectId?.() === projectId && readWriteCancellationSequence() === cancellation;
|
|
1489
|
+
&& treeOperations.getProjectId?.() === projectId && readWriteCancellationSequence() === cancellation && !options.reviewGuard?.();
|
|
1460
1490
|
if (!isCurrent() || (!text && !binary) || typeof deps.deleteTextFile !== 'function') {
|
|
1461
1491
|
return { ok: false, code: 'undo_operation_failed', changedDocument: false,
|
|
1462
1492
|
reason: 'Verified created-file deletion is unavailable for this run.' };
|
|
@@ -1653,15 +1683,6 @@
|
|
|
1653
1683
|
}
|
|
1654
1684
|
}
|
|
1655
1685
|
|
|
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
1686
|
function normalizeOperationPaths(operation) {
|
|
1666
1687
|
if (!operation || typeof operation !== 'object') {
|
|
1667
1688
|
return operation;
|
|
@@ -1734,10 +1755,6 @@
|
|
|
1734
1755
|
}
|
|
1735
1756
|
|
|
1736
1757
|
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
1758
|
function normalizeTrackedChangeRefs(refs = []) {
|
|
1742
1759
|
const seen = new Set();
|
|
1743
1760
|
const normalized = [];
|
|
@@ -1796,23 +1813,12 @@
|
|
|
1796
1813
|
|
|
1797
1814
|
async function applyOperationsForBridge(operationsOrOptions, options = {}) {
|
|
1798
1815
|
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.
|
|
1816
|
+
// Both public call shapes require the original project identity.
|
|
1806
1817
|
const writeGuardBlock = checkWritebackRunProjectId(options);
|
|
1807
1818
|
if (writeGuardBlock) return writeGuardBlock;
|
|
1808
1819
|
return applyOperations(operationsOrOptions, options);
|
|
1809
1820
|
}
|
|
1810
1821
|
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
1822
|
const writeGuardBlock = checkWritebackRunProjectId(payload);
|
|
1817
1823
|
if (writeGuardBlock) return writeGuardBlock;
|
|
1818
1824
|
return applyOperations(payload.operations || [], payload);
|
|
@@ -1821,10 +1827,7 @@
|
|
|
1821
1827
|
function checkWritebackRunProjectId(params) {
|
|
1822
1828
|
const runProjectId = params && typeof params.runProjectId === 'string' ? params.runProjectId : '';
|
|
1823
1829
|
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.
|
|
1830
|
+
// Keep operation.path safe for downstream readers of batch-level failures.
|
|
1828
1831
|
return {
|
|
1829
1832
|
ok: false,
|
|
1830
1833
|
applied: [],
|
|
@@ -1916,9 +1919,7 @@
|
|
|
1916
1919
|
};
|
|
1917
1920
|
}
|
|
1918
1921
|
|
|
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.
|
|
1922
|
+
// Tracked lifecycle is loaded by the Page script list or required by Node tests.
|
|
1922
1923
|
const trackedChangesLifecycleModule = (typeof window !== 'undefined' && window.CodexOverleafTrackedChangesLifecycle)
|
|
1923
1924
|
|| (typeof module === 'object' && module.exports ? require('./trackedChangesLifecycle.js') : null)
|
|
1924
1925
|
|| (typeof globalThis !== 'undefined' ? globalThis.CodexOverleafTrackedChangesLifecycle : null);
|
|
@@ -1930,7 +1931,7 @@
|
|
|
1930
1931
|
captureTrackedWrite, reconcileTrackedChangeCapture,
|
|
1931
1932
|
waitForTrackedChangeDiff
|
|
1932
1933
|
} = trackedChangesLifecycleModule.create({
|
|
1933
|
-
window, confirmReviewWriteback: deps.confirmReviewWriteback,
|
|
1934
|
+
window, now, readWriteCancellationSequence, confirmReviewWriteback: deps.confirmReviewWriteback,
|
|
1934
1935
|
checkWritebackRunProjectId,
|
|
1935
1936
|
buildNoTraceUndoBlockedResult,
|
|
1936
1937
|
buildReviewingRequiredBlockedResult,
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
window
|
|
64
64
|
});
|
|
65
65
|
snapshotRouter = requirePageModule('CodexOverleafSnapshotRouter').create({
|
|
66
|
+
getActiveEditorIdentity,
|
|
66
67
|
normalizePath: normalizeSafeProjectPath,
|
|
67
68
|
readActiveEditorText,
|
|
68
69
|
treeOperations,
|
|
@@ -76,12 +77,17 @@
|
|
|
76
77
|
});
|
|
77
78
|
binaryAssetUploader = requirePageModule('CodexOverleafBinaryAssetUploader').create({
|
|
78
79
|
prepareUploadParent: textFileCreator.prepareUploadParent,
|
|
80
|
+
cleanupCreatedDirectories: textFileCreator.cleanupCreatedDirectories,
|
|
81
|
+
isUploadParentCurrent: textFileCreator.isUploadParentCurrent,
|
|
79
82
|
findFolderNode: textFileCreator.findFolderNode,
|
|
80
83
|
document,
|
|
81
84
|
snapshotRouter,
|
|
82
85
|
treeOperations,
|
|
83
86
|
window
|
|
84
87
|
});
|
|
88
|
+
const fileMover = requirePageModule('CodexOverleafFileMover').create({
|
|
89
|
+
window, document, treeOperations, snapshotRouter, textFileCreator, normalizeSafeProjectPath
|
|
90
|
+
});
|
|
85
91
|
writeGuard = requirePageModule('CodexOverleafWriteGuard').create({
|
|
86
92
|
document,
|
|
87
93
|
treeOperations,
|
|
@@ -102,6 +108,7 @@
|
|
|
102
108
|
beginTextCreateBatch: textFileCreator.beginCreateBatch,
|
|
103
109
|
finishTextCreateBatch: textFileCreator.finishCreateBatch,
|
|
104
110
|
deleteTextFile: textFileCreator.deleteFile,
|
|
111
|
+
moveFile: fileMover.moveFile,
|
|
105
112
|
activeEditorIdentityChanged,
|
|
106
113
|
clickNode,
|
|
107
114
|
collectElements,
|
|
@@ -221,7 +228,8 @@
|
|
|
221
228
|
|
|
222
229
|
const pageRpcHandlers = {
|
|
223
230
|
probe,
|
|
224
|
-
cancelActiveWrite() {
|
|
231
|
+
cancelActiveWrite(params = {}) {
|
|
232
|
+
if (params.reviewOwnerToken) return { ok: true, cancelled: requirePageModule('CodexOverleafReviewContext').cancelOwner(params.reviewOwnerToken) };
|
|
225
233
|
const sequence = bumpWriteCancellationSequence();
|
|
226
234
|
textFileCreator.cancelVerifications();
|
|
227
235
|
snapshotRouter.cancelWritebackReads();
|
|
@@ -690,7 +698,14 @@
|
|
|
690
698
|
if (to - from > 20000) return { ok: false, code: 'selection_too_large' };
|
|
691
699
|
const source = editorState.doc.toString();
|
|
692
700
|
const text = source.slice(from, to);
|
|
693
|
-
|
|
701
|
+
// UTF-8 replaces isolated surrogates; exclude them so flank hashes prove exact UTF-16 text.
|
|
702
|
+
if (/[\uD800-\uDFFF]/u.test(source) || /[\uD800-\uDFFF]/u.test(text)) return { ok: false, code: 'selection_unavailable' };
|
|
703
|
+
const hashText = async value => Array.from(new Uint8Array(
|
|
704
|
+
await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(value))
|
|
705
|
+
), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
706
|
+
const [documentHash, prefixHash, suffixHash] = await Promise.all([
|
|
707
|
+
hashText(source), hashText(source.slice(0, from)), hashText(source.slice(to))
|
|
708
|
+
]);
|
|
694
709
|
if (getProjectId() !== projectId || getActiveFilePath() !== path || getCodeMirrorEditorView() !== view
|
|
695
710
|
|| view.state.doc !== editorState.doc || view.state.selection.main.from !== from || view.state.selection.main.to !== to) {
|
|
696
711
|
return { ok: false, code: 'selection_changed' };
|
|
@@ -698,7 +713,8 @@
|
|
|
698
713
|
const captured = { projectId, path, from, to, text,
|
|
699
714
|
lineStart: editorState.doc.lineAt(from).number,
|
|
700
715
|
lineEnd: editorState.doc.lineAt(Math.max(from, to - 1)).number,
|
|
701
|
-
documentHash
|
|
716
|
+
documentHash,
|
|
717
|
+
rebaseEvidence: { v: 1, documentLength: source.length, prefixHash, suffixHash },
|
|
702
718
|
capturedAt: new Date().toISOString() };
|
|
703
719
|
let anchor = null;
|
|
704
720
|
try {
|