codex-overleaf-link 2.2.1 → 2.3.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 +25 -23
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/runtime-manifest.json +5 -71
- package/extension/src/background.js +53 -69
- package/extension/src/backgroundUpdateCoordinator.js +128 -66
- package/extension/src/content/applyResultFormatters.js +2 -3
- package/extension/src/content/contentRuntime.js +650 -1449
- package/extension/src/content/contextTray.js +4 -6
- package/extension/src/content/diagnosticsController.js +1 -2
- package/extension/src/content/diffReviewPanel.js +14 -13
- package/extension/src/content/generated/content.bundle.js +41370 -0
- package/extension/src/content/generated/content.bundle.meta.json +102 -0
- package/extension/src/content/legacyGlobalRegistry.js +129 -0
- package/extension/src/content/markdownText.js +5 -7
- package/extension/src/content/modelPicker.js +1 -1
- package/extension/src/content/modelPickerSupport.js +10 -5
- package/extension/src/content/moduleRegistryKernel.js +125 -0
- package/extension/src/content/nativeCompatibilityController.js +313 -0
- package/extension/src/content/otWarmMirror.js +7 -5
- package/extension/src/content/pageBridgeClient.js +224 -0
- package/extension/src/content/panelMaintenance.js +8 -3
- package/extension/src/content/postNavigationSettlementPersistence.js +100 -0
- package/extension/src/content/projectProviderSelection.js +4 -12
- package/extension/src/content/projectSessionCleanup.js +3 -3
- package/extension/src/content/providerSettingsCoordinator.js +59 -26
- package/extension/src/content/providerSettingsDialog.js +30 -18
- package/extension/src/content/recentProjects.js +15 -23
- package/extension/src/content/runGuidanceController.js +1 -1
- package/extension/src/content/runQueueScheduler.js +129 -29
- package/extension/src/content/runSettlementPersistence.js +51 -0
- package/extension/src/content/runTimelineView.js +11 -8
- package/extension/src/content/scopedPersistenceCoordinator.js +100 -0
- package/extension/src/content/scopedPersistenceDetachedCommit.js +47 -0
- package/extension/src/content/scopedPersistencePanelState.js +111 -0
- package/extension/src/content/sessionManager.js +15 -12
- package/extension/src/content/sessionPersistence.js +67 -8
- package/extension/src/content/updateNotice.js +10 -4
- package/extension/src/content/writebackOrchestrator.js +91 -23
- package/extension/src/contentScript.js +5 -1
- package/extension/src/page/trackedChangesLifecycle.js +6 -1
- package/extension/src/pageBridge.js +47 -98
- package/extension/src/shared/agentTranscript.js +8 -5
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/governanceRules.js +1 -1
- package/extension/src/shared/managedUpdateProjection.js +337 -0
- package/extension/src/shared/nativeRequestIdentity.js +60 -0
- package/extension/src/shared/otText.js +0 -19
- package/extension/src/shared/pageRpcContract.js +136 -0
- package/extension/src/shared/runExecutionSnapshot.js +177 -0
- package/extension/src/shared/runExecutionSnapshotCodec.js +168 -0
- package/extension/src/shared/runInputQueue.js +81 -34
- package/extension/src/shared/scopedPersistenceBrowserAdapter.js +79 -0
- package/extension/src/shared/scopedPersistenceQueuePolicy.js +181 -0
- package/extension/src/shared/scopedPersistenceTransaction.js +238 -0
- package/extension/src/shared/sessionState.js +64 -48
- package/extension/src/shared/settlementFacts.js +351 -0
- package/extension/src/shared/storageDb.js +19 -5
- package/extension/src/shared/storageMigration.js +275 -61
- package/extension/src/shared/storageSessionClaims.js +60 -0
- package/extension/src/shared/updateConsent.js +4 -0
- package/extension/src/shared/updateRevocationIntent.js +109 -0
- package/extension/src/shared/writebackEvidenceProjection.js +150 -0
- package/extension/src/shared/writebackOperationEvidence.js +143 -0
- package/extension/src/shared/writebackSettlement.js +705 -0
- package/native-host/src/codexRuntimeIdentity.js +1 -1
- package/native-host/src/codexSessionRunner.js +7 -13
- package/native-host/src/index.js +3 -69
- package/native-host/src/managedInstall.js +50 -8
- package/native-host/src/nativeTransportEnvelope.js +148 -0
- package/native-host/src/taskRunnerRuntime.js +3 -3
- package/package.json +13 -5
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
(function initCodexOverleafNativeCompatibilityController(root, factory) {
|
|
2
|
+
const api = factory();
|
|
3
|
+
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
4
|
+
if (root) root.CodexOverleafNativeCompatibilityController = api;
|
|
5
|
+
})(typeof window !== 'undefined' ? window : globalThis, function nativeCompatibilityControllerFactory() {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
function create(options = {}) {
|
|
9
|
+
const Compatibility = options.compatibility;
|
|
10
|
+
const nativeChannel = options.nativeChannel;
|
|
11
|
+
const gatedMethods = options.gatedMethods || new Set();
|
|
12
|
+
const tx = options.tx;
|
|
13
|
+
const tr = options.tr;
|
|
14
|
+
|
|
15
|
+
function sendNative(payload) {
|
|
16
|
+
return dispatch(payload, 'sendNative');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function sendBackgroundNative(payload) {
|
|
20
|
+
return dispatch(payload, 'sendBackgroundNative');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function dispatch(payload, channelMethod) {
|
|
24
|
+
const compatibilityGate = await ensureForMethod(payload?.method);
|
|
25
|
+
if (!compatibilityGate.ok) return compatibilityGate.response;
|
|
26
|
+
if (gatedMethods.has(payload?.method)) options.throwIfCancellationRequested?.();
|
|
27
|
+
return nativeChannel[channelMethod](
|
|
28
|
+
attachEvidence(payload, compatibilityGate.compatibility)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function ensureForMethod(method) {
|
|
33
|
+
const metadata = options.getExtensionCompatibilityMetadata?.() || {};
|
|
34
|
+
const params = Compatibility?.buildBridgePingParams
|
|
35
|
+
? Compatibility.buildBridgePingParams(metadata)
|
|
36
|
+
: {};
|
|
37
|
+
const response = await nativeChannel.sendBackgroundNative({
|
|
38
|
+
method: 'bridge.ping',
|
|
39
|
+
params
|
|
40
|
+
});
|
|
41
|
+
const compatibility = Compatibility?.evaluateNativeCompatibility
|
|
42
|
+
? Compatibility.evaluateNativeCompatibility(response, metadata)
|
|
43
|
+
: options.fallbackNativeCompatibility(response);
|
|
44
|
+
const allowed = !gatedMethods.has(method) ||
|
|
45
|
+
(Compatibility?.isNativeMethodAllowed
|
|
46
|
+
? Compatibility.isNativeMethodAllowed(method, compatibility)
|
|
47
|
+
: compatibility.status === 'ok');
|
|
48
|
+
if (allowed) return { ok: true, compatibility };
|
|
49
|
+
|
|
50
|
+
const message = formatBlockedMessage(method, compatibility);
|
|
51
|
+
const installCommand = options.installCommand;
|
|
52
|
+
const error = {
|
|
53
|
+
code: 'native_update_required',
|
|
54
|
+
message,
|
|
55
|
+
status: compatibility.status || 'unknown_native',
|
|
56
|
+
classification: options.getNativeCompatibilityClassification(compatibility),
|
|
57
|
+
installCommand: compatibility.installCommand || installCommand,
|
|
58
|
+
updateCommand: compatibility.updateCommand || compatibility.installCommand || installCommand,
|
|
59
|
+
currentNativeVersion: compatibility.currentNativeVersion
|
|
60
|
+
|| compatibility.nativeVersion
|
|
61
|
+
|| compatibility.native?.version
|
|
62
|
+
|| '',
|
|
63
|
+
requiredVersion: compatibility.requiredVersion || Compatibility?.BUILD_TARGET_VERSION || '',
|
|
64
|
+
releaseUrl: compatibility.releaseUrl || ''
|
|
65
|
+
};
|
|
66
|
+
notifyBlocked(error, compatibility);
|
|
67
|
+
return { ok: false, compatibility, response: { ok: false, error } };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function attachEvidence(payload = {}, compatibility) {
|
|
71
|
+
if (!compatibility || !gatedMethods.has(payload?.method)) return payload;
|
|
72
|
+
return {
|
|
73
|
+
...payload,
|
|
74
|
+
params: {
|
|
75
|
+
...(payload.params || {}),
|
|
76
|
+
nativeCompatibility: compatibility
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function notifyBlocked(error, compatibility = {}) {
|
|
82
|
+
const message = error?.message || String(error || '');
|
|
83
|
+
if (options.getCurrentRunView?.()) {
|
|
84
|
+
options.appendRunEvent?.({ title: message, status: 'failed' });
|
|
85
|
+
showUpdateGuidance(compatibility);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
showUpdateGuidance(compatibility);
|
|
89
|
+
options.showPluginToast?.(message, { status: 'failed', sticky: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function refreshBadge() {
|
|
93
|
+
const renderer = options.getPanelRendererInstance?.();
|
|
94
|
+
if (!renderer?.headerEl || !Compatibility?.evaluateNativeCompatibility) return;
|
|
95
|
+
try {
|
|
96
|
+
const metadata = options.getExtensionCompatibilityMetadata?.() || {};
|
|
97
|
+
const params = Compatibility.buildBridgePingParams
|
|
98
|
+
? Compatibility.buildBridgePingParams(metadata)
|
|
99
|
+
: {};
|
|
100
|
+
const response = await sendBackgroundNative({ method: 'bridge.ping', params });
|
|
101
|
+
const compatibility = Compatibility.evaluateNativeCompatibility(response, metadata);
|
|
102
|
+
const classification = options.getNativeCompatibilityClassification(compatibility);
|
|
103
|
+
if (classification === 'compatible') {
|
|
104
|
+
options.setBadge?.(renderer.headerEl, { type: 'none' });
|
|
105
|
+
options.setDiagnosticsHealth?.('ok');
|
|
106
|
+
try {
|
|
107
|
+
options.localStorage?.setItem('codexOverleafNativeEverOk', 'true');
|
|
108
|
+
} catch (_storageError) {}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
options.setDiagnosticsHealth?.(classification === 'update-available' ? 'warn' : 'fail');
|
|
112
|
+
options.setBadge?.(renderer.headerEl, {
|
|
113
|
+
type: 'update',
|
|
114
|
+
tooltip: tx('Native host update available', 'Native host 可更新'),
|
|
115
|
+
onClick: () => showUpdateGuidance(compatibility)
|
|
116
|
+
});
|
|
117
|
+
if (!compatibility?.native?.version) maybePromptFirstRunSetup();
|
|
118
|
+
} catch (_error) {
|
|
119
|
+
options.setDiagnosticsHealth?.('fail');
|
|
120
|
+
options.setBadge?.(renderer.headerEl, {
|
|
121
|
+
type: 'update',
|
|
122
|
+
tooltip: tx(
|
|
123
|
+
'Native host is not responding — click for setup steps',
|
|
124
|
+
'Native host 未响应——点击查看安装步骤'
|
|
125
|
+
),
|
|
126
|
+
onClick: () => showUpdateGuidance(options.fallbackNativeCompatibility({ ok: false }))
|
|
127
|
+
});
|
|
128
|
+
maybePromptFirstRunSetup();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function maybePromptFirstRunSetup() {
|
|
133
|
+
const storage = options.localStorage;
|
|
134
|
+
try {
|
|
135
|
+
if (!storage || storage.getItem('codexOverleafSetupPromptShown') === 'true') return;
|
|
136
|
+
if (storage.getItem('codexOverleafNativeEverOk') === 'true') return;
|
|
137
|
+
storage.setItem('codexOverleafSetupPromptShown', 'true');
|
|
138
|
+
} catch (_storageError) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
showUpdateGuidance(options.fallbackNativeCompatibility({ ok: false }));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function showUpdateGuidance(compatibility = {}) {
|
|
145
|
+
let panel = options.getPanel?.();
|
|
146
|
+
if (!panel) {
|
|
147
|
+
options.ensurePanelOpen?.();
|
|
148
|
+
panel = options.getPanel?.();
|
|
149
|
+
}
|
|
150
|
+
if (!panel) return;
|
|
151
|
+
panel.querySelector('[data-native-update-guidance]')?.remove();
|
|
152
|
+
const native = compatibility.native || {};
|
|
153
|
+
const extensionVersion = compatibility.extensionVersion || Compatibility?.BUILD_TARGET_VERSION || '';
|
|
154
|
+
const nativeVersion = compatibility.currentNativeVersion
|
|
155
|
+
|| compatibility.nativeVersion
|
|
156
|
+
|| native.version
|
|
157
|
+
|| tx('missing', '未安装');
|
|
158
|
+
const command = compatibility.updateCommand
|
|
159
|
+
|| compatibility.installCommand
|
|
160
|
+
|| options.installCommand;
|
|
161
|
+
const releaseUrl = compatibility.releaseUrl || '';
|
|
162
|
+
const overlay = options.document.createElement('div');
|
|
163
|
+
overlay.className = 'codex-plugin-confirm';
|
|
164
|
+
overlay.dataset.nativeUpdateGuidance = 'true';
|
|
165
|
+
overlay.setAttribute('role', 'dialog');
|
|
166
|
+
overlay.setAttribute('aria-modal', 'true');
|
|
167
|
+
const nativeMissing = !native.version;
|
|
168
|
+
overlay.setAttribute(
|
|
169
|
+
'aria-label',
|
|
170
|
+
nativeMissing
|
|
171
|
+
? tx('Set up the native host', '安装 Native host')
|
|
172
|
+
: tx('Native host update available', 'Native host 可更新')
|
|
173
|
+
);
|
|
174
|
+
const card = options.document.createElement('section');
|
|
175
|
+
card.className = 'codex-plugin-confirm-card';
|
|
176
|
+
const title = options.document.createElement('div');
|
|
177
|
+
title.className = 'codex-plugin-confirm-title';
|
|
178
|
+
title.textContent = nativeMissing
|
|
179
|
+
? tx('One step left: install the native host', '还差一步:安装 Native host')
|
|
180
|
+
: tx('Native host update available', 'Native host 可更新');
|
|
181
|
+
const body = options.document.createElement('div');
|
|
182
|
+
body.className = 'codex-plugin-confirm-body';
|
|
183
|
+
body.textContent = [
|
|
184
|
+
nativeMissing
|
|
185
|
+
? tx(
|
|
186
|
+
`Extension v${extensionVersion} is ready; Codex still needs its local bridge to read and edit this project.`,
|
|
187
|
+
`扩展 v${extensionVersion} 已就绪;Codex 还需要本地桥接程序才能读写这个项目。`
|
|
188
|
+
)
|
|
189
|
+
: tx(
|
|
190
|
+
`Extension v${extensionVersion} / Native v${nativeVersion}`,
|
|
191
|
+
`扩展 v${extensionVersion} / Native v${nativeVersion}`
|
|
192
|
+
),
|
|
193
|
+
tx(
|
|
194
|
+
'Run the platform-specific command below, reload the extension, then refresh Overleaf.',
|
|
195
|
+
'运行下面的平台命令,重新加载扩展,然后刷新 Overleaf。'
|
|
196
|
+
)
|
|
197
|
+
].join('\n\n');
|
|
198
|
+
const commandCode = options.document.createElement('code');
|
|
199
|
+
commandCode.textContent = command;
|
|
200
|
+
Object.assign(commandCode.style, {
|
|
201
|
+
display: 'block',
|
|
202
|
+
whiteSpace: 'pre-wrap',
|
|
203
|
+
marginTop: '0.75rem'
|
|
204
|
+
});
|
|
205
|
+
body.append(commandCode);
|
|
206
|
+
if (releaseUrl) {
|
|
207
|
+
const link = options.document.createElement('a');
|
|
208
|
+
link.href = releaseUrl;
|
|
209
|
+
link.target = '_blank';
|
|
210
|
+
link.rel = 'noopener noreferrer';
|
|
211
|
+
link.textContent = tx('Open GitHub Release', '打开 GitHub Release');
|
|
212
|
+
Object.assign(link.style, { display: 'block', marginTop: '0.75rem' });
|
|
213
|
+
body.append(link);
|
|
214
|
+
}
|
|
215
|
+
const actions = options.document.createElement('div');
|
|
216
|
+
actions.className = 'codex-plugin-confirm-actions';
|
|
217
|
+
const copy = options.document.createElement('button');
|
|
218
|
+
copy.type = 'button';
|
|
219
|
+
copy.className = 'codex-plugin-confirm-confirm';
|
|
220
|
+
copy.textContent = tx('Copy command', '复制命令');
|
|
221
|
+
copy.addEventListener('click', async () => {
|
|
222
|
+
await options.navigator.clipboard.writeText(command);
|
|
223
|
+
copy.textContent = tx('Copied', '已复制');
|
|
224
|
+
});
|
|
225
|
+
const close = options.document.createElement('button');
|
|
226
|
+
close.type = 'button';
|
|
227
|
+
close.className = 'codex-plugin-confirm-cancel';
|
|
228
|
+
close.textContent = tx('Close', '关闭');
|
|
229
|
+
close.addEventListener('click', () => overlay.remove());
|
|
230
|
+
actions.append(close, copy);
|
|
231
|
+
card.append(title, body, actions);
|
|
232
|
+
overlay.append(card);
|
|
233
|
+
overlay.addEventListener('click', event => {
|
|
234
|
+
if (event.target === overlay) overlay.remove();
|
|
235
|
+
});
|
|
236
|
+
panel.append(overlay);
|
|
237
|
+
copy.focus();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function scheduleFirstUseOnboardingTip() {
|
|
241
|
+
options.setTimeout(() => {
|
|
242
|
+
maybeShowFirstUseOnboardingTip().catch(() => {});
|
|
243
|
+
}, 250);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function maybeShowFirstUseOnboardingTip() {
|
|
247
|
+
const panel = options.getPanel?.();
|
|
248
|
+
if (!panel || (options.getState?.()?.sessions || []).length > 0) return;
|
|
249
|
+
const key = options.onboardingTipStorageKey;
|
|
250
|
+
const stored = await options.chromeApi.storage.local.get([key]);
|
|
251
|
+
if (stored?.[key]) return;
|
|
252
|
+
const modeSelect = panel.querySelector('[data-mode]');
|
|
253
|
+
if (!modeSelect) return;
|
|
254
|
+
const tip = options.document.createElement('div');
|
|
255
|
+
tip.className = 'codex-onboarding-tip';
|
|
256
|
+
tip.dataset.onboardingTip = 'true';
|
|
257
|
+
tip.setAttribute('role', 'status');
|
|
258
|
+
tip.textContent = tx(
|
|
259
|
+
'Tip: Start with Ask mode to explore safely. Switch to Suggest to review edits before applying.',
|
|
260
|
+
'提示:先用 Ask 模式安全探索;需要修改时切换到 Suggest,应用前可先审阅。'
|
|
261
|
+
);
|
|
262
|
+
Object.assign(tip.style, {
|
|
263
|
+
position: 'absolute',
|
|
264
|
+
zIndex: '2147483647',
|
|
265
|
+
maxWidth: '280px'
|
|
266
|
+
});
|
|
267
|
+
panel.append(tip);
|
|
268
|
+
const panelRect = panel.getBoundingClientRect();
|
|
269
|
+
const modeRect = modeSelect.getBoundingClientRect();
|
|
270
|
+
tip.style.left = `${Math.max(12, modeRect.left - panelRect.left)}px`;
|
|
271
|
+
tip.style.bottom = `${Math.max(72, panelRect.bottom - modeRect.top + 8)}px`;
|
|
272
|
+
const dismiss = () => {
|
|
273
|
+
tip.remove();
|
|
274
|
+
options.chromeApi.storage.local.set({ [key]: true }).catch?.(() => {});
|
|
275
|
+
panel.removeEventListener('click', dismiss, true);
|
|
276
|
+
panel.removeEventListener('keydown', dismiss, true);
|
|
277
|
+
};
|
|
278
|
+
panel.addEventListener('click', dismiss, true);
|
|
279
|
+
panel.addEventListener('keydown', dismiss, true);
|
|
280
|
+
options.setTimeout(dismiss, 8000);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function formatBlockedMessage(method, compatibility = {}) {
|
|
284
|
+
const params = { method: method || 'native request' };
|
|
285
|
+
switch (compatibility.status || 'unknown_native') {
|
|
286
|
+
case 'native_too_old':
|
|
287
|
+
return tr('nativeCompatibilityBlockedNativeTooOld', params);
|
|
288
|
+
case 'extension_too_old':
|
|
289
|
+
return tr('nativeCompatibilityBlockedExtensionTooOld', params);
|
|
290
|
+
case 'protocol_unsupported':
|
|
291
|
+
return tr('nativeCompatibilityBlockedProtocol', params);
|
|
292
|
+
case 'native_unhealthy':
|
|
293
|
+
return tr('nativeCompatibilityBlockedUnhealthy', params);
|
|
294
|
+
case 'native_missing':
|
|
295
|
+
return tr('nativeCompatibilityBlockedMissing', params);
|
|
296
|
+
default:
|
|
297
|
+
return tr('nativeCompatibilityBlockedGeneric', params);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return Object.freeze({
|
|
302
|
+
attachEvidence,
|
|
303
|
+
ensureForMethod,
|
|
304
|
+
refreshBadge,
|
|
305
|
+
scheduleFirstUseOnboardingTip,
|
|
306
|
+
sendBackgroundNative,
|
|
307
|
+
sendNative,
|
|
308
|
+
showUpdateGuidance
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return Object.freeze({ create });
|
|
313
|
+
});
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
(function initCodexOverleafOtWarmMirror() {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const otWarmMirrorController = window.CodexOverleafOtWarmMirrorController;
|
|
5
|
-
const runController = window.CodexOverleafRunController;
|
|
6
|
-
const mirrorHealth = window.CodexOverleafMirrorHealth;
|
|
7
|
-
|
|
8
4
|
// Experimental OT warm-mirror glue carved out of contentRuntime.js (v1.4.9
|
|
9
5
|
// structural-debt phase 5): the per-project enable toggle flow, the
|
|
10
6
|
// poll/flush timers and patch queue, mirror prefetch, warm-start
|
|
@@ -15,6 +11,12 @@
|
|
|
15
11
|
// runtime reads it through the exported accessors and clears it on project
|
|
16
12
|
// navigation via clearMirrorPrefetchTimer()/releaseOtWarmMirrorProject().
|
|
17
13
|
function create(deps = {}) {
|
|
14
|
+
const {
|
|
15
|
+
otWarmMirrorController,
|
|
16
|
+
runController,
|
|
17
|
+
mirrorHealth,
|
|
18
|
+
projectFiles
|
|
19
|
+
} = deps;
|
|
18
20
|
const {
|
|
19
21
|
tr,
|
|
20
22
|
tx,
|
|
@@ -853,7 +855,7 @@
|
|
|
853
855
|
project,
|
|
854
856
|
snapshotWarnings,
|
|
855
857
|
focusFiles,
|
|
856
|
-
isUsableProjectFileContent:
|
|
858
|
+
isUsableProjectFileContent: projectFiles.isUsableProjectFileContent
|
|
857
859
|
});
|
|
858
860
|
if (snapshotWarnings.blocking.length && !focusedPartialSnapshot) {
|
|
859
861
|
for (const warning of snapshotWarnings.blocking) {
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
(function initCodexOverleafPageBridgeClient(root, factory) {
|
|
2
|
+
const api = factory();
|
|
3
|
+
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
4
|
+
if (root) root.CodexOverleafPageBridgeClient = api;
|
|
5
|
+
})(typeof window !== 'undefined' ? window : globalThis, function pageBridgeClientFactory() {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const PAGE_WORLD_SCRIPTS = Object.freeze([
|
|
9
|
+
['src/shared/reviewing.js', 'codex-overleaf-reviewing-script'],
|
|
10
|
+
['src/shared/projectFiles.js', 'codex-overleaf-project-files-script'],
|
|
11
|
+
['src/shared/compatibility.js', 'codex-overleaf-compatibility-page-script'],
|
|
12
|
+
['src/shared/pageRpcContract.js', 'codex-overleaf-page-rpc-contract-script'],
|
|
13
|
+
['src/shared/staleGuard.js', 'codex-overleaf-stale-guard-script'],
|
|
14
|
+
['src/shared/compileAdapter.js', 'codex-overleaf-compile-adapter-script'],
|
|
15
|
+
['src/shared/sensitiveScan.js', 'codex-overleaf-sensitive-scan-script'],
|
|
16
|
+
['src/shared/auditRecords.js', 'codex-overleaf-audit-records-script'],
|
|
17
|
+
['src/page/saveState.js', 'codex-overleaf-save-state-script', true],
|
|
18
|
+
['src/page/overleafCapabilities.js', 'codex-overleaf-capabilities-script'],
|
|
19
|
+
['src/page/compileBridge.js', 'codex-overleaf-compile-bridge-script'],
|
|
20
|
+
['src/page/overleafEditor.js', 'codex-overleaf-editor-script'],
|
|
21
|
+
['src/page/overleafProjectSnapshot.js', 'codex-overleaf-project-snapshot-script'],
|
|
22
|
+
['src/page/pageBridgeCapability.js', 'codex-overleaf-page-bridge-capability-script'],
|
|
23
|
+
['src/page/treeOperations.js', 'codex-overleaf-tree-operations-script', true],
|
|
24
|
+
['src/page/snapshotRouter.js', 'codex-overleaf-snapshot-router-script'],
|
|
25
|
+
['src/page/writeGuard.js', 'codex-overleaf-write-guard-script', true],
|
|
26
|
+
['src/page/trackedChangesLifecycle.js', 'codex-overleaf-tracked-changes-lifecycle-script', true],
|
|
27
|
+
['src/page/writebackRouter.js', 'codex-overleaf-writeback-router-script', true],
|
|
28
|
+
['src/pageBridge.js', 'codex-overleaf-page-bridge-script', true]
|
|
29
|
+
]);
|
|
30
|
+
const OPTIONAL_OT_SCRIPTS = Object.freeze([
|
|
31
|
+
['src/shared/otText.js', 'codex-overleaf-ot-text-script'],
|
|
32
|
+
['src/page/overleafRealtimeObserver.js', 'codex-overleaf-realtime-observer-script']
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
function create(options = {}) {
|
|
36
|
+
const windowRef = options.window || globalThis.window;
|
|
37
|
+
const documentRef = options.document || windowRef?.document;
|
|
38
|
+
const chromeApi = options.chromeApi || globalThis.chrome;
|
|
39
|
+
const cryptoImpl = options.crypto || globalThis.crypto;
|
|
40
|
+
const runtimeRoot = options.root || windowRef;
|
|
41
|
+
const Contract = options.contract;
|
|
42
|
+
const Compatibility = options.compatibility;
|
|
43
|
+
const timeoutOverrides = { ...(options.timeoutOverrides || {}) };
|
|
44
|
+
const revision = Contract.REVISION;
|
|
45
|
+
const capability = createCapability(cryptoImpl);
|
|
46
|
+
const activeCancellationHandlers = new Map();
|
|
47
|
+
let readyPromise = null;
|
|
48
|
+
|
|
49
|
+
function start() {
|
|
50
|
+
if (!readyPromise) readyPromise = injectPageBridge();
|
|
51
|
+
return readyPromise;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function call(method, params) {
|
|
55
|
+
try {
|
|
56
|
+
await start();
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return { ok: false, error: `Page bridge unavailable: ${error.message}` };
|
|
59
|
+
}
|
|
60
|
+
return send(method, params, Contract.resolveDispatchPolicy(method, timeoutOverrides));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function send(method, params, policy = {}) {
|
|
64
|
+
const id = cryptoImpl.randomUUID();
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
const timeoutMs = Number.isFinite(Number(policy.timeoutMs))
|
|
67
|
+
? Number(policy.timeoutMs)
|
|
68
|
+
: Contract.resolveTimeoutMs(method, timeoutOverrides);
|
|
69
|
+
const cancellable = policy.cancellation === 'content_abort';
|
|
70
|
+
let settled = false;
|
|
71
|
+
let timeout = null;
|
|
72
|
+
function cleanup() {
|
|
73
|
+
if (timeout !== null) windowRef.clearTimeout(timeout);
|
|
74
|
+
windowRef.removeEventListener('message', onMessage);
|
|
75
|
+
if (cancellable) activeCancellationHandlers.delete(id);
|
|
76
|
+
}
|
|
77
|
+
function resolveOnce(value) {
|
|
78
|
+
if (settled) return;
|
|
79
|
+
settled = true;
|
|
80
|
+
cleanup();
|
|
81
|
+
resolve(value);
|
|
82
|
+
}
|
|
83
|
+
function rejectOnce(error) {
|
|
84
|
+
if (settled) return;
|
|
85
|
+
settled = true;
|
|
86
|
+
cleanup();
|
|
87
|
+
reject(error);
|
|
88
|
+
}
|
|
89
|
+
function cancelRequest() {
|
|
90
|
+
const error = new Error('Codex run was cancelled by the user');
|
|
91
|
+
error.code = 'codex_cancelled';
|
|
92
|
+
error.cancelled = true;
|
|
93
|
+
rejectOnce(error);
|
|
94
|
+
}
|
|
95
|
+
timeout = windowRef.setTimeout(() => {
|
|
96
|
+
resolveOnce({ ok: false, error: 'Page bridge timed out' });
|
|
97
|
+
}, timeoutMs);
|
|
98
|
+
function onMessage(event) {
|
|
99
|
+
if (event.source !== windowRef
|
|
100
|
+
|| event.origin !== windowRef.location.origin
|
|
101
|
+
|| event.data?.source !== 'codex-overleaf/page'
|
|
102
|
+
|| event.data?.pageBridgeVersion !== Compatibility?.BUILD_TARGET_VERSION
|
|
103
|
+
|| event.data?.pageBridgeRevision !== revision
|
|
104
|
+
|| event.data.id !== id) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
resolveOnce(event.data.result);
|
|
108
|
+
}
|
|
109
|
+
if (cancellable) {
|
|
110
|
+
activeCancellationHandlers.set(id, cancelRequest);
|
|
111
|
+
if (options.isCancellationRequested?.()) windowRef.queueMicrotask(cancelRequest);
|
|
112
|
+
}
|
|
113
|
+
windowRef.addEventListener('message', onMessage);
|
|
114
|
+
// Every low-level request remains one attempt. Higher layers must opt
|
|
115
|
+
// into replay so a document mutation is never duplicated implicitly.
|
|
116
|
+
windowRef.postMessage({
|
|
117
|
+
source: 'codex-overleaf/content',
|
|
118
|
+
id,
|
|
119
|
+
method,
|
|
120
|
+
params,
|
|
121
|
+
capability
|
|
122
|
+
}, windowRef.location.origin);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function cancelActiveRequests() {
|
|
127
|
+
const handlers = Array.from(activeCancellationHandlers.values());
|
|
128
|
+
activeCancellationHandlers.clear();
|
|
129
|
+
handlers.forEach(cancel => cancel());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function resolveTimeoutMs(method) {
|
|
133
|
+
return Contract.resolveTimeoutMs(method, timeoutOverrides);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function injectPageBridge() {
|
|
137
|
+
const splitAt = PAGE_WORLD_SCRIPTS.findIndex(([src]) =>
|
|
138
|
+
src === 'src/page/pageBridgeCapability.js'
|
|
139
|
+
);
|
|
140
|
+
for (const [src, id, force] of PAGE_WORLD_SCRIPTS.slice(0, splitAt)) {
|
|
141
|
+
await injectScriptOnce(src, id, { force });
|
|
142
|
+
}
|
|
143
|
+
await injectOptionalOtDependencies();
|
|
144
|
+
for (const [src, id, force] of PAGE_WORLD_SCRIPTS.slice(splitAt)) {
|
|
145
|
+
await injectScriptOnce(src, id, { force });
|
|
146
|
+
}
|
|
147
|
+
await initializeCapability();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function injectOptionalOtDependencies() {
|
|
151
|
+
try {
|
|
152
|
+
for (const [src, id] of OPTIONAL_OT_SCRIPTS) await injectScriptOnce(src, id);
|
|
153
|
+
} catch (_error) {
|
|
154
|
+
// The page bridge keeps its read-only unavailable fallback.
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function injectScriptOnce(src, id, injectOptions = {}) {
|
|
159
|
+
return new Promise((resolve, reject) => {
|
|
160
|
+
const existing = documentRef.getElementById(id);
|
|
161
|
+
if (existing && injectOptions.force !== true) {
|
|
162
|
+
resolve();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
existing?.remove?.();
|
|
166
|
+
const script = documentRef.createElement('script');
|
|
167
|
+
const timeout = windowRef.setTimeout(() => {
|
|
168
|
+
cleanup();
|
|
169
|
+
reject(new Error(`Timed out loading ${src}`));
|
|
170
|
+
}, 8000);
|
|
171
|
+
function cleanup() {
|
|
172
|
+
windowRef.clearTimeout(timeout);
|
|
173
|
+
script.onload = null;
|
|
174
|
+
script.onerror = null;
|
|
175
|
+
script.remove();
|
|
176
|
+
}
|
|
177
|
+
script.id = id;
|
|
178
|
+
const runtimePrefix = String(runtimeRoot.__CODEX_OVERLEAF_RUNTIME_PREFIX__ || '');
|
|
179
|
+
script.src = chromeApi.runtime.getURL(runtimePrefix + src);
|
|
180
|
+
script.onload = () => {
|
|
181
|
+
cleanup();
|
|
182
|
+
resolve();
|
|
183
|
+
};
|
|
184
|
+
script.onerror = () => {
|
|
185
|
+
cleanup();
|
|
186
|
+
reject(new Error(`Failed to load ${src}`));
|
|
187
|
+
};
|
|
188
|
+
(documentRef.head || documentRef.documentElement).append(script);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function initializeCapability() {
|
|
193
|
+
const result = await send(
|
|
194
|
+
'initializeCapability',
|
|
195
|
+
{},
|
|
196
|
+
Contract.resolveDispatchPolicy('initializeCapability', timeoutOverrides)
|
|
197
|
+
);
|
|
198
|
+
if (!result?.ok) {
|
|
199
|
+
throw new Error(result?.error || result?.reason || 'Page bridge capability initialization failed');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return Object.freeze({
|
|
204
|
+
call,
|
|
205
|
+
cancelActiveRequests,
|
|
206
|
+
resolveTimeoutMs,
|
|
207
|
+
send,
|
|
208
|
+
start
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function createCapability(cryptoImpl) {
|
|
213
|
+
if (cryptoImpl?.randomUUID) return cryptoImpl.randomUUID();
|
|
214
|
+
const bytes = new Uint8Array(24);
|
|
215
|
+
cryptoImpl.getRandomValues(bytes);
|
|
216
|
+
return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return Object.freeze({
|
|
220
|
+
OPTIONAL_OT_SCRIPTS,
|
|
221
|
+
PAGE_WORLD_SCRIPTS,
|
|
222
|
+
create
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
setState,
|
|
30
30
|
getCurrentRunView,
|
|
31
31
|
getSettingsPanelInstance,
|
|
32
|
+
StorageDb
|
|
32
33
|
} = deps;
|
|
33
34
|
|
|
34
35
|
// --- Change history (v1.7.5): the audit log finally gets a read path. ---
|
|
@@ -48,7 +49,6 @@
|
|
|
48
49
|
list.textContent = tx('Loading\u2026', '正在加载…');
|
|
49
50
|
let records = [];
|
|
50
51
|
try {
|
|
51
|
-
const StorageDb = window.CodexOverleafStorageDb;
|
|
52
52
|
records = StorageDb?.getAllByIndex
|
|
53
53
|
? await StorageDb.getAllByIndex('auditLogs', 'projectId', getCurrentProjectId())
|
|
54
54
|
: [];
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
return;
|
|
172
172
|
}
|
|
173
173
|
try {
|
|
174
|
-
await
|
|
174
|
+
await StorageDb?.clearAllStores?.();
|
|
175
175
|
// v1.8.1: the dashboard's project-name cache lives in
|
|
176
176
|
// chrome.storage.local, outside the IndexedDB stores — clear it too so
|
|
177
177
|
// no (potentially sensitive) project titles outlive the wipe.
|
|
@@ -244,7 +244,12 @@
|
|
|
244
244
|
return;
|
|
245
245
|
}
|
|
246
246
|
try {
|
|
247
|
-
await callPageBridge('jumpToPosition', {
|
|
247
|
+
await callPageBridge('jumpToPosition', {
|
|
248
|
+
path: target,
|
|
249
|
+
from: 0,
|
|
250
|
+
to: 0,
|
|
251
|
+
runProjectId: getCurrentProjectId()
|
|
252
|
+
});
|
|
248
253
|
} catch (error) {
|
|
249
254
|
showPluginToast(tx(`Could not open ${target}: ${error.message}`, `无法打开 ${target}:${error.message}`));
|
|
250
255
|
}
|