codex-overleaf-link 2.3.0 → 2.3.2
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 +23 -23
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +20 -0
- package/extension/src/content/assetTransferBroker.js +169 -0
- package/extension/src/content/composerPanel.js +0 -2
- package/extension/src/content/contentRuntime.js +135 -111
- package/extension/src/content/generated/content.bundle.js +1066 -345
- package/extension/src/content/generated/content.bundle.meta.json +7 -3
- package/extension/src/content/legacyGlobalRegistry.js +3 -0
- package/extension/src/content/markdownText.js +12 -10
- package/extension/src/content/mathText.js +155 -9
- package/extension/src/content/modelPicker.js +2 -2
- package/extension/src/content/modelPickerSupport.js +3 -3
- package/extension/src/content/nativeCompatibilityController.js +2 -2
- package/extension/src/content/otWarmMirror.js +1 -1
- package/extension/src/content/pageBridgeClient.js +1 -0
- package/extension/src/content/runResultActions.js +114 -0
- package/extension/src/content/runTimelineView.js +23 -0
- package/extension/src/content/sessionForkController.js +120 -0
- package/extension/src/content/writebackController.js +3 -0
- package/extension/src/content/writebackOrchestrator.js +5 -50
- package/extension/src/page/binaryAssetUploader.js +343 -0
- package/extension/src/page/trackedChangesLifecycle.js +12 -4
- package/extension/src/page/writebackRouter.js +6 -2
- package/extension/src/pageBridge.js +30 -1
- package/extension/src/shared/agentTranscript.js +10 -3
- package/extension/src/shared/compatibility.js +4 -2
- package/extension/src/shared/i18n.js +18 -4
- package/extension/src/shared/models.js +13 -2
- package/extension/src/shared/pageRpcContract.js +6 -2
- package/extension/src/shared/runExecutionSnapshot.js +14 -4
- package/extension/src/shared/runExecutionSnapshotCodec.js +5 -2
- package/extension/src/shared/scopedPersistenceQueuePolicy.js +20 -1
- package/extension/src/shared/scopedPersistenceReceipt.js +60 -0
- package/extension/src/shared/scopedPersistenceTransaction.js +50 -47
- package/extension/src/shared/sessionState.js +58 -15
- package/extension/src/shared/storageDb.js +6 -5
- package/extension/src/shared/storageRunActions.js +11 -1
- package/extension/src/shared/writebackOperationEvidence.js +16 -6
- package/extension/src/shared/writebackSettlement.js +28 -6
- package/extension/styles/panel.css +79 -8
- package/native-host/src/codexModels.js +6 -2
- package/native-host/src/codexPromptAssembly.js +1 -1
- package/native-host/src/codexSessionRunner.js +5 -1
- package/native-host/src/codexThreadFork.js +103 -0
- package/native-host/src/mirrorWorkspace.js +5 -19
- package/native-host/src/nativeAssetTransfer.js +183 -0
- package/native-host/src/taskRunnerRuntime.js +33 -81
- package/native-host/src/updateManager.js +36 -1
- package/package.json +1 -1
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
let projectSnapshotBridge = null;
|
|
23
23
|
let writebackRouter = null;
|
|
24
24
|
let writeGuard = null;
|
|
25
|
+
let binaryAssetUploader = null;
|
|
25
26
|
const saveState = requirePageModule('CodexOverleafSaveState').create({
|
|
26
27
|
delay,
|
|
27
28
|
detectEditor,
|
|
@@ -66,6 +67,12 @@
|
|
|
66
67
|
treeOperations,
|
|
67
68
|
window
|
|
68
69
|
});
|
|
70
|
+
binaryAssetUploader = requirePageModule('CodexOverleafBinaryAssetUploader').create({
|
|
71
|
+
document,
|
|
72
|
+
snapshotRouter,
|
|
73
|
+
treeOperations,
|
|
74
|
+
window
|
|
75
|
+
});
|
|
69
76
|
writeGuard = requirePageModule('CodexOverleafWriteGuard').create({
|
|
70
77
|
document,
|
|
71
78
|
treeOperations,
|
|
@@ -207,6 +214,10 @@
|
|
|
207
214
|
requireEditing: params.requireEditing === true,
|
|
208
215
|
runProjectId: typeof params.runProjectId === 'string' ? params.runProjectId : ''
|
|
209
216
|
}),
|
|
217
|
+
binaryUploadBegin: params => binaryAssetUploader.begin(params),
|
|
218
|
+
binaryUploadAppend: params => binaryAssetUploader.append(params),
|
|
219
|
+
binaryUploadCommit: params => binaryAssetUploader.commit(params),
|
|
220
|
+
binaryUploadAbort: params => binaryAssetUploader.abort(params),
|
|
210
221
|
jumpToPosition,
|
|
211
222
|
rejectTrackedChanges,
|
|
212
223
|
acceptTrackedChanges,
|
|
@@ -613,7 +624,7 @@
|
|
|
613
624
|
return range;
|
|
614
625
|
}
|
|
615
626
|
|
|
616
|
-
|
|
627
|
+
let focused = editorAdapter.focusActiveEditorRange(range.from, range.to);
|
|
617
628
|
if (!focused.ok) {
|
|
618
629
|
return {
|
|
619
630
|
...focused,
|
|
@@ -621,6 +632,24 @@
|
|
|
621
632
|
};
|
|
622
633
|
}
|
|
623
634
|
|
|
635
|
+
if (params.line !== undefined) {
|
|
636
|
+
// Overleaf can publish the new file path and text before its unstable
|
|
637
|
+
// store swaps in the final CodeMirror view. Re-resolve the editor after
|
|
638
|
+
// two short settlement windows so a first dispatch to the retiring view
|
|
639
|
+
// cannot leave the visible cursor at its old line. Repeating an already
|
|
640
|
+
// successful selection is idempotent.
|
|
641
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
642
|
+
await delay(120);
|
|
643
|
+
if (getActiveFilePath() !== filePath) {
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
const settled = editorAdapter.focusActiveEditorRange(range.from, range.to);
|
|
647
|
+
if (settled.ok) {
|
|
648
|
+
focused = settled;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
624
653
|
return {
|
|
625
654
|
ok: true,
|
|
626
655
|
method: focused.method,
|
|
@@ -727,10 +727,17 @@
|
|
|
727
727
|
if (providerError) {
|
|
728
728
|
return providerError;
|
|
729
729
|
}
|
|
730
|
-
if (/
|
|
730
|
+
if (/suggest_mode_removed|Suggest mode has been removed/i.test(text)) {
|
|
731
|
+
return {
|
|
732
|
+
conclusion: textFor(locale, '建议修改模式已经移除。', 'Suggest mode has been removed.'),
|
|
733
|
+
nextStep: textFor(locale, '请刷新扩展,然后选择“只问不改”或“自动写入”。', 'Reload the extension, then choose Ask or Auto.'),
|
|
734
|
+
failureCode: 'suggest_mode_removed'
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
if (/Mode must be "(?:confirm|ask)" or "auto"/i.test(text)) {
|
|
731
738
|
return {
|
|
732
739
|
conclusion: textFor(locale, '这轮没有写入:当前是“只问不改”,但这个任务需要写入权限。', 'No files were written: this task needs write access, but the current mode is Ask.'),
|
|
733
|
-
nextStep: textFor(locale, '
|
|
740
|
+
nextStep: textFor(locale, '请切换到“自动写入”后重新运行。', 'Switch to Auto and run the task again.'),
|
|
734
741
|
failureCode: null // no direct catalog match — preflight / governance
|
|
735
742
|
};
|
|
736
743
|
}
|
|
@@ -797,7 +804,7 @@
|
|
|
797
804
|
if (/checkpoint/i.test(text)) {
|
|
798
805
|
return {
|
|
799
806
|
conclusion: textFor(locale, '这轮没有自动写入:Codex 没有拿到可恢复版本。', 'This run did not auto-write: Codex did not get a recoverable version.'),
|
|
800
|
-
nextStep: textFor(locale, '
|
|
807
|
+
nextStep: textFor(locale, '请确认 Overleaf Reviewing 已开启后再用“自动写入”。', 'Confirm that Overleaf Reviewing is enabled before using Auto.'),
|
|
801
808
|
failureCode: null // no current catalog code; preflight reviewing issue
|
|
802
809
|
};
|
|
803
810
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
const MIN_NATIVE_VERSION = '1.0.0';
|
|
13
13
|
const MIN_COMPATIBLE_NATIVE_VERSION = '1.0.0';
|
|
14
14
|
const MIN_COMPATIBLE_EXTENSION_VERSION = '1.0.0';
|
|
15
|
-
const BUILD_TARGET_VERSION = '2.3.
|
|
15
|
+
const BUILD_TARGET_VERSION = '2.3.2';
|
|
16
16
|
const DEFAULT_CHROME_EXTENSION_ID = 'illdpneeeopfffmiepaejglgmhpmdhdc';
|
|
17
17
|
const REQUIRED_CAPABILITIES = Object.freeze([
|
|
18
18
|
'bridgePing',
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
'historyClearPlugin',
|
|
27
27
|
'localSkills',
|
|
28
28
|
'mirrorSensitiveScan',
|
|
29
|
-
'providerProfiles'
|
|
29
|
+
'providerProfiles',
|
|
30
|
+
'assetTransfer',
|
|
31
|
+
'threadFork'
|
|
30
32
|
]);
|
|
31
33
|
const UPDATE_AVAILABLE_CAPABILITIES = Object.freeze([
|
|
32
34
|
'bridgePing',
|
|
@@ -94,6 +94,14 @@
|
|
|
94
94
|
deleteSessionHistoryFailedToast: 'Deleted this plugin session, but the matching plugin-local Codex thread history was not fully cleared: {message}',
|
|
95
95
|
deleteSessionNoThreadToast: 'Deleted this plugin session. This old session had no identifiable local Codex thread history, so no local history was cleared.',
|
|
96
96
|
deleteSessionDoneToast: 'Deleted this plugin session.',
|
|
97
|
+
forkRunTitle: 'Fork a new conversation from this turn',
|
|
98
|
+
forkRunUnavailable: 'This turn cannot be forked because its Codex thread position is unavailable.',
|
|
99
|
+
forkRunFailed: 'Could not fork this conversation.',
|
|
100
|
+
forkRunDone: 'Forked conversation created.',
|
|
101
|
+
forkSessionTitle: '{title} (fork)',
|
|
102
|
+
copyConclusionTitle: 'Copy conclusion',
|
|
103
|
+
copyConclusionDone: 'Conclusion copied',
|
|
104
|
+
copyConclusionFailed: 'Could not copy conclusion',
|
|
97
105
|
modeSwitchFailedToast: 'Could not switch mode: {message}',
|
|
98
106
|
diffAccepted: 'Accepted',
|
|
99
107
|
diffRejected: 'Rejected',
|
|
@@ -266,8 +274,6 @@
|
|
|
266
274
|
writeMode: 'Write mode',
|
|
267
275
|
modeAsk: 'Ask',
|
|
268
276
|
modeAskTitle: 'Read and analyze only. Do not write to Overleaf.',
|
|
269
|
-
modeConfirm: 'Suggest',
|
|
270
|
-
modeConfirmTitle: 'Show a change plan first, then write after approval.',
|
|
271
277
|
modeAuto: 'Auto',
|
|
272
278
|
modeAutoTitle: 'Write directly after authorization. Deletes still require confirmation.',
|
|
273
279
|
addContext: 'Add @ context',
|
|
@@ -403,6 +409,7 @@
|
|
|
403
409
|
refreshProbeDone: 'Refreshed: {status}',
|
|
404
410
|
refreshProbeFailed: 'Refresh failed. Reload Overleaf and try again.',
|
|
405
411
|
processedFailed: 'Failed {elapsed}',
|
|
412
|
+
processedCancelled: 'Cancelled {elapsed}',
|
|
406
413
|
processing: 'Running {elapsed}',
|
|
407
414
|
processed: 'Done {elapsed}',
|
|
408
415
|
restoredRunStoppedTitle: 'Stopped tracking this run after a page refresh',
|
|
@@ -607,6 +614,14 @@
|
|
|
607
614
|
deleteSessionHistoryFailedToast: '已删除这个插件会话,但对应的插件本地 Codex 线程历史没有清理完成:{message}',
|
|
608
615
|
deleteSessionNoThreadToast: '已删除这个插件会话;这个旧会话没有可识别的本地 Codex 线程历史,未清理本地历史。',
|
|
609
616
|
deleteSessionDoneToast: '已删除这个插件会话。',
|
|
617
|
+
forkRunTitle: '从此轮分叉一个新对话',
|
|
618
|
+
forkRunUnavailable: '该轮缺少可定位的 Codex thread 节点,无法分叉。',
|
|
619
|
+
forkRunFailed: '无法分叉此对话。',
|
|
620
|
+
forkRunDone: '已创建分叉对话。',
|
|
621
|
+
forkSessionTitle: '{title}(分叉)',
|
|
622
|
+
copyConclusionTitle: '复制结论',
|
|
623
|
+
copyConclusionDone: '已复制结论',
|
|
624
|
+
copyConclusionFailed: '无法复制结论',
|
|
610
625
|
modeSwitchFailedToast: '切换模式失败:{message}',
|
|
611
626
|
diffAccepted: '已接受',
|
|
612
627
|
diffRejected: '已拒绝',
|
|
@@ -779,8 +794,6 @@
|
|
|
779
794
|
writeMode: '写入模式',
|
|
780
795
|
modeAsk: '只问不改',
|
|
781
796
|
modeAskTitle: '只读取和分析,不写入 Overleaf',
|
|
782
|
-
modeConfirm: '建议修改',
|
|
783
|
-
modeConfirmTitle: '先给出修改方案,确认后写入',
|
|
784
797
|
modeAuto: '自动写入',
|
|
785
798
|
modeAutoTitle: '授权后直接写入,删除仍需确认',
|
|
786
799
|
addContext: '添加 @ 上下文',
|
|
@@ -916,6 +929,7 @@
|
|
|
916
929
|
refreshProbeDone: '已重新检测:{status}',
|
|
917
930
|
refreshProbeFailed: '检测失败:请刷新 Overleaf 页面后重试',
|
|
918
931
|
processedFailed: '处理失败 {elapsed}',
|
|
932
|
+
processedCancelled: '已取消 {elapsed}',
|
|
919
933
|
processing: '处理中 {elapsed}',
|
|
920
934
|
processed: '已处理 {elapsed}',
|
|
921
935
|
restoredRunStoppedTitle: '页面刷新后已停止跟踪这轮任务',
|
|
@@ -62,13 +62,13 @@
|
|
|
62
62
|
const normalized = {
|
|
63
63
|
id,
|
|
64
64
|
label,
|
|
65
|
-
reasoningEfforts:
|
|
65
|
+
reasoningEfforts: normalizeReasoningEfforts(model?.reasoningEfforts),
|
|
66
66
|
reasoningPresentation: typeof model?.reasoningPresentation === 'string' ? model.reasoningPresentation : '',
|
|
67
67
|
speedTiers: normalizeSpeedTiers(model?.speedTiers)
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
if (Object.prototype.hasOwnProperty.call(Object(model), 'defaultReasoningEffort')) {
|
|
71
|
-
normalized.defaultReasoningEffort = model.defaultReasoningEffort;
|
|
71
|
+
normalized.defaultReasoningEffort = normalizeReasoningEffort(model.defaultReasoningEffort);
|
|
72
72
|
}
|
|
73
73
|
if (Object.prototype.hasOwnProperty.call(Object(model), 'defaultSpeedTier')) {
|
|
74
74
|
normalized.defaultSpeedTier = model.defaultSpeedTier;
|
|
@@ -77,6 +77,17 @@
|
|
|
77
77
|
return normalized;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function normalizeReasoningEfforts(reasoningEfforts) {
|
|
81
|
+
if (!Array.isArray(reasoningEfforts)) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
return Array.from(new Set(reasoningEfforts.map(normalizeReasoningEffort).filter(Boolean)));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeReasoningEffort(reasoningEffort) {
|
|
88
|
+
return normalizeModelId(reasoningEffort);
|
|
89
|
+
}
|
|
90
|
+
|
|
80
91
|
function normalizeSpeedTiers(speedTiers) {
|
|
81
92
|
if (!Array.isArray(speedTiers)) {
|
|
82
93
|
return ['standard'];
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
})(typeof window !== 'undefined' ? window : (typeof globalThis !== 'undefined' ? globalThis : this), function () {
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
-
var REVISION = '2026-
|
|
8
|
+
var REVISION = '2026-08-04-page-rpc-contract-v15';
|
|
9
9
|
var METHODS = freezeCatalog({
|
|
10
10
|
initializeCapability: entry('bridge.initialize', 'control', 'none', 'default', false, 'none'),
|
|
11
11
|
probe: entry('bridge.probe', 'read', 'none', 'default', false, 'safe'),
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
ensureReviewing: entry('reviewing.enable', 'page_state', 'optional', 'default', true, 'idempotent'),
|
|
18
18
|
ensureEditing: entry('reviewing.disable', 'page_state', 'optional', 'default', true, 'idempotent'),
|
|
19
19
|
applyOperations: entry('document.write', 'document', 'required', 'writeback', true, 'no_retry'),
|
|
20
|
+
binaryUploadBegin: entry('asset.stage', 'cache', 'required', 'default', false, 'no_retry'),
|
|
21
|
+
binaryUploadAppend: entry('asset.stage', 'cache', 'none', 'writeback', true, 'no_retry'),
|
|
22
|
+
binaryUploadCommit: entry('asset.write', 'document', 'required', 'writeback', true, 'no_retry'),
|
|
23
|
+
binaryUploadAbort: entry('asset.stage', 'cache', 'none', 'default', false, 'idempotent'),
|
|
20
24
|
jumpToPosition: entry('editor.navigate', 'navigation', 'required', 'default', false, 'idempotent'),
|
|
21
25
|
rejectTrackedChanges: entry('tracked_changes.reject', 'document', 'required', 'lifecycle', true, 'no_retry'),
|
|
22
26
|
acceptTrackedChanges: entry('tracked_changes.accept', 'document', 'required', 'lifecycle', true, 'no_retry'),
|
|
@@ -67,7 +71,7 @@
|
|
|
67
71
|
file_list: 35000,
|
|
68
72
|
compile: 75000,
|
|
69
73
|
lifecycle: 120000,
|
|
70
|
-
writeback:
|
|
74
|
+
writeback: 45000
|
|
71
75
|
};
|
|
72
76
|
var candidate = Number(overrides[timeoutClass]);
|
|
73
77
|
return Number.isFinite(candidate) && candidate > 0 ? candidate : defaults[timeoutClass];
|
|
@@ -28,7 +28,10 @@
|
|
|
28
28
|
: rawQueueItem;
|
|
29
29
|
const persisted = rawQueueItem?.executionSnapshot || rawPayload?.executionSnapshot;
|
|
30
30
|
if (persisted && typeof persisted === 'object') {
|
|
31
|
-
return Codec.normalizeSnapshot(persisted, {
|
|
31
|
+
return Codec.normalizeSnapshot(persisted, {
|
|
32
|
+
source: persisted.source || 'submitted',
|
|
33
|
+
migrateLegacyConfirm: true
|
|
34
|
+
});
|
|
32
35
|
}
|
|
33
36
|
const raw = rawPayload && typeof rawPayload === 'object' ? rawPayload : {};
|
|
34
37
|
const fallback = fallbackInput && typeof fallbackInput === 'object' ? fallbackInput : {};
|
|
@@ -48,13 +51,17 @@
|
|
|
48
51
|
inferred = true;
|
|
49
52
|
}
|
|
50
53
|
return Codec.normalizeSnapshot(captured, {
|
|
51
|
-
source: inferred ? 'legacy-inferred' : 'legacy-captured'
|
|
54
|
+
source: inferred ? 'legacy-inferred' : 'legacy-captured',
|
|
55
|
+
migrateLegacyConfirm: true
|
|
52
56
|
});
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
function resolveForExecution(value = {}, options = {}) {
|
|
56
60
|
const persisted = value.executionSnapshot || value;
|
|
57
|
-
const snapshot = Codec.normalizeSnapshot(persisted, {
|
|
61
|
+
const snapshot = Codec.normalizeSnapshot(persisted, {
|
|
62
|
+
source: persisted.source || 'submitted',
|
|
63
|
+
migrateLegacyConfirm: true
|
|
64
|
+
});
|
|
58
65
|
if (snapshot.providerId === 'builtin') return Codec.normalizeSnapshot(snapshot, { source: snapshot.source });
|
|
59
66
|
const catalog = options.catalog || {};
|
|
60
67
|
const provider = ProviderProfiles?.getProviderById
|
|
@@ -80,7 +87,10 @@
|
|
|
80
87
|
|
|
81
88
|
function snapshotOf(value = {}) {
|
|
82
89
|
const persisted = value.executionSnapshot || value;
|
|
83
|
-
return Codec.normalizeSnapshot(persisted, {
|
|
90
|
+
return Codec.normalizeSnapshot(persisted, {
|
|
91
|
+
source: persisted.source || 'submitted',
|
|
92
|
+
migrateLegacyConfirm: true
|
|
93
|
+
});
|
|
84
94
|
}
|
|
85
95
|
|
|
86
96
|
function cloneSnapshot(value = {}) {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
const SCHEMA_VERSION = 1;
|
|
11
|
-
const VALID_MODES = new Set(['ask', '
|
|
11
|
+
const VALID_MODES = new Set(['ask', 'auto']);
|
|
12
12
|
const VALID_SOURCES = new Set(['submitted', 'legacy-captured', 'legacy-inferred']);
|
|
13
13
|
const TUPLE_FIELDS = Object.freeze([
|
|
14
14
|
'mode', 'providerId', 'providerRevision', 'model', 'reasoningEffort',
|
|
@@ -19,12 +19,15 @@
|
|
|
19
19
|
function normalizeSnapshot(value = {}, options = {}) {
|
|
20
20
|
const input = value && typeof value === 'object' ? value : {};
|
|
21
21
|
assertNoSecretFields(input);
|
|
22
|
+
if (input.mode === 'confirm' && options.migrateLegacyConfirm !== true) {
|
|
23
|
+
throw snapshotError('suggest_mode_removed', 'Suggest mode has been removed. Choose Ask or Auto.');
|
|
24
|
+
}
|
|
22
25
|
const providerId = normalizeText(input.providerId) || 'builtin';
|
|
23
26
|
const revision = normalizeRevisionResult(input.providerRevision);
|
|
24
27
|
if (!revision.ok) throw snapshotError('provider_revision_conflict', revision.error);
|
|
25
28
|
const snapshot = {
|
|
26
29
|
schemaVersion: SCHEMA_VERSION,
|
|
27
|
-
mode:
|
|
30
|
+
mode: input.mode === 'auto' ? 'auto' : 'ask',
|
|
28
31
|
providerId,
|
|
29
32
|
providerRevision: providerId === 'builtin' ? '' : revision.value,
|
|
30
33
|
model: normalizeText(input.model).slice(0, 160),
|
|
@@ -35,8 +35,27 @@
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function normalizeMeta(value) {
|
|
38
|
+
var revision = normalizeRevision(value && value.revision);
|
|
39
|
+
var hasDurableRevision = Boolean(value
|
|
40
|
+
&& Object.prototype.hasOwnProperty.call(value, 'durableRevision'));
|
|
41
|
+
var durableRevision = hasDurableRevision
|
|
42
|
+
? Math.min(normalizeRevision(value.durableRevision), revision)
|
|
43
|
+
: revision;
|
|
44
|
+
var pendingValue = value && value.pendingCommit;
|
|
45
|
+
var pendingRevision = normalizeRevision(pendingValue && pendingValue.revision);
|
|
46
|
+
var pendingCommit = pendingValue && typeof pendingValue === 'object'
|
|
47
|
+
&& pendingRevision > durableRevision
|
|
48
|
+
&& pendingRevision <= revision
|
|
49
|
+
? {
|
|
50
|
+
revision: pendingRevision,
|
|
51
|
+
writerId: cleanPart(pendingValue.writerId),
|
|
52
|
+
startedAt: cleanPart(pendingValue.startedAt)
|
|
53
|
+
}
|
|
54
|
+
: null;
|
|
38
55
|
return {
|
|
39
|
-
revision:
|
|
56
|
+
revision: revision,
|
|
57
|
+
durableRevision: durableRevision,
|
|
58
|
+
pendingCommit: pendingCommit,
|
|
40
59
|
queueRevision: normalizeRevision(value && value.queueRevision),
|
|
41
60
|
writerId: cleanPart(value && value.writerId),
|
|
42
61
|
updatedAt: cleanPart(value && value.updatedAt),
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafModuleRegistry.define('ScopedPersistenceReceipt', [], factory);
|
|
6
|
+
}
|
|
7
|
+
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
function createFenceMeta(meta, currentMeta, writerId) {
|
|
11
|
+
var startedAt = new Date().toISOString();
|
|
12
|
+
var revision = currentMeta.revision + 1;
|
|
13
|
+
return {
|
|
14
|
+
...meta,
|
|
15
|
+
revision: revision,
|
|
16
|
+
durableRevision: currentMeta.durableRevision,
|
|
17
|
+
pendingCommit: { revision: revision, writerId: writerId, startedAt: startedAt },
|
|
18
|
+
writerId: writerId,
|
|
19
|
+
updatedAt: startedAt
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function createCommittedMeta(fenceMeta) {
|
|
24
|
+
return {
|
|
25
|
+
...fenceMeta,
|
|
26
|
+
durableRevision: fenceMeta.revision,
|
|
27
|
+
pendingCommit: null,
|
|
28
|
+
updatedAt: new Date().toISOString()
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createRecoveredMeta(currentMeta, fenceMeta, writerId) {
|
|
33
|
+
var revision = fenceMeta.revision + 1;
|
|
34
|
+
return {
|
|
35
|
+
...currentMeta,
|
|
36
|
+
revision: revision,
|
|
37
|
+
durableRevision: revision,
|
|
38
|
+
pendingCommit: null,
|
|
39
|
+
queueRevision: fenceMeta.queueRevision + 1,
|
|
40
|
+
writerId: writerId,
|
|
41
|
+
updatedAt: new Date().toISOString()
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function decorateReceiptError(error, revision) {
|
|
46
|
+
if (error && typeof error === 'object') {
|
|
47
|
+
error.code = error.code || 'persistence_receipt_write_failed';
|
|
48
|
+
error.persistenceActionCommitted = true;
|
|
49
|
+
error.persistenceRevision = revision;
|
|
50
|
+
}
|
|
51
|
+
return error;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
createFenceMeta: createFenceMeta,
|
|
56
|
+
createCommittedMeta: createCommittedMeta,
|
|
57
|
+
createRecoveredMeta: createRecoveredMeta,
|
|
58
|
+
decorateReceiptError: decorateReceiptError
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
if (typeof module === 'object' && module.exports) {
|
|
3
3
|
module.exports = factory(
|
|
4
4
|
require('./scopedPersistenceQueuePolicy'),
|
|
5
|
-
require('./scopedPersistenceBrowserAdapter')
|
|
5
|
+
require('./scopedPersistenceBrowserAdapter'),
|
|
6
|
+
require('./scopedPersistenceReceipt')
|
|
6
7
|
);
|
|
7
8
|
} else {
|
|
8
9
|
root.CodexOverleafModuleRegistry.define('ScopedPersistenceTransaction', [
|
|
9
10
|
'ScopedPersistenceQueuePolicy',
|
|
10
|
-
'ScopedPersistenceBrowserAdapter'
|
|
11
|
+
'ScopedPersistenceBrowserAdapter',
|
|
12
|
+
'ScopedPersistenceReceipt'
|
|
11
13
|
], factory);
|
|
12
14
|
}
|
|
13
|
-
})(typeof globalThis !== 'undefined' ? globalThis : this, function (QueuePolicy, BrowserAdapter) {
|
|
15
|
+
})(typeof globalThis !== 'undefined' ? globalThis : this, function (QueuePolicy, BrowserAdapter, Receipt) {
|
|
14
16
|
'use strict';
|
|
15
17
|
|
|
16
18
|
var applyQueueMutations = QueuePolicy.applyQueueMutations;
|
|
@@ -43,9 +45,9 @@
|
|
|
43
45
|
var scope = normalizeScope(inputScope);
|
|
44
46
|
generation += 1;
|
|
45
47
|
var tokenGeneration = generation;
|
|
46
|
-
active =
|
|
48
|
+
active = tokenFromMeta(scope, tokenGeneration, false, {});
|
|
47
49
|
var meta = await strictRead(adapter, scope);
|
|
48
|
-
var token =
|
|
50
|
+
var token = tokenFromMeta(scope, tokenGeneration, false, meta);
|
|
49
51
|
if (active && active.generation === tokenGeneration && sameScope(active, token)) active = token;
|
|
50
52
|
return token;
|
|
51
53
|
}
|
|
@@ -60,10 +62,10 @@
|
|
|
60
62
|
});
|
|
61
63
|
}
|
|
62
64
|
if (!detached && active && sameScope(active, scope)) {
|
|
63
|
-
return
|
|
65
|
+
return tokenFromMeta(scope, active.generation, false, active);
|
|
64
66
|
}
|
|
65
67
|
var meta = await strictRead(adapter, scope);
|
|
66
|
-
return
|
|
68
|
+
return tokenFromMeta(scope, detached ? 0 : generation, detached, meta);
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
function isCurrent(token) {
|
|
@@ -94,13 +96,7 @@
|
|
|
94
96
|
if (queueMutations.length
|
|
95
97
|
&& currentMeta.queueRevision !== normalizeRevision(token.queueRevision)) {
|
|
96
98
|
if (!token.detached && isCurrent(token)) {
|
|
97
|
-
active =
|
|
98
|
-
scope,
|
|
99
|
-
token.generation,
|
|
100
|
-
currentMeta.revision,
|
|
101
|
-
false,
|
|
102
|
-
currentMeta.queueRevision
|
|
103
|
-
);
|
|
99
|
+
active = tokenFromMeta(scope, token.generation, false, currentMeta);
|
|
104
100
|
}
|
|
105
101
|
return {
|
|
106
102
|
ok: false,
|
|
@@ -112,33 +108,26 @@
|
|
|
112
108
|
}
|
|
113
109
|
var projected = applyQueueMutations(currentMeta, queueMutations, writerId);
|
|
114
110
|
if (!projected.ok) return projected;
|
|
115
|
-
var nextMeta = {
|
|
111
|
+
var nextMeta = Receipt.createFenceMeta({
|
|
116
112
|
...projected.meta,
|
|
117
|
-
revision: currentMeta.revision + 1,
|
|
118
|
-
writerId: writerId,
|
|
119
|
-
updatedAt: new Date().toISOString(),
|
|
120
113
|
sessionTombstones: mergeTombstones(
|
|
121
114
|
projected.meta.sessionTombstones,
|
|
122
115
|
commitOptions && commitOptions.sessionTombstones,
|
|
123
116
|
200
|
|
124
117
|
)
|
|
125
|
-
};
|
|
118
|
+
}, currentMeta, writerId);
|
|
126
119
|
await adapter.write(scope, nextMeta);
|
|
127
120
|
if (!token.detached && isCurrent(token)) {
|
|
128
|
-
active =
|
|
129
|
-
scope,
|
|
130
|
-
token.generation,
|
|
131
|
-
nextMeta.revision,
|
|
132
|
-
false,
|
|
133
|
-
nextMeta.queueRevision
|
|
134
|
-
);
|
|
121
|
+
active = tokenFromMeta(scope, token.generation, false, nextMeta);
|
|
135
122
|
}
|
|
136
123
|
var value;
|
|
137
124
|
try {
|
|
138
125
|
value = await action({
|
|
139
126
|
scope: scope,
|
|
140
127
|
baseRevision: currentMeta.revision,
|
|
128
|
+
durableRevision: currentMeta.durableRevision,
|
|
141
129
|
conflict: conflict,
|
|
130
|
+
previousCommitIncomplete: Boolean(currentMeta.pendingCommit),
|
|
142
131
|
writerId: writerId,
|
|
143
132
|
currentMeta: currentMeta,
|
|
144
133
|
nextMeta: nextMeta
|
|
@@ -147,23 +136,11 @@
|
|
|
147
136
|
if (queueMutations.some(function (mutation) {
|
|
148
137
|
return ['remove', 'release', 'restore'].includes(cleanPart(mutation.type));
|
|
149
138
|
})) {
|
|
150
|
-
var recoveredMeta =
|
|
151
|
-
...currentMeta,
|
|
152
|
-
revision: nextMeta.revision + 1,
|
|
153
|
-
queueRevision: nextMeta.queueRevision + 1,
|
|
154
|
-
writerId: writerId,
|
|
155
|
-
updatedAt: new Date().toISOString()
|
|
156
|
-
};
|
|
139
|
+
var recoveredMeta = Receipt.createRecoveredMeta(currentMeta, nextMeta, writerId);
|
|
157
140
|
try {
|
|
158
141
|
await adapter.write(scope, recoveredMeta);
|
|
159
142
|
if (!token.detached && isCurrent(token)) {
|
|
160
|
-
active =
|
|
161
|
-
scope,
|
|
162
|
-
token.generation,
|
|
163
|
-
recoveredMeta.revision,
|
|
164
|
-
false,
|
|
165
|
-
recoveredMeta.queueRevision
|
|
166
|
-
);
|
|
143
|
+
active = tokenFromMeta(scope, token.generation, false, recoveredMeta);
|
|
167
144
|
}
|
|
168
145
|
} catch (recoveryError) {
|
|
169
146
|
if (error && typeof error === 'object') {
|
|
@@ -173,13 +150,24 @@
|
|
|
173
150
|
}
|
|
174
151
|
throw error;
|
|
175
152
|
}
|
|
153
|
+
var committedMeta = Receipt.createCommittedMeta(nextMeta);
|
|
154
|
+
try {
|
|
155
|
+
await adapter.write(scope, committedMeta);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
throw Receipt.decorateReceiptError(error, nextMeta.revision);
|
|
158
|
+
}
|
|
159
|
+
if (!token.detached && isCurrent(token)) {
|
|
160
|
+
active = tokenFromMeta(scope, token.generation, false, committedMeta);
|
|
161
|
+
}
|
|
176
162
|
return {
|
|
177
163
|
ok: true,
|
|
178
164
|
value: value,
|
|
179
|
-
revision:
|
|
180
|
-
|
|
181
|
-
|
|
165
|
+
revision: committedMeta.revision,
|
|
166
|
+
durableRevision: committedMeta.durableRevision,
|
|
167
|
+
queueRevision: committedMeta.queueRevision,
|
|
168
|
+
queueClaims: committedMeta.queueClaims,
|
|
182
169
|
rebased: conflict,
|
|
170
|
+
recoveredIncompleteCommit: Boolean(currentMeta.pendingCommit),
|
|
183
171
|
superseded: !token.detached && !isCurrent(token)
|
|
184
172
|
};
|
|
185
173
|
});
|
|
@@ -190,9 +178,7 @@
|
|
|
190
178
|
captureCommitView: captureCommitView,
|
|
191
179
|
commit: commit,
|
|
192
180
|
getActiveView: function () {
|
|
193
|
-
return active
|
|
194
|
-
? makeToken(active, active.generation, active.revision, false, active.queueRevision)
|
|
195
|
-
: null;
|
|
181
|
+
return active ? tokenFromMeta(active, active.generation, false, active) : null;
|
|
196
182
|
},
|
|
197
183
|
getWriterId: function () { return writerId; },
|
|
198
184
|
invalidate: function () {
|
|
@@ -203,17 +189,34 @@
|
|
|
203
189
|
});
|
|
204
190
|
}
|
|
205
191
|
|
|
206
|
-
function makeToken(
|
|
192
|
+
function makeToken(
|
|
193
|
+
scope,
|
|
194
|
+
tokenGeneration,
|
|
195
|
+
revision,
|
|
196
|
+
detached,
|
|
197
|
+
queueRevision,
|
|
198
|
+
durableRevision,
|
|
199
|
+
pendingCommit
|
|
200
|
+
) {
|
|
207
201
|
return Object.freeze({
|
|
208
202
|
accountScopeId: scope.accountScopeId,
|
|
209
203
|
projectId: scope.projectId,
|
|
210
204
|
generation: normalizeRevision(tokenGeneration),
|
|
211
205
|
revision: normalizeRevision(revision),
|
|
206
|
+
durableRevision: normalizeRevision(durableRevision),
|
|
207
|
+
pendingCommit: pendingCommit && typeof pendingCommit === 'object'
|
|
208
|
+
? Object.freeze({ ...pendingCommit })
|
|
209
|
+
: null,
|
|
212
210
|
queueRevision: normalizeRevision(queueRevision),
|
|
213
211
|
detached: detached === true
|
|
214
212
|
});
|
|
215
213
|
}
|
|
216
214
|
|
|
215
|
+
function tokenFromMeta(scope, tokenGeneration, detached, meta) {
|
|
216
|
+
return makeToken(scope, tokenGeneration, meta.revision, detached, meta.queueRevision,
|
|
217
|
+
meta.durableRevision, meta.pendingCommit);
|
|
218
|
+
}
|
|
219
|
+
|
|
217
220
|
async function strictRead(adapter, scope) {
|
|
218
221
|
return normalizeMeta(await adapter.read(scope));
|
|
219
222
|
}
|