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
|
@@ -1,13 +1,48 @@
|
|
|
1
1
|
(function initUndoOperations(root, factory) {
|
|
2
2
|
if (typeof module === 'object' && module.exports) {
|
|
3
|
-
module.exports = factory();
|
|
3
|
+
module.exports = factory(require('./staleGuard'));
|
|
4
4
|
} else {
|
|
5
|
-
root.CodexOverleafUndoOperations = factory();
|
|
5
|
+
root.CodexOverleafUndoOperations = factory(root.CodexOverleafStaleGuard);
|
|
6
6
|
}
|
|
7
|
-
})(typeof globalThis !== 'undefined' ? globalThis : window, function undoOperationsFactory() {
|
|
7
|
+
})(typeof globalThis !== 'undefined' ? globalThis : window, function undoOperationsFactory(StaleGuard) {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
function buildUndoOperations(project, appliedOperations) {
|
|
11
|
+
return buildUndoPlan(project, appliedOperations).undoOperations;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getUndoUnavailableFiles(run = {}) {
|
|
15
|
+
const payload = run.recoveryPayload || run;
|
|
16
|
+
const files = new Map();
|
|
17
|
+
for (const operation of payload.appliedOperations || []) {
|
|
18
|
+
const reason = operation?.undoUnavailable;
|
|
19
|
+
if (typeof reason?.path === 'string' && reason.path === operation?.path
|
|
20
|
+
&& ['stale_source_changed', 'snapshot_undo_unavailable'].includes(reason.code)) {
|
|
21
|
+
files.set(reason.path, { path: reason.path, code: reason.code });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return [...files.values()];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function withAvailableUndo(run = {}) {
|
|
28
|
+
const unavailable = new Set(getUndoUnavailableFiles(run).map(item => item.path));
|
|
29
|
+
if (!unavailable.size) return run;
|
|
30
|
+
const result = { ...run };
|
|
31
|
+
for (const field of ['undoOperations', 'undoBaseFiles', 'undoExpectedFiles', 'undoTrackedChanges', 'trackedChangeCaptures']) {
|
|
32
|
+
result[field] = (run[field] || []).filter(item => !unavailable.has(item?.path) && !unavailable.has(item?.to));
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Fallback history keeps only bounded reasons, never document bytes or
|
|
38
|
+
// authority to reconstruct a withheld checkpoint.
|
|
39
|
+
function compactUnavailableUndoOperations(run = {}) {
|
|
40
|
+
if (run.forkSnapshot || run.undoStatus === 'applied' || ['accepted', 'rejected'].includes(run.trackedChangeStatus)) return [];
|
|
41
|
+
return getUndoUnavailableFiles(run).filter(item => item.path.length <= 1024).slice(0, 100)
|
|
42
|
+
.map(item => ({ type: 'edit', path: item.path, undoUnavailable: item }));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildUndoPlan(project, appliedOperations) {
|
|
11
46
|
const workingFilesByPath = new Map();
|
|
12
47
|
for (const file of project?.files || []) {
|
|
13
48
|
if (typeof file?.path === 'string' && typeof file.content === 'string') {
|
|
@@ -15,7 +50,12 @@
|
|
|
15
50
|
}
|
|
16
51
|
}
|
|
17
52
|
|
|
53
|
+
const sourceFilesByPath = new Map(workingFilesByPath);
|
|
18
54
|
const undo = [];
|
|
55
|
+
const inverseBases = new Map();
|
|
56
|
+
const mappedPaths = new Set((appliedOperations || []).filter(operation => Array.isArray(operation?.appliedPatches))
|
|
57
|
+
.map(operation => operation.path));
|
|
58
|
+
const unavailable = new Map(getUndoUnavailableFiles({ appliedOperations }).map(item => [item.path, item.code]));
|
|
19
59
|
const fullRestoreEditPaths = new Set();
|
|
20
60
|
|
|
21
61
|
for (const operation of appliedOperations || []) {
|
|
@@ -29,22 +69,33 @@
|
|
|
29
69
|
}
|
|
30
70
|
const currentBeforeEdit = workingFilesByPath.get(operation.path);
|
|
31
71
|
if (!fullRestoreEditPaths.has(operation.path)) {
|
|
32
|
-
const
|
|
72
|
+
const hasAppliedPatches = Array.isArray(operation.appliedPatches) && typeof operation.verifiedContent === 'string';
|
|
73
|
+
const inversePatches = hasAppliedPatches
|
|
74
|
+
? buildVerifiedInversePatches(operation.appliedPatches, operation.verifiedContent)
|
|
75
|
+
: buildInverseTextPatches(sourceFilesByPath.get(operation.path), operation.patches,
|
|
76
|
+
operation.verifiedContent, operation.path);
|
|
77
|
+
let inverse;
|
|
33
78
|
if (inversePatches.length) {
|
|
34
|
-
|
|
79
|
+
inverse = normalizeUndoOperation({
|
|
35
80
|
type: 'edit',
|
|
36
81
|
path: operation.path,
|
|
37
82
|
patches: inversePatches,
|
|
38
83
|
reason: 'Undo edit'
|
|
39
|
-
})
|
|
40
|
-
} else {
|
|
84
|
+
});
|
|
85
|
+
} else if (!hasAppliedPatches) {
|
|
41
86
|
fullRestoreEditPaths.add(operation.path);
|
|
42
|
-
|
|
87
|
+
inverse = normalizeUndoOperation({
|
|
43
88
|
type: 'edit',
|
|
44
89
|
path: operation.path,
|
|
45
90
|
replaceAll: currentBeforeEdit,
|
|
46
91
|
reason: 'Undo edit'
|
|
47
|
-
})
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
unavailable.set(operation.path, 'snapshot_undo_unavailable');
|
|
95
|
+
}
|
|
96
|
+
if (inverse) {
|
|
97
|
+
undo.push(inverse);
|
|
98
|
+
if (typeof operation.verifiedContent === 'string') inverseBases.set(inverse, operation.verifiedContent);
|
|
48
99
|
}
|
|
49
100
|
}
|
|
50
101
|
} else if (operation.type === 'create') {
|
|
@@ -70,10 +121,13 @@
|
|
|
70
121
|
if (!operation.path || !operation.to) {
|
|
71
122
|
continue;
|
|
72
123
|
}
|
|
124
|
+
if (operation.moveProof && !operation.moveProof.targetEntityId) continue;
|
|
73
125
|
undo.push(normalizeUndoOperation({
|
|
74
126
|
type: 'move',
|
|
75
127
|
path: operation.to,
|
|
76
128
|
to: operation.path,
|
|
129
|
+
...(operation.moveProof ? { moveProof: { ...operation.moveProof,
|
|
130
|
+
sourceEntityId: operation.moveProof.targetEntityId, targetEntityId: operation.moveProof.sourceEntityId } } : {}),
|
|
77
131
|
reason: 'Undo move'
|
|
78
132
|
}));
|
|
79
133
|
} else if (operation.type === 'delete') {
|
|
@@ -89,12 +143,57 @@
|
|
|
89
143
|
}
|
|
90
144
|
|
|
91
145
|
applyOperationToFiles(workingFilesByPath, operation);
|
|
146
|
+
applyOperationToFiles(sourceFilesByPath, { ...operation, verifiedContent: undefined });
|
|
92
147
|
}
|
|
93
148
|
|
|
94
|
-
|
|
149
|
+
// Each inverse was measured in its own verified post-image. Compose in
|
|
150
|
+
// reverse order against the latest image, preserving changes between
|
|
151
|
+
// entries. Empty inverse insertions must never reuse an older offset.
|
|
152
|
+
const composed = [];
|
|
153
|
+
for (const inverse of undo.reverse()) {
|
|
154
|
+
let operation = inverse;
|
|
155
|
+
if (mappedPaths.has(inverse.path) && inverseBases.has(inverse)) {
|
|
156
|
+
const current = workingFilesByPath.get(inverse.path);
|
|
157
|
+
const base = StaleGuard.buildBaseFileLookup([{ path: inverse.path, content: inverseBases.get(inverse) }]);
|
|
158
|
+
const fresh = StaleGuard.checkOperationFreshness(inverse, current, base);
|
|
159
|
+
const prepared = fresh.ok && inverse.patches
|
|
160
|
+
? fresh.preparedPatches || StaleGuard.prepareOperationPatches(inverse, current, base) : fresh;
|
|
161
|
+
if (typeof current !== 'string' || !prepared.ok) {
|
|
162
|
+
unavailable.set(inverse.path, 'stale_source_changed');
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (inverse.patches) operation = { ...inverse, patches: prepared.patches };
|
|
166
|
+
}
|
|
167
|
+
composed.push(operation);
|
|
168
|
+
applyOperationToFiles(workingFilesByPath, operation);
|
|
169
|
+
}
|
|
170
|
+
return { undoOperations: composed.filter(operation => !unavailable.has(operation.path)),
|
|
171
|
+
undoUnavailable: [...unavailable].map(([path, code]) => ({ path, code })) };
|
|
95
172
|
}
|
|
96
173
|
|
|
97
|
-
|
|
174
|
+
// Invert the ranges actually dispatched to C, not the original Native
|
|
175
|
+
// coordinates in B. Verify against the observed post-image before storing
|
|
176
|
+
// recovery; invalid evidence must not fall back to overwriting C with B.
|
|
177
|
+
function buildVerifiedInversePatches(patches, postImage) {
|
|
178
|
+
const beforeLength = patches.reduce((length, patch) =>
|
|
179
|
+
length + Number(patch?.to) - Number(patch?.from) - String(patch?.insert ?? '').length, postImage.length);
|
|
180
|
+
if (!Number.isSafeInteger(beforeLength) || beforeLength < 0) return [];
|
|
181
|
+
const normalized = normalizeTextPatches(patches, beforeLength);
|
|
182
|
+
if (!normalized.ok || normalized.patches.some(patch => patch.expected.length !== patch.to - patch.from)) return [];
|
|
183
|
+
const inverse = invertPatches(normalized.patches);
|
|
184
|
+
return applyTextPatches(postImage, inverse).ok ? inverse : [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function invertPatches(patches) {
|
|
188
|
+
let offset = 0;
|
|
189
|
+
return patches.map(patch => {
|
|
190
|
+
const from = patch.from + offset;
|
|
191
|
+
offset += patch.insert.length - (patch.to - patch.from);
|
|
192
|
+
return { from, to: from + patch.insert.length, expected: patch.insert, insert: patch.expected };
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function buildInverseTextPatches(textBeforeEdit, patches, verifiedContent, path) {
|
|
98
197
|
if (!Array.isArray(patches) || !patches.length) {
|
|
99
198
|
return [];
|
|
100
199
|
}
|
|
@@ -107,26 +206,57 @@
|
|
|
107
206
|
return [];
|
|
108
207
|
}
|
|
109
208
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
209
|
+
const inverse = invertPatches(normalized.patches);
|
|
210
|
+
if (typeof verifiedContent !== 'string' || verifiedContent === applied.text) return inverse;
|
|
211
|
+
// The stored checkpoint uses the verified editor post-image. Its inverse
|
|
212
|
+
// must use those same offsets, while Native patches retain their source
|
|
213
|
+
// representation for later operations in the batch.
|
|
214
|
+
const prepared = StaleGuard.prepareStoredInversePatches({ type: 'edit', path, patches: inverse }, verifiedContent,
|
|
215
|
+
StaleGuard.buildBaseFileLookup([{ path, content: applied.text }]));
|
|
216
|
+
return prepared.ok && applyTextPatches(verifiedContent, prepared.patches).ok ? prepared.patches : [];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function selectExpectedFilesForTrackedUndo(project, operations = [], trackedChanges = [], previousExpectedFiles = []) {
|
|
220
|
+
const checkpoint = buildUndoCheckpoint(project, operations);
|
|
221
|
+
const unavailable = new Set(checkpoint.undoUnavailable.map(item => item.path));
|
|
222
|
+
const mappedPaths = new Set(operations.filter(operation => Array.isArray(operation?.appliedPatches)).map(operation => operation.path));
|
|
223
|
+
const paths = new Set();
|
|
224
|
+
for (const change of trackedChanges || []) if (change?.path) paths.add(change.path);
|
|
225
|
+
for (const operation of operations || []) {
|
|
226
|
+
if (operation?.path) paths.add(operation.path);
|
|
227
|
+
if (operation?.to) paths.add(operation.to);
|
|
228
|
+
}
|
|
229
|
+
const expectedByPath = new Map();
|
|
230
|
+
for (const file of previousExpectedFiles || []) {
|
|
231
|
+
if (paths.has(file?.path) && !unavailable.has(file.path) && !mappedPaths.has(file.path)
|
|
232
|
+
&& typeof file?.content === 'string' && !expectedByPath.has(file.path)) {
|
|
233
|
+
expectedByPath.set(file.path, { path: file.path, content: file.content });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Reconstruct the pre-image with the same inverse patches as plain Undo.
|
|
237
|
+
// This preserves the editor's unchanged line endings and collaborator
|
|
238
|
+
// content, including when the ZIP baseline used a different representation.
|
|
239
|
+
const hasVerifiedPatches = operations.some(operation => typeof operation?.verifiedContent === 'string'
|
|
240
|
+
&& Array.isArray(operation.patches) && operation.patches.length);
|
|
241
|
+
const restored = hasVerifiedPatches ? buildExpectedFilesAfterOperations({ files: checkpoint.undoBaseFiles }, checkpoint.undoOperations) : new Map();
|
|
242
|
+
for (const file of project?.files || []) {
|
|
243
|
+
if (paths.has(file?.path) && !unavailable.has(file.path) && typeof file?.content === 'string' && !expectedByPath.has(file.path)) {
|
|
244
|
+
expectedByPath.set(file.path, { path: file.path, content: restored.get(file.path) ?? file.content });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return Array.from(expectedByPath.values());
|
|
122
248
|
}
|
|
123
249
|
|
|
124
250
|
function buildUndoCheckpoint(project, appliedOperations) {
|
|
125
|
-
const undoOperations =
|
|
251
|
+
const { undoOperations, undoUnavailable } = buildUndoPlan(project, appliedOperations);
|
|
252
|
+
const unavailable = new Map(undoUnavailable.map(item => [item.path, item]));
|
|
126
253
|
const expectedFiles = buildExpectedFilesAfterOperations(project, appliedOperations);
|
|
127
254
|
return {
|
|
128
255
|
undoOperations,
|
|
129
|
-
undoBaseFiles: filesMapToList(selectUndoBaseFiles(expectedFiles, undoOperations))
|
|
256
|
+
undoBaseFiles: filesMapToList(selectUndoBaseFiles(expectedFiles, undoOperations)),
|
|
257
|
+
undoUnavailable,
|
|
258
|
+
appliedOperations: (appliedOperations || []).map(operation => unavailable.has(operation?.path)
|
|
259
|
+
? { ...operation, undoUnavailable: unavailable.get(operation.path) } : operation)
|
|
130
260
|
};
|
|
131
261
|
}
|
|
132
262
|
|
|
@@ -140,9 +270,9 @@
|
|
|
140
270
|
const proof = operation?.undoCreatedFile;
|
|
141
271
|
if (operation?.type !== 'delete' || proof?.v !== 1) continue;
|
|
142
272
|
if (proof.kind === 'text' && priorFiles.has(operation.path)) {
|
|
143
|
-
created.set(operation.path, { kind: 'text', content: priorFiles.get(operation.path) });
|
|
273
|
+
created.set(operation.path, { kind: 'text', content: priorFiles.get(operation.path), createdDirectories: proof.createdDirectories });
|
|
144
274
|
} else if (proof.kind === 'binary' && /^[a-f0-9]{64}$/i.test(proof.sha256 || '')) {
|
|
145
|
-
created.set(operation.path, { kind: 'binary', sha256: proof.sha256.toLowerCase() });
|
|
275
|
+
created.set(operation.path, { kind: 'binary', sha256: proof.sha256.toLowerCase(), createdDirectories: proof.createdDirectories });
|
|
146
276
|
}
|
|
147
277
|
}
|
|
148
278
|
for (const operation of appliedOperations || []) {
|
|
@@ -152,17 +282,17 @@
|
|
|
152
282
|
if (originalPaths.has(path) && !created.has(path)) continue;
|
|
153
283
|
if (operation.type === 'create' && typeof operation.content === 'string') {
|
|
154
284
|
created.set(path, { kind: 'text', content: typeof operation.verifiedContent === 'string'
|
|
155
|
-
? operation.verifiedContent : operation.content });
|
|
285
|
+
? operation.verifiedContent : operation.content, createdDirectories: operation.createdDirectories });
|
|
156
286
|
} else {
|
|
157
287
|
const hash = operation.sha256 || operation.assetRef?.sha256;
|
|
158
288
|
if (operation.previousExists !== true && /^[a-f0-9]{64}$/i.test(hash || '')) {
|
|
159
|
-
created.set(path, { kind: 'binary', sha256: hash.toLowerCase() });
|
|
289
|
+
created.set(path, { kind: 'binary', sha256: hash.toLowerCase(), createdDirectories: operation.createdDirectories });
|
|
160
290
|
}
|
|
161
291
|
}
|
|
162
292
|
} else if (operation.type === 'edit' && created.get(path)?.kind === 'text') {
|
|
163
293
|
const files = new Map([[path, created.get(path).content]]);
|
|
164
294
|
applyOperationToFiles(files, operation);
|
|
165
|
-
created.set(path, { kind: 'text', content: files.get(path) });
|
|
295
|
+
created.set(path, { ...created.get(path), kind: 'text', content: files.get(path) });
|
|
166
296
|
} else if (['delete', 'rename', 'move', 'overwrite-binary'].includes(operation.type)) {
|
|
167
297
|
// Existing rename/overwrite recovery retains ownership of these cases.
|
|
168
298
|
created.delete(path);
|
|
@@ -173,17 +303,83 @@
|
|
|
173
303
|
for (const [path, proof] of Array.from(created).reverse()) {
|
|
174
304
|
const undoCreatedFile = proof.kind === 'binary'
|
|
175
305
|
? { v: 1, kind: 'binary', sha256: proof.sha256 } : { v: 1, kind: 'text' };
|
|
306
|
+
if (Array.isArray(proof.createdDirectories) && proof.createdDirectories.length) {
|
|
307
|
+
undoCreatedFile.createdDirectories = proof.createdDirectories.map(item => ({ ...item }));
|
|
308
|
+
}
|
|
176
309
|
undoOperations.push({ ...normalizeUndoOperation({ type: 'delete', path, reason: 'Undo create' }), undoCreatedFile });
|
|
177
310
|
if (proof.kind === 'text') undoBaseFiles.push({ path, content: proof.content });
|
|
178
311
|
}
|
|
312
|
+
// Reviewing has no text insertion/deletion refs for a native metadata move.
|
|
313
|
+
// Keep its identity-bound inverse with the other file-level proofs.
|
|
314
|
+
undoOperations.push(...buildUndoOperations(project, appliedOperations).filter(operation => operation.type === 'move' && operation.moveProof));
|
|
179
315
|
return { undoOperations, undoBaseFiles };
|
|
180
316
|
}
|
|
181
317
|
|
|
318
|
+
function isFileUndoOperation(operation) {
|
|
319
|
+
return operation?.undoCreatedFile?.v === 1
|
|
320
|
+
|| (operation?.type === 'move' && operation.moveProof?.v === 1)
|
|
321
|
+
|| (operation?.type === 'create' && typeof operation.content === 'string');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// A deleted original has no editor post-image. Its inverse is a guarded
|
|
325
|
+
// file creation, backed by both the forward delete and the captured original.
|
|
326
|
+
// This also recovers older Track-on records that omitted that inverse.
|
|
327
|
+
function getFileUndoOperations(run = {}) {
|
|
328
|
+
run = withAvailableUndo(run);
|
|
329
|
+
const operations = (run.undoOperations || []).filter(isFileUndoOperation);
|
|
330
|
+
const before = filesListToMap(run.undoExpectedFiles);
|
|
331
|
+
const after = buildExpectedFilesAfterOperations({ files: run.undoExpectedFiles }, run.appliedOperations);
|
|
332
|
+
const deleted = new Set((run.appliedOperations || []).filter(operation => operation?.type === 'delete'
|
|
333
|
+
&& before.has(operation.path) && !after.has(operation.path)).map(operation => operation.path));
|
|
334
|
+
return [...operations.filter(operation => !deleted.has(operation.path)), ...Array.from(deleted, path =>
|
|
335
|
+
normalizeUndoOperation({ type: 'create', path, content: before.get(path), reason: 'Undo delete' }))];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function prepareRunUndo(run = {}) {
|
|
339
|
+
const original = run;
|
|
340
|
+
run = withAvailableUndo(run);
|
|
341
|
+
const files = getFileUndoOperations(run);
|
|
342
|
+
const restored = new Set(files.filter(operation => operation.type === 'create').map(operation => operation.path));
|
|
343
|
+
const pending = [...files], operations = [];
|
|
344
|
+
for (const operation of run.undoOperations || []) {
|
|
345
|
+
if (isFileUndoOperation(operation)) {
|
|
346
|
+
const index = pending.findIndex(file => file.path === operation.path && file.type === operation.type);
|
|
347
|
+
if (index >= 0) operations.push(...pending.splice(index, 1));
|
|
348
|
+
} else if (!restored.has(operation.path)) operations.push(operation);
|
|
349
|
+
}
|
|
350
|
+
return { ...original, undoOperations: [...operations, ...pending],
|
|
351
|
+
...(run !== original ? { undoBaseFiles: run.undoBaseFiles, undoExpectedFiles: run.undoExpectedFiles } : {}) };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function textUndoRun(run = {}) {
|
|
355
|
+
run = withAvailableUndo(run);
|
|
356
|
+
const restored = new Set(getFileUndoOperations(run).flatMap(operation => operation.type === 'create' ? [operation.path]
|
|
357
|
+
: operation.type === 'move' ? [operation.path, operation.to] : []));
|
|
358
|
+
return { ...run,
|
|
359
|
+
undoExpectedFiles: (run.undoExpectedFiles || []).filter(file => !restored.has(file.path)),
|
|
360
|
+
undoTrackedChanges: (run.undoTrackedChanges || []).filter(ref => !restored.has(ref.path))
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function mergeFileUndoResult(textResult, fileResult, operations) {
|
|
365
|
+
const applied = [...(textResult.applied || []), ...(fileResult.applied || [])];
|
|
366
|
+
const skipped = [...(textResult.skipped || []), ...(fileResult.skipped || [])];
|
|
367
|
+
for (const operation of operations) {
|
|
368
|
+
if (!(fileResult.applied || []).some(entry => entry.operation?.path === operation.path
|
|
369
|
+
&& entry.operation?.type === operation.type && entry.result?.ok === true && entry.result?.verified === true
|
|
370
|
+
&& entry.result.verification === 'overleaf-zip')
|
|
371
|
+
&& !(fileResult.skipped || []).some(entry => entry.operation?.path === operation.path)) {
|
|
372
|
+
skipped.push({ operation, result: { ok: false, code: 'undo_operation_failed',
|
|
373
|
+
reason: 'The file restoration has no verified server receipt.' } });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return { ...textResult, ok: textResult.ok !== false && fileResult.ok !== false && !skipped.length, applied, skipped };
|
|
377
|
+
}
|
|
378
|
+
|
|
182
379
|
// Consume only independently verified rollback receipts. Forward writes
|
|
183
380
|
// remain audit evidence; recovery arrays describe only unfinished work.
|
|
184
381
|
function advanceUndoCheckpoint(run = {}, result = {}, verifyText = () => false) {
|
|
185
382
|
const operations = Array.isArray(run?.undoOperations) ? run.undoOperations : [];
|
|
186
|
-
if (!operations.some(operation => operation?.undoCreatedFile?.v === 1)) return run;
|
|
187
383
|
const applied = Array.isArray(result.applied) ? result.applied : [];
|
|
188
384
|
const skipped = Array.isArray(result.skipped) ? result.skipped : [];
|
|
189
385
|
const failed = path => skipped.some(entry => {
|
|
@@ -201,8 +397,22 @@
|
|
|
201
397
|
&& JSON.stringify(entry.operation.undoCreatedFile) === JSON.stringify(operation.undoCreatedFile)
|
|
202
398
|
&& entry.result?.ok === true && entry.result.verified === true
|
|
203
399
|
&& entry.result.verification === 'overleaf-zip')).map(operation => operation.path));
|
|
400
|
+
const restoredPaths = new Set(operations.filter(operation => operation?.type === 'create'
|
|
401
|
+
&& !failed(operation.path) && applied.some(entry => entry?.operation?.type === 'create'
|
|
402
|
+
&& entry.operation.path === operation.path && entry.operation.content === operation.content
|
|
403
|
+
&& entry.result?.ok === true && entry.result.verified === true
|
|
404
|
+
&& entry.result.verification === 'overleaf-zip')).map(operation => operation.path));
|
|
405
|
+
const movedPaths = new Set(operations.filter(operation => operation?.type === 'move' && operation.moveProof?.v === 1
|
|
406
|
+
&& !failed(operation.path) && applied.some(entry => entry?.operation?.type === 'move'
|
|
407
|
+
&& entry.operation.path === operation.path && entry.operation.to === operation.to
|
|
408
|
+
&& JSON.stringify(entry.operation.moveProof) === JSON.stringify(operation.moveProof)
|
|
409
|
+
&& entry.result?.ok === true && entry.result.verified === true && entry.result.verification === 'overleaf-zip'))
|
|
410
|
+
.flatMap(operation => [operation.path, operation.to]));
|
|
411
|
+
for (const path of restoredPaths) textPaths.add(path);
|
|
412
|
+
for (const path of movedPaths) textPaths.add(path);
|
|
204
413
|
if (!textPaths.size && !deletedPaths.size) return run;
|
|
205
|
-
const pending = operations.filter(operation => !(operation?.type === 'edit' && textPaths.has(operation.path))
|
|
414
|
+
const pending = operations.filter(operation => !restoredPaths.has(operation.path) && !(operation?.type === 'edit' && textPaths.has(operation.path))
|
|
415
|
+
&& !(operation?.type === 'move' && movedPaths.has(operation.path))
|
|
206
416
|
&& !(operation?.type === 'delete' && operation.undoCreatedFile?.v === 1 && deletedPaths.has(operation.path)));
|
|
207
417
|
const needed = new Set(pending.flatMap(operation => [operation?.path, operation?.to]).filter(Boolean));
|
|
208
418
|
return { ...run,
|
|
@@ -216,6 +426,7 @@
|
|
|
216
426
|
}
|
|
217
427
|
|
|
218
428
|
function buildSnapshotRestoreUndo(run = {}) {
|
|
429
|
+
run = withAvailableUndo(run);
|
|
219
430
|
const undoOperations = Array.isArray(run.undoOperations) ? run.undoOperations : [];
|
|
220
431
|
const originalByPath = buildOriginalFilesForSnapshotUndo(run, undoOperations);
|
|
221
432
|
if (!originalByPath.size) {
|
|
@@ -396,6 +607,7 @@
|
|
|
396
607
|
insert: String(patch.insert ?? '')
|
|
397
608
|
}));
|
|
398
609
|
}
|
|
610
|
+
if (operation.type === 'move' && operation.moveProof?.v === 1) normalized.moveProof = JSON.parse(JSON.stringify(operation.moveProof));
|
|
399
611
|
return normalized;
|
|
400
612
|
}
|
|
401
613
|
|
|
@@ -454,11 +666,20 @@
|
|
|
454
666
|
}
|
|
455
667
|
|
|
456
668
|
return {
|
|
669
|
+
getUndoUnavailableFiles,
|
|
670
|
+
withAvailableUndo,
|
|
671
|
+
compactUnavailableUndoOperations,
|
|
672
|
+
isFileUndoOperation,
|
|
673
|
+
getFileUndoOperations,
|
|
674
|
+
prepareRunUndo,
|
|
675
|
+
textUndoRun,
|
|
676
|
+
mergeFileUndoResult,
|
|
457
677
|
buildUndoCheckpoint,
|
|
458
678
|
buildCreatedFileUndoCheckpoint,
|
|
459
679
|
advanceUndoCheckpoint,
|
|
460
680
|
buildExpectedFilesAfterOperations,
|
|
461
681
|
buildUndoOperations,
|
|
682
|
+
selectExpectedFilesForTrackedUndo,
|
|
462
683
|
buildSnapshotRestoreUndo
|
|
463
684
|
};
|
|
464
685
|
});
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
deadlineAt: finiteNumber(value.deadlineAt),
|
|
79
79
|
heartbeatAt: finiteNumber(value.heartbeatAt),
|
|
80
80
|
postponeUntil: finiteNumber(value.postponeUntil),
|
|
81
|
+
cooldownUntil: finiteNumber(value.cooldownUntil),
|
|
81
82
|
code: safeToken(value.code),
|
|
82
83
|
message: String(value.message || '').replace(/\s+/g, ' ').trim().slice(0, 300),
|
|
83
84
|
manualReason: normalizeManualReason(value.manualReason)
|
|
@@ -118,9 +119,6 @@
|
|
|
118
119
|
const state = normalizeUpdateState(updateValue, options.currentVersion);
|
|
119
120
|
const consent = normalizeConsentState(consentValue);
|
|
120
121
|
const newer = compareStableVersions(state.latestVersion, state.currentVersion) > 0;
|
|
121
|
-
const snoozed = newer &&
|
|
122
|
-
consent.snoozedVersion === state.latestVersion &&
|
|
123
|
-
consent.snoozedUntil > now;
|
|
124
122
|
const execution = EXECUTION_STATES.has(state.state);
|
|
125
123
|
const available = state.state === 'update_available' && newer;
|
|
126
124
|
// A newer release that only a reinstall can apply. It is shown even after
|
|
@@ -129,7 +127,11 @@
|
|
|
129
127
|
// still names the old one, and the notice must clear by itself.
|
|
130
128
|
const runningVersion = safeVersion(options.currentVersion) || state.currentVersion;
|
|
131
129
|
const manualInstall = state.state === 'manual_install_required'
|
|
132
|
-
&& compareStableVersions(state.latestVersion, runningVersion) > 0
|
|
130
|
+
&& (compareStableVersions(state.latestVersion, runningVersion) > 0
|
|
131
|
+
|| (state.code === 'prerelease_promotion_requires_manual_update'
|
|
132
|
+
&& state.latestVersion === runningVersion));
|
|
133
|
+
const snoozed = (newer || manualInstall) &&
|
|
134
|
+
consent.snoozedVersion === state.latestVersion && consent.snoozedUntil > now;
|
|
133
135
|
const progress = getProgressModel(state);
|
|
134
136
|
const quietFailure = state.state === 'failed' &&
|
|
135
137
|
state.initiatedBy !== 'manual' &&
|
|
@@ -12,21 +12,35 @@
|
|
|
12
12
|
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function verifiedRollback(status = {}) {
|
|
16
|
+
const transaction = status.transaction;
|
|
17
|
+
const pair = status.restoredPair;
|
|
18
|
+
const digest = value => typeof value === 'string' && /^[0-9a-f]{64}$/.test(value);
|
|
19
|
+
return Boolean(transaction?.id && transaction.state === 'rolled_back'
|
|
20
|
+
&& version(transaction.sourceVersion) && version(transaction.targetVersion)
|
|
21
|
+
&& status.installedAligned === true && status.activeVersion === transaction.sourceVersion
|
|
22
|
+
&& pair?.extensionVersion === transaction.sourceVersion && pair.nativeVersion === transaction.sourceVersion
|
|
23
|
+
&& digest(pair.extensionSha256) && digest(pair.nativeSha256)
|
|
24
|
+
&& (!transaction.bootstrapRevision || digest(pair.bootstrapSha256)));
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
function inspectInstalledRuntime(status = {}, options = {}) {
|
|
16
28
|
const result = { installedVersion: version(status.activeVersion), nativeVersion: version(status.runtimeVersion),
|
|
17
29
|
extensionVersion: version(options.extensionVersion), runtimeVersion: version(options.runtimeVersion), state: 'unknown' };
|
|
18
30
|
if (!status.managed || !result.installedVersion || !result.nativeVersion ||
|
|
19
31
|
!result.extensionVersion || !result.runtimeVersion) return result;
|
|
20
32
|
if (['authorized', 'bound'].includes(status.authorization?.state) ||
|
|
21
|
-
['staged', 'activation_pending', 'applying', 'awaiting_health'].includes(status.transaction?.state)) {
|
|
33
|
+
['staged', 'activation_pending', 'applying', 'bootstrap_pending', 'bootstrap_rolling_back', 'awaiting_health', 'rolling_back'].includes(status.transaction?.state)) {
|
|
22
34
|
return { ...result, state: 'transaction_active' };
|
|
23
35
|
}
|
|
24
36
|
if (status.installedAligned !== true) return { ...result, state: 'repair_required' };
|
|
25
37
|
const loaded = [result.nativeVersion, result.extensionVersion, result.runtimeVersion];
|
|
26
38
|
if (loaded.every(value => value === result.installedVersion)) return { ...result, state: 'aligned' };
|
|
27
|
-
|
|
39
|
+
const rollbackReload = verifiedRollback(status) && loaded.every(value =>
|
|
40
|
+
value === status.transaction.sourceVersion || value === status.transaction.targetVersion);
|
|
41
|
+
return { ...result, state: rollbackReload || loaded.every(value => compare(result.installedVersion, value) >= 0)
|
|
28
42
|
? 'reload_required' : 'repair_required' };
|
|
29
43
|
}
|
|
30
44
|
|
|
31
|
-
return Object.freeze({ inspectInstalledRuntime });
|
|
45
|
+
return Object.freeze({ inspectInstalledRuntime, verifiedRollback });
|
|
32
46
|
});
|
|
@@ -18,6 +18,22 @@
|
|
|
18
18
|
if (typeof input[key] === 'string') op[key] = input[key];
|
|
19
19
|
}
|
|
20
20
|
if (Number.isSafeInteger(input.size)) op.size = input.size;
|
|
21
|
+
if (input.moveProof !== undefined) {
|
|
22
|
+
const proof = input.moveProof;
|
|
23
|
+
if (input.type !== 'move' || proof?.v !== 1 || !['text', 'binary'].includes(proof.kind)
|
|
24
|
+
|| !/^[a-f0-9]{64}$/i.test(proof.sha256 || '')) return null;
|
|
25
|
+
op.moveProof = { v: 1, kind: proof.kind, sha256: proof.sha256.toLowerCase() };
|
|
26
|
+
if (proof.entityType !== undefined) {
|
|
27
|
+
if (!['doc', 'file'].includes(proof.entityType)) return null;
|
|
28
|
+
op.moveProof.entityType = proof.entityType;
|
|
29
|
+
}
|
|
30
|
+
for (const key of ['sourceEntityId', 'targetEntityId']) {
|
|
31
|
+
if (proof[key] !== undefined) {
|
|
32
|
+
if (typeof proof[key] !== 'string' || !proof[key] || proof[key].length > 160 || /[\0\r\n]/.test(proof[key])) return null;
|
|
33
|
+
op.moveProof[key] = proof[key];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
21
37
|
if (Array.isArray(input.patches)) {
|
|
22
38
|
if (input.patches.length > 1000) return null;
|
|
23
39
|
op.patches = input.patches.map(p => ({ from: p.from, to: p.to, expected: String(p.expected ?? ''), insert: String(p.insert ?? '') }));
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
(function initCodexOverleafWritebackOperationEvidence(root, factory) {
|
|
2
2
|
if (typeof module === 'object' && module.exports) {
|
|
3
|
-
module.exports = factory();
|
|
3
|
+
module.exports = factory(require('./failureReasons'));
|
|
4
4
|
} else {
|
|
5
|
-
root.CodexOverleafModuleRegistry.define('WritebackOperationEvidence', [], factory);
|
|
5
|
+
root.CodexOverleafModuleRegistry.define('WritebackOperationEvidence', ['FailureReasons'], factory);
|
|
6
6
|
}
|
|
7
|
-
})(typeof window !== 'undefined' ? window : globalThis, function writebackOperationEvidenceFactory() {
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function writebackOperationEvidenceFactory(FailureReasons) {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
function normalizeOperationEvidence(operations = [], applyResult = {}) {
|
|
@@ -99,18 +99,7 @@
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
function normalizeFailure(result, operation) {
|
|
102
|
-
|
|
103
|
-
if (!result?.code) return null;
|
|
104
|
-
return {
|
|
105
|
-
code: result.code,
|
|
106
|
-
stage: result.stage || 'write',
|
|
107
|
-
severity: result.severity || 'error',
|
|
108
|
-
userMessage: result.reason || result.message || result.code,
|
|
109
|
-
retryable: result.retryable === true,
|
|
110
|
-
file: operation?.path || operation?.to || '',
|
|
111
|
-
changedDocument: result.changedDocument === true,
|
|
112
|
-
terminalState: result.terminalState || ''
|
|
113
|
-
};
|
|
102
|
+
return FailureReasons.normalizeWritebackFailure(result, operation);
|
|
114
103
|
}
|
|
115
104
|
|
|
116
105
|
function normalizeTrackedChanges(value) {
|