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.
Files changed (71) hide show
  1. package/README.md +25 -23
  2. package/extension/bootstrap/manifest.template.json +1 -1
  3. package/extension/runtime-manifest.json +5 -71
  4. package/extension/src/background.js +53 -69
  5. package/extension/src/backgroundUpdateCoordinator.js +128 -66
  6. package/extension/src/content/applyResultFormatters.js +2 -3
  7. package/extension/src/content/contentRuntime.js +650 -1449
  8. package/extension/src/content/contextTray.js +4 -6
  9. package/extension/src/content/diagnosticsController.js +1 -2
  10. package/extension/src/content/diffReviewPanel.js +14 -13
  11. package/extension/src/content/generated/content.bundle.js +41370 -0
  12. package/extension/src/content/generated/content.bundle.meta.json +102 -0
  13. package/extension/src/content/legacyGlobalRegistry.js +129 -0
  14. package/extension/src/content/markdownText.js +5 -7
  15. package/extension/src/content/modelPicker.js +1 -1
  16. package/extension/src/content/modelPickerSupport.js +10 -5
  17. package/extension/src/content/moduleRegistryKernel.js +125 -0
  18. package/extension/src/content/nativeCompatibilityController.js +313 -0
  19. package/extension/src/content/otWarmMirror.js +7 -5
  20. package/extension/src/content/pageBridgeClient.js +224 -0
  21. package/extension/src/content/panelMaintenance.js +8 -3
  22. package/extension/src/content/postNavigationSettlementPersistence.js +100 -0
  23. package/extension/src/content/projectProviderSelection.js +4 -12
  24. package/extension/src/content/projectSessionCleanup.js +3 -3
  25. package/extension/src/content/providerSettingsCoordinator.js +59 -26
  26. package/extension/src/content/providerSettingsDialog.js +30 -18
  27. package/extension/src/content/recentProjects.js +15 -23
  28. package/extension/src/content/runGuidanceController.js +1 -1
  29. package/extension/src/content/runQueueScheduler.js +129 -29
  30. package/extension/src/content/runSettlementPersistence.js +51 -0
  31. package/extension/src/content/runTimelineView.js +11 -8
  32. package/extension/src/content/scopedPersistenceCoordinator.js +100 -0
  33. package/extension/src/content/scopedPersistenceDetachedCommit.js +47 -0
  34. package/extension/src/content/scopedPersistencePanelState.js +111 -0
  35. package/extension/src/content/sessionManager.js +15 -12
  36. package/extension/src/content/sessionPersistence.js +67 -8
  37. package/extension/src/content/updateNotice.js +10 -4
  38. package/extension/src/content/writebackOrchestrator.js +91 -23
  39. package/extension/src/contentScript.js +5 -1
  40. package/extension/src/page/trackedChangesLifecycle.js +6 -1
  41. package/extension/src/pageBridge.js +47 -98
  42. package/extension/src/shared/agentTranscript.js +8 -5
  43. package/extension/src/shared/compatibility.js +1 -1
  44. package/extension/src/shared/governanceRules.js +1 -1
  45. package/extension/src/shared/managedUpdateProjection.js +337 -0
  46. package/extension/src/shared/nativeRequestIdentity.js +60 -0
  47. package/extension/src/shared/otText.js +0 -19
  48. package/extension/src/shared/pageRpcContract.js +136 -0
  49. package/extension/src/shared/runExecutionSnapshot.js +177 -0
  50. package/extension/src/shared/runExecutionSnapshotCodec.js +168 -0
  51. package/extension/src/shared/runInputQueue.js +81 -34
  52. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +79 -0
  53. package/extension/src/shared/scopedPersistenceQueuePolicy.js +181 -0
  54. package/extension/src/shared/scopedPersistenceTransaction.js +238 -0
  55. package/extension/src/shared/sessionState.js +64 -48
  56. package/extension/src/shared/settlementFacts.js +351 -0
  57. package/extension/src/shared/storageDb.js +19 -5
  58. package/extension/src/shared/storageMigration.js +275 -61
  59. package/extension/src/shared/storageSessionClaims.js +60 -0
  60. package/extension/src/shared/updateConsent.js +4 -0
  61. package/extension/src/shared/updateRevocationIntent.js +109 -0
  62. package/extension/src/shared/writebackEvidenceProjection.js +150 -0
  63. package/extension/src/shared/writebackOperationEvidence.js +143 -0
  64. package/extension/src/shared/writebackSettlement.js +705 -0
  65. package/native-host/src/codexRuntimeIdentity.js +1 -1
  66. package/native-host/src/codexSessionRunner.js +7 -13
  67. package/native-host/src/index.js +3 -69
  68. package/native-host/src/managedInstall.js +50 -8
  69. package/native-host/src/nativeTransportEnvelope.js +148 -0
  70. package/native-host/src/taskRunnerRuntime.js +3 -3
  71. 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: window.CodexOverleafProjectFiles.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 window.CodexOverleafStorageDb?.clearAllStores?.();
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', { path: target, from: 0, to: 0 });
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
  }