codex-overleaf-link 1.8.6 → 1.9.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 +45 -21
- package/bin/codex-overleaf-link.mjs +58 -0
- package/extension/assets/icons/codex-overleaf-dialog-icon.png +0 -0
- package/extension/assets/icons/codex-overleaf-icon.png +0 -0
- package/extension/assets/icons/icon128.png +0 -0
- package/extension/assets/icons/icon16.png +0 -0
- package/extension/assets/icons/icon32.png +0 -0
- package/extension/assets/icons/icon48.png +0 -0
- package/extension/bootstrap/background.js +404 -0
- package/extension/bootstrap/manifest.template.json +51 -0
- package/extension/bootstrap/popup.css +26 -0
- package/extension/bootstrap/popup.html +38 -0
- package/extension/bootstrap/popup.js +44 -0
- package/extension/bootstrap/runtimeContext.js +2 -0
- package/extension/runtime-manifest.json +60 -0
- package/extension/src/background.js +532 -0
- package/extension/src/content/applyResultFormatters.js +387 -0
- package/extension/src/content/composerAttachments.js +522 -0
- package/extension/src/content/composerPanel.js +254 -0
- package/extension/src/content/contentRuntime.js +8897 -0
- package/extension/src/content/contextTray.js +686 -0
- package/extension/src/content/diagnosticsController.js +659 -0
- package/extension/src/content/diagnosticsPanel.js +342 -0
- package/extension/src/content/diffReviewPanel.js +797 -0
- package/extension/src/content/localSkillsPanel.js +280 -0
- package/extension/src/content/markdownText.js +606 -0
- package/extension/src/content/mirrorHealth.js +92 -0
- package/extension/src/content/modelPicker.js +489 -0
- package/extension/src/content/nativeChannel.js +62 -0
- package/extension/src/content/otWarmMirror.js +964 -0
- package/extension/src/content/otWarmMirrorController.js +174 -0
- package/extension/src/content/panelMaintenance.js +367 -0
- package/extension/src/content/panelRenderer.js +253 -0
- package/extension/src/content/recentProjects.js +1063 -0
- package/extension/src/content/reviewHunks.js +247 -0
- package/extension/src/content/runController.js +242 -0
- package/extension/src/content/runTimelineView.js +1131 -0
- package/extension/src/content/sessionManager.js +487 -0
- package/extension/src/content/sessionPanel.js +263 -0
- package/extension/src/content/settingsPanel.js +527 -0
- package/extension/src/content/themeController.js +91 -0
- package/extension/src/content/updateIdle.js +67 -0
- package/extension/src/content/writebackController.js +300 -0
- package/extension/src/content/writebackOrchestrator.js +850 -0
- package/extension/src/contentScript.js +8 -0
- package/extension/src/page/compileBridge.js +460 -0
- package/extension/src/page/overleafCapabilities.js +100 -0
- package/extension/src/page/overleafEditor.js +282 -0
- package/extension/src/page/overleafProjectSnapshot.js +234 -0
- package/extension/src/page/overleafRealtimeObserver.js +491 -0
- package/extension/src/page/pageBridgeCapability.js +71 -0
- package/extension/src/page/snapshotRouter.js +948 -0
- package/extension/src/page/trackedChangesLifecycle.js +1506 -0
- package/extension/src/page/treeOperations.js +1584 -0
- package/extension/src/page/writeGuard.js +142 -0
- package/extension/src/page/writebackRouter.js +1934 -0
- package/extension/src/pageBridge.js +2224 -0
- package/extension/src/popup.js +247 -0
- package/extension/src/shared/agentTranscript.js +18 -4
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/styles/panel.css +4048 -0
- package/native-host/src/index.js +9 -2
- package/native-host/src/managedInstall.js +393 -0
- package/native-host/src/managedLauncherRuntime.js +71 -0
- package/native-host/src/nativeHostPlatform.js +16 -0
- package/native-host/src/runtimeInstaller.js +6 -1
- package/native-host/src/taskRunnerRuntime.js +8 -0
- package/native-host/src/updateArchive.js +253 -0
- package/native-host/src/updateManager.js +548 -0
- package/native-host/src/updateTrust.js +156 -0
- package/package.json +11 -2
- package/scripts/install-managed.mjs +64 -0
- package/scripts/uninstall-managed.mjs +48 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
(function initCodexOverleafUpdateIdle(root) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
function create(options = {}) {
|
|
5
|
+
let lastUserActivityAt = Date.now();
|
|
6
|
+
let stableSavedSince = 0;
|
|
7
|
+
let applying = false;
|
|
8
|
+
const documentRef = options.document || root.document;
|
|
9
|
+
const noteActivity = () => { lastUserActivityAt = Date.now(); stableSavedSince = 0; };
|
|
10
|
+
for (const eventName of ['input', 'keydown', 'pointerdown']) {
|
|
11
|
+
documentRef?.addEventListener?.(eventName, noteActivity, true);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function handleMessage(message, sendResponse) {
|
|
15
|
+
if (message?.type === 'codex-overleaf/update-idle-probe') {
|
|
16
|
+
buildIdleProbe().then(sendResponse).catch(() => sendResponse({ idle: false, saved: false, blockers: ['idle_probe_failed'] }));
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (message?.type === 'codex-overleaf/update-state') {
|
|
20
|
+
const state = String(message.state || '');
|
|
21
|
+
applying = state === 'applying' || state === 'awaiting_health' || state === 'rolling_back';
|
|
22
|
+
options.onApplyingChange?.(applying);
|
|
23
|
+
const copy = options.getStateMessage?.(state);
|
|
24
|
+
if (copy) options.showToast?.(copy, { status: state === 'rolled_back' ? 'warning' : 'info', sticky: applying });
|
|
25
|
+
sendResponse?.({ ok: true });
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function buildIdleProbe() {
|
|
32
|
+
const busy = options.getBusyState?.() || {};
|
|
33
|
+
const blockers = Object.entries(busy).filter(([_key, value]) => Boolean(value)).map(([key]) => key);
|
|
34
|
+
let saveResult;
|
|
35
|
+
try {
|
|
36
|
+
saveResult = await options.checkSaved?.();
|
|
37
|
+
} catch (_error) {
|
|
38
|
+
saveResult = { ok: false, state: 'unavailable' };
|
|
39
|
+
}
|
|
40
|
+
const saved = saveResult?.ok === true && saveResult?.state === 'verified_saved';
|
|
41
|
+
if (saved) {
|
|
42
|
+
stableSavedSince = stableSavedSince || Date.now();
|
|
43
|
+
} else {
|
|
44
|
+
stableSavedSince = 0;
|
|
45
|
+
blockers.push('save_state_unverified');
|
|
46
|
+
}
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
if (now - lastUserActivityAt < 30000) blockers.push('recent_user_activity');
|
|
49
|
+
if (!stableSavedSince || now - stableSavedSince < 3000) blockers.push('save_state_not_stable');
|
|
50
|
+
return {
|
|
51
|
+
idle: blockers.length === 0,
|
|
52
|
+
saved,
|
|
53
|
+
lastUserActivityAt,
|
|
54
|
+
stableSavedSince,
|
|
55
|
+
blockers: [...new Set(blockers)]
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
buildIdleProbe,
|
|
61
|
+
handleMessage,
|
|
62
|
+
isApplying: () => applying
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
root.CodexOverleafUpdateIdle = Object.freeze({ create });
|
|
67
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
(function initCodexOverleafWritebackController(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafWritebackController = factory();
|
|
6
|
+
}
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function writebackControllerFactory() {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
function buildSyncApplyOperations(syncChanges = [], project = {}) {
|
|
11
|
+
const existingPaths = new Set((project.files || []).map(file => file.path));
|
|
12
|
+
return (syncChanges || []).map(change => {
|
|
13
|
+
if (change.type === 'binary-create' || change.type === 'overwrite-binary') {
|
|
14
|
+
return {
|
|
15
|
+
type: change.type,
|
|
16
|
+
path: change.path,
|
|
17
|
+
contentBase64: change.contentBase64 || '',
|
|
18
|
+
size: Number.isFinite(Number(change.size)) ? Number(change.size) : undefined,
|
|
19
|
+
previousExists: change.previousExists === true,
|
|
20
|
+
previousKind: change.previousKind || '',
|
|
21
|
+
previousSize: Number.isFinite(Number(change.previousSize)) ? Number(change.previousSize) : undefined,
|
|
22
|
+
reasonKey: change.type === 'overwrite-binary' ? 'localWorkspaceBinaryOverwrite' : 'localWorkspaceBinaryCreate',
|
|
23
|
+
reason: change.type === 'overwrite-binary'
|
|
24
|
+
? 'Synced a binary asset replacement from the local Codex workspace.'
|
|
25
|
+
: 'Synced a new binary asset from the local Codex workspace.'
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (change.type === 'delete') {
|
|
29
|
+
return {
|
|
30
|
+
type: 'delete',
|
|
31
|
+
path: change.path,
|
|
32
|
+
reasonKey: 'localWorkspaceDelete',
|
|
33
|
+
reason: 'Local Codex workspace deleted this file.'
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (change.type === 'write' && existingPaths.has(change.path)) {
|
|
37
|
+
const patches = getSyncChangePatches(change);
|
|
38
|
+
if (patches.length) {
|
|
39
|
+
return {
|
|
40
|
+
type: 'edit',
|
|
41
|
+
path: change.path,
|
|
42
|
+
patches,
|
|
43
|
+
reasonKey: 'localWorkspacePatch',
|
|
44
|
+
reasonParams: { count: patches.length },
|
|
45
|
+
reason: `Synced ${patches.length} local Codex workspace edit${patches.length === 1 ? '' : 's'}.`
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
type: 'edit',
|
|
50
|
+
path: change.path,
|
|
51
|
+
replaceAll: String(change.content ?? ''),
|
|
52
|
+
reasonKey: 'localWorkspaceContent',
|
|
53
|
+
reason: 'Synced file content from the local Codex workspace.'
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
type: 'create',
|
|
58
|
+
path: change.path,
|
|
59
|
+
content: change.content || '',
|
|
60
|
+
reasonKey: 'localWorkspaceCreate',
|
|
61
|
+
reason: 'Synced a new file from the local Codex workspace.'
|
|
62
|
+
};
|
|
63
|
+
}).filter(operation => operation.path);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getSyncChangePatches(change = {}) {
|
|
67
|
+
const normalized = normalizeTextPatches(change.patches);
|
|
68
|
+
if (normalized.length) {
|
|
69
|
+
return normalized;
|
|
70
|
+
}
|
|
71
|
+
if (typeof change.previousContent === 'string' && typeof change.content === 'string') {
|
|
72
|
+
return computeSingleTextPatch(change.previousContent, change.content);
|
|
73
|
+
}
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeTextPatches(patches) {
|
|
78
|
+
const normalized = [];
|
|
79
|
+
for (const patch of patches || []) {
|
|
80
|
+
const from = Number(patch?.from);
|
|
81
|
+
const to = Number(patch?.to);
|
|
82
|
+
if (!Number.isInteger(from) || !Number.isInteger(to) || from < 0 || to < from) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
normalized.push({
|
|
86
|
+
from,
|
|
87
|
+
to,
|
|
88
|
+
expected: String(patch.expected ?? ''),
|
|
89
|
+
insert: String(patch.insert ?? '')
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return normalized.sort((left, right) => left.from - right.from);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function computeSingleTextPatch(oldText, newText) {
|
|
96
|
+
if (oldText === newText) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
let prefix = 0;
|
|
100
|
+
const sharedLength = Math.min(oldText.length, newText.length);
|
|
101
|
+
while (prefix < sharedLength && oldText[prefix] === newText[prefix]) {
|
|
102
|
+
prefix += 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let oldEnd = oldText.length;
|
|
106
|
+
let newEnd = newText.length;
|
|
107
|
+
while (oldEnd > prefix && newEnd > prefix && oldText[oldEnd - 1] === newText[newEnd - 1]) {
|
|
108
|
+
oldEnd -= 1;
|
|
109
|
+
newEnd -= 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return [
|
|
113
|
+
{
|
|
114
|
+
from: prefix,
|
|
115
|
+
to: oldEnd,
|
|
116
|
+
expected: oldText.slice(prefix, oldEnd),
|
|
117
|
+
insert: newText.slice(prefix, newEnd)
|
|
118
|
+
}
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getAppliedOperationPaths(applied = {}) {
|
|
123
|
+
const paths = [];
|
|
124
|
+
for (const item of getAppliedEntries(applied)) {
|
|
125
|
+
const operation = item?.operation || {};
|
|
126
|
+
if (operation.path) {
|
|
127
|
+
paths.push(operation.path);
|
|
128
|
+
}
|
|
129
|
+
if ((operation.type === 'rename' || operation.type === 'move') && operation.to) {
|
|
130
|
+
paths.push(operation.to);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return Array.from(new Set(paths.filter(Boolean)));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function getAppliedEntries(applied = {}) {
|
|
137
|
+
return Array.isArray(applied?.applied) ? applied.applied : [];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function mergeVerifiedAppliedFiles(freshProject = {}, originalProject = {}, applied = {}) {
|
|
141
|
+
const filesByPath = new Map((freshProject.files || []).map(file => [file.path, { ...file }]));
|
|
142
|
+
const originalByPath = new Map((originalProject.files || []).map(file => [file.path, file]));
|
|
143
|
+
|
|
144
|
+
for (const item of getAppliedEntries(applied)) {
|
|
145
|
+
const operation = item?.operation || {};
|
|
146
|
+
const result = item?.result || {};
|
|
147
|
+
if (!operation.path) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (operation.type === 'delete') {
|
|
152
|
+
filesByPath.delete(operation.path);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if ((operation.type === 'rename' || operation.type === 'move') && operation.to) {
|
|
157
|
+
const previous = filesByPath.get(operation.path) || originalByPath.get(operation.path);
|
|
158
|
+
filesByPath.delete(operation.path);
|
|
159
|
+
filesByPath.set(operation.to, {
|
|
160
|
+
...(previous || {}),
|
|
161
|
+
path: operation.to,
|
|
162
|
+
kind: 'text',
|
|
163
|
+
content: result.verifiedContent || previous?.content || '',
|
|
164
|
+
source: 'verified-writeback'
|
|
165
|
+
});
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (operation.type === 'create') {
|
|
170
|
+
filesByPath.set(operation.path, {
|
|
171
|
+
path: operation.path,
|
|
172
|
+
kind: 'text',
|
|
173
|
+
content: result.verifiedContent || operation.content || '',
|
|
174
|
+
source: 'verified-writeback'
|
|
175
|
+
});
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (operation.type === 'edit' && typeof result.verifiedContent === 'string') {
|
|
180
|
+
filesByPath.set(operation.path, {
|
|
181
|
+
...(filesByPath.get(operation.path) || originalByPath.get(operation.path) || {}),
|
|
182
|
+
path: operation.path,
|
|
183
|
+
kind: 'text',
|
|
184
|
+
content: result.verifiedContent,
|
|
185
|
+
source: 'verified-writeback'
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
...freshProject,
|
|
192
|
+
files: Array.from(filesByPath.values())
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function formatUnsupportedLocalChangeSummary(changes = [], locale = 'zh') {
|
|
197
|
+
if (!changes.length) {
|
|
198
|
+
return '';
|
|
199
|
+
}
|
|
200
|
+
const isEnglish = locale === 'en';
|
|
201
|
+
const visibleChanges = changes.slice(0, 5);
|
|
202
|
+
const lines = [
|
|
203
|
+
isEnglish
|
|
204
|
+
? 'Codex generated these local files, but the extension did not sync them back to Overleaf:'
|
|
205
|
+
: 'Codex 在本地生成了这些文件,但插件没有同步回 Overleaf:',
|
|
206
|
+
...visibleChanges.map(change => {
|
|
207
|
+
const path = change.path || (isEnglish ? 'unnamed file' : '未命名文件');
|
|
208
|
+
const reason = formatUnsupportedLocalChangeReason(change, locale);
|
|
209
|
+
return isEnglish ? `- ${path}: ${reason}` : `- ${path}:${reason}`;
|
|
210
|
+
})
|
|
211
|
+
];
|
|
212
|
+
if (changes.length > visibleChanges.length) {
|
|
213
|
+
const hiddenCount = changes.length - visibleChanges.length;
|
|
214
|
+
lines.push(isEnglish
|
|
215
|
+
? `${hiddenCount} more file${hiddenCount === 1 ? '' : 's'} not shown.`
|
|
216
|
+
: `另外 ${hiddenCount} 个文件未显示。`);
|
|
217
|
+
}
|
|
218
|
+
return lines.join('\n');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function formatUnsupportedLocalChangeReason(changeOrReason, locale = 'zh') {
|
|
222
|
+
const isEnglish = locale === 'en';
|
|
223
|
+
const change = typeof changeOrReason === 'object' && changeOrReason
|
|
224
|
+
? changeOrReason
|
|
225
|
+
: { reason: changeOrReason };
|
|
226
|
+
const reason = change.reason;
|
|
227
|
+
if (reason === 'generated_artifact') {
|
|
228
|
+
return isEnglish
|
|
229
|
+
? 'LaTeX build artifact; not written back by default.'
|
|
230
|
+
: 'LaTeX 构建产物,默认不写回。';
|
|
231
|
+
}
|
|
232
|
+
if (reason === 'unsupported_non_text_file') {
|
|
233
|
+
return isEnglish
|
|
234
|
+
? 'Non-text file; automatic writeback is not supported yet.'
|
|
235
|
+
: '非文本文件,暂不支持自动写回。';
|
|
236
|
+
}
|
|
237
|
+
if (reason === 'binary_payload_exceeds_native_message_limit') {
|
|
238
|
+
return formatBinaryPayloadLimitReason(change, locale);
|
|
239
|
+
}
|
|
240
|
+
if (reason === 'subagent_unauthorized_edit') {
|
|
241
|
+
return isEnglish
|
|
242
|
+
? "A subagent's change to this file was withheld from Overleaf — it either fell outside the subagent's assigned files or the subagent did not finish cleanly."
|
|
243
|
+
: '子代理对该文件的改动未写回 Overleaf——它可能超出了被分配的文件范围,或没有正常完成。';
|
|
244
|
+
}
|
|
245
|
+
return isEnglish
|
|
246
|
+
? 'This file type is not supported for automatic writeback yet.'
|
|
247
|
+
: '当前类型暂不支持自动写回。';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function formatBinaryPayloadLimitReason(change = {}, locale = 'zh') {
|
|
251
|
+
const isEnglish = locale === 'en';
|
|
252
|
+
const size = formatBytes(change.size);
|
|
253
|
+
const limit = formatBytes(change.limit || change.aggregateLimit || change.nativeOutputLimit);
|
|
254
|
+
if (isEnglish) {
|
|
255
|
+
const sizeText = size ? ` (${size})` : '';
|
|
256
|
+
const limitText = limit ? ` or reduce it below ${limit}` : '';
|
|
257
|
+
return `Binary change is too large to send through native messaging${sizeText}. Upload it in Overleaf${limitText}.`;
|
|
258
|
+
}
|
|
259
|
+
const sizeText = size ? `(${size})` : '';
|
|
260
|
+
const limitText = limit ? `,或减小到 ${limit} 以下` : '';
|
|
261
|
+
return `二进制改动过大,无法通过 Native Messaging 返回${sizeText}。请在 Overleaf 中手动上传${limitText}。`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function formatBytes(value) {
|
|
265
|
+
const bytes = Number(value);
|
|
266
|
+
if (!Number.isFinite(bytes) || bytes < 0) {
|
|
267
|
+
return '';
|
|
268
|
+
}
|
|
269
|
+
if (bytes < 1024) {
|
|
270
|
+
return `${bytes} bytes`;
|
|
271
|
+
}
|
|
272
|
+
const kib = bytes / 1024;
|
|
273
|
+
if (kib < 1024) {
|
|
274
|
+
return `${formatByteNumber(kib)} KB`;
|
|
275
|
+
}
|
|
276
|
+
return `${formatByteNumber(kib / 1024)} MB`;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function formatByteNumber(value) {
|
|
280
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(1);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function getAppliedSyncChanges(syncChanges = [], applied = {}) {
|
|
284
|
+
const appliedPaths = new Set(getAppliedEntries(applied)
|
|
285
|
+
.map(item => item.operation?.path || item.operation?.to || item.operation?.from)
|
|
286
|
+
.filter(Boolean));
|
|
287
|
+
return (syncChanges || []).filter(change => appliedPaths.has(change.path));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
buildSyncApplyOperations,
|
|
292
|
+
computeSingleTextPatch,
|
|
293
|
+
formatUnsupportedLocalChangeSummary,
|
|
294
|
+
getAppliedOperationPaths,
|
|
295
|
+
getAppliedSyncChanges,
|
|
296
|
+
getSyncChangePatches,
|
|
297
|
+
mergeVerifiedAppliedFiles,
|
|
298
|
+
normalizeTextPatches
|
|
299
|
+
};
|
|
300
|
+
});
|