codex-overleaf-link 2.1.5 → 2.2.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 +27 -23
- package/extension/bootstrap/background.js +14 -1
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/runtime-manifest.json +10 -0
- package/extension/src/background.js +507 -8
- package/extension/src/backgroundUpdateCoordinator.js +284 -19
- package/extension/src/content/activeTurnControl.js +179 -0
- package/extension/src/content/composerPanel.js +14 -5
- package/extension/src/content/contentRuntime.js +330 -309
- package/extension/src/content/diagnosticsPanel.js +132 -1
- package/extension/src/content/markdownDomRenderer.js +451 -0
- package/extension/src/content/markdownText.js +86 -7
- package/extension/src/content/mathText.js +49 -4
- package/extension/src/content/modelPicker.js +14 -5
- package/extension/src/content/nativeChannel.js +7 -1
- package/extension/src/content/panelRenderer.js +112 -12
- package/extension/src/content/pendingInputView.js +106 -0
- package/extension/src/content/projectProviderSelection.js +68 -0
- package/extension/src/content/projectSessionCleanup.js +4 -0
- package/extension/src/content/providerSettingsCoordinator.js +170 -22
- package/extension/src/content/providerSettingsDialog.js +2 -2
- package/extension/src/content/recentProjects.js +7 -2
- package/extension/src/content/runGuidanceController.js +70 -0
- package/extension/src/content/runGuidanceView.js +48 -0
- package/extension/src/content/runQueueScheduler.js +260 -0
- package/extension/src/content/runTimelineView.js +33 -80
- package/extension/src/content/sessionManager.js +2 -2
- package/extension/src/content/sessionPersistence.js +62 -0
- package/extension/src/content/settingsPanel.js +1 -1
- package/extension/src/content/updateNotice.js +80 -1
- package/extension/src/page/writebackRouter.js +3 -9
- package/extension/src/shared/compatibility.js +2 -1
- package/extension/src/shared/i18n.js +38 -0
- package/extension/src/shared/providerProfiles.js +3 -2
- package/extension/src/shared/runInputQueue.js +253 -0
- package/extension/src/shared/sessionState.js +45 -22
- package/extension/src/shared/storageDb.js +39 -44
- package/extension/src/shared/storageMigration.js +123 -3
- package/extension/src/shared/storageRunActions.js +34 -1
- package/extension/src/shared/updateConsent.js +15 -4
- package/extension/styles/panel.css +319 -20
- package/extension/vendor/katex/katex.min.css +1 -1
- package/extension/vendor/markdown-it/LICENSE +22 -0
- package/extension/vendor/markdown-it/SOURCE.json +7 -0
- package/extension/vendor/markdown-it/VERSION +1 -0
- package/extension/vendor/markdown-it/markdown-it.min.js +3 -0
- package/native-host/src/anthropicBridgeRequest.js +3 -3
- package/native-host/src/anthropicBridgeResponse.js +58 -17
- package/native-host/src/anthropicMessagesBridge.js +156 -23
- package/native-host/src/chatBridgeRequest.js +1 -1
- package/native-host/src/chatBridgeResponse.js +12 -8
- package/native-host/src/chatCompletionsBridge.js +22 -40
- package/native-host/src/codexPromptAssembly.js +3 -1
- package/native-host/src/codexProviderLaunch.js +6 -3
- package/native-host/src/codexSessionRunner.js +65 -65
- package/native-host/src/codexSessionTiming.js +45 -0
- package/native-host/src/index.js +58 -3
- package/native-host/src/managedInstall.js +45 -7
- package/native-host/src/providerProfile.js +2 -2
- package/native-host/src/providerStreamLifecycle.js +100 -0
- package/native-host/src/responsesPassthroughBridge.js +636 -0
- package/native-host/src/taskRunnerRuntime.js +81 -2
- package/native-host/src/updateArchive.js +4 -3
- package/native-host/src/updateManager.js +306 -19
- package/native-host/src/updateTrust.js +1 -1
- package/package.json +3 -1
|
@@ -6,8 +6,22 @@ if (!globalThis.CodexOverleafCompatibility) {
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const HOST_NAME = 'com.codex.overleaf';
|
|
9
|
+
const MANAGED_UPDATE_STATE_KEY = 'codex-overleaf-managed-update-state-v1';
|
|
10
|
+
const MANAGED_UPDATE_TABS_KEY = 'codex-overleaf-managed-update-tabs-v1';
|
|
11
|
+
const MANAGED_OVERLEAF_MATCHES = [
|
|
12
|
+
'https://www.overleaf.com/project',
|
|
13
|
+
'https://overleaf.com/project',
|
|
14
|
+
'https://www.overleaf.com/project/*',
|
|
15
|
+
'https://overleaf.com/project/*'
|
|
16
|
+
];
|
|
17
|
+
const MANAGED_UPDATE_PHASE_TIMEOUT_MS = Object.freeze({
|
|
18
|
+
applying: 90 * 1000,
|
|
19
|
+
awaiting_health: 2 * 60 * 1000,
|
|
20
|
+
rolling_back: 60 * 1000
|
|
21
|
+
});
|
|
9
22
|
const COMPATIBILITY_REQUIRED_METHODS = new Set([
|
|
10
23
|
'codex.run',
|
|
24
|
+
'codex.steer',
|
|
11
25
|
'task.run',
|
|
12
26
|
'task.confirm',
|
|
13
27
|
'mirror.sync',
|
|
@@ -34,16 +48,57 @@ if (!globalThis.CodexOverleafCompatibility) {
|
|
|
34
48
|
]);
|
|
35
49
|
const CodexOverleafCompatibility = globalThis.CodexOverleafCompatibility;
|
|
36
50
|
let port = null;
|
|
51
|
+
let managedUpdateExecutionLocked = false;
|
|
37
52
|
const pending = new Map();
|
|
53
|
+
const runJournals = new Map();
|
|
54
|
+
const journalWrites = new Map();
|
|
55
|
+
const ownerBindings = new Map();
|
|
56
|
+
const ownerCancelTimers = new Map();
|
|
57
|
+
const RUN_JOURNAL_PREFIX = 'codex-overleaf:run-journal:';
|
|
58
|
+
const RUN_JOURNAL_MAX_EVENTS = 300;
|
|
59
|
+
const RUN_JOURNAL_MAX_BYTES = 512 * 1024;
|
|
60
|
+
const managedBootstrapRuntime = isManagedBootstrapRuntime();
|
|
38
61
|
globalThis.CodexOverleafNativeBridge = Object.freeze({
|
|
39
|
-
requestInternal: payload =>
|
|
62
|
+
requestInternal: payload => requestManagedInternal(payload),
|
|
40
63
|
getPendingState: () => ({
|
|
41
64
|
executionRequests: Array.from(pending.values()).filter(item => item.retryClass === 'no_silent_retry').length,
|
|
42
65
|
totalRequests: pending.size
|
|
43
66
|
})
|
|
44
67
|
});
|
|
45
68
|
|
|
69
|
+
if (managedBootstrapRuntime) {
|
|
70
|
+
chrome.storage?.onChanged?.addListener((changes, area) => {
|
|
71
|
+
if (area !== 'local' || !changes[MANAGED_UPDATE_STATE_KEY]?.newValue) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
void repairManagedUpdatePhaseMetadata(changes[MANAGED_UPDATE_STATE_KEY].newValue);
|
|
75
|
+
});
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
void reconcileManagedUpdateTransaction();
|
|
78
|
+
}, 250);
|
|
79
|
+
}
|
|
80
|
+
|
|
46
81
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
82
|
+
if (message?.type === 'codex-overleaf/run-journal/list') {
|
|
83
|
+
if (!isAllowedOverleafSender(sender)) {
|
|
84
|
+
sendResponse({ ok: false, journals: [] });
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
listRunJournals(String(message.projectKey || ''))
|
|
88
|
+
.then(journals => sendResponse({ ok: true, journals }))
|
|
89
|
+
.catch(error => sendResponse({ ok: false, journals: [], error: getErrorMessage(error, 'Journal read failed.') }));
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (message?.type === 'codex-overleaf/run-journal/ack') {
|
|
93
|
+
if (!isAllowedOverleafSender(sender)) {
|
|
94
|
+
sendResponse({ ok: false });
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
acknowledgeRunJournal(String(message.requestId || ''))
|
|
98
|
+
.then(() => sendResponse({ ok: true }))
|
|
99
|
+
.catch(error => sendResponse({ ok: false, error: getErrorMessage(error, 'Journal cleanup failed.') }));
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
47
102
|
if (message?.type !== 'codex-overleaf/native-request') {
|
|
48
103
|
return undefined;
|
|
49
104
|
}
|
|
@@ -103,6 +158,42 @@ if (!globalThis.CodexOverleafCompatibility) {
|
|
|
103
158
|
return true;
|
|
104
159
|
});
|
|
105
160
|
|
|
161
|
+
chrome.runtime.onConnect?.addListener(portConnection => {
|
|
162
|
+
if (portConnection.name !== 'codex-overleaf/run-owner' || !isAllowedOverleafSender(portConnection.sender)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
portConnection.onMessage.addListener(message => {
|
|
166
|
+
const requestId = String(message?.requestId || '');
|
|
167
|
+
if (!requestId) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (message.type === 'release') {
|
|
171
|
+
ownerBindings.delete(portConnection);
|
|
172
|
+
clearTimeout(ownerCancelTimers.get(requestId));
|
|
173
|
+
ownerCancelTimers.delete(requestId);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (message.type === 'bind') {
|
|
177
|
+
ownerBindings.set(portConnection, {
|
|
178
|
+
requestId,
|
|
179
|
+
projectKey: String(message.projectKey || ''),
|
|
180
|
+
documentId: String(portConnection.sender?.documentId || '')
|
|
181
|
+
});
|
|
182
|
+
clearTimeout(ownerCancelTimers.get(requestId));
|
|
183
|
+
ownerCancelTimers.delete(requestId);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
portConnection.onDisconnect.addListener(() => {
|
|
187
|
+
const binding = ownerBindings.get(portConnection);
|
|
188
|
+
ownerBindings.delete(portConnection);
|
|
189
|
+
if (!binding?.requestId) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const timer = setTimeout(() => interruptOwnerlessRun(binding), 150);
|
|
193
|
+
ownerCancelTimers.set(binding.requestId, timer);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
106
197
|
function getNativeRetryClass(method) {
|
|
107
198
|
switch (method) {
|
|
108
199
|
case 'bridge.ping':
|
|
@@ -141,6 +232,18 @@ if (!globalThis.CodexOverleafCompatibility) {
|
|
|
141
232
|
});
|
|
142
233
|
}
|
|
143
234
|
const request = sanitizeNativeRequest(requestWithEvidence);
|
|
235
|
+
if (managedUpdateExecutionLocked &&
|
|
236
|
+
!request.method.startsWith('update.') &&
|
|
237
|
+
request.method !== 'codex.cancel' &&
|
|
238
|
+
getNativeRetryClass(request.method) === 'no_silent_retry') {
|
|
239
|
+
return Promise.resolve({
|
|
240
|
+
ok: false,
|
|
241
|
+
error: {
|
|
242
|
+
code: 'update_applying',
|
|
243
|
+
message: 'A managed update is activating. Retry after the extension reloads.'
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
144
247
|
|
|
145
248
|
return new Promise((resolve, reject) => {
|
|
146
249
|
const pendingRequest = {
|
|
@@ -156,6 +259,9 @@ if (!globalThis.CodexOverleafCompatibility) {
|
|
|
156
259
|
eventForwarded: false
|
|
157
260
|
};
|
|
158
261
|
pending.set(id, pendingRequest);
|
|
262
|
+
if (request.method === 'codex.run') {
|
|
263
|
+
createRunJournal(id, pendingRequest);
|
|
264
|
+
}
|
|
159
265
|
|
|
160
266
|
try {
|
|
161
267
|
postNativeRequest(pendingRequest);
|
|
@@ -165,6 +271,213 @@ if (!globalThis.CodexOverleafCompatibility) {
|
|
|
165
271
|
});
|
|
166
272
|
}
|
|
167
273
|
|
|
274
|
+
async function requestManagedInternal(payload) {
|
|
275
|
+
if (managedBootstrapRuntime && payload?.method === 'update.apply') {
|
|
276
|
+
const gate = await verifyManagedUpdateSafety();
|
|
277
|
+
if (!gate.ok) {
|
|
278
|
+
return gate;
|
|
279
|
+
}
|
|
280
|
+
managedUpdateExecutionLocked = true;
|
|
281
|
+
try {
|
|
282
|
+
const result = await sendNativeRequest(payload, null, { skipCompatibility: true });
|
|
283
|
+
if (!result?.ok) {
|
|
284
|
+
managedUpdateExecutionLocked = false;
|
|
285
|
+
}
|
|
286
|
+
return result;
|
|
287
|
+
} catch (error) {
|
|
288
|
+
managedUpdateExecutionLocked = false;
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return sendNativeRequest(payload, null, { skipCompatibility: true });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function verifyManagedUpdateSafety() {
|
|
296
|
+
const tabs = await chrome.tabs.query({ url: MANAGED_OVERLEAF_MATCHES }).catch(() => []);
|
|
297
|
+
const editorTabs = tabs.filter(tab => (
|
|
298
|
+
isManagedEditorTab(tab) && !tab.discarded && tab.status !== 'unloaded'
|
|
299
|
+
));
|
|
300
|
+
const probes = await Promise.all(editorTabs.map(tab => probeManagedUpdateTab(tab.id)));
|
|
301
|
+
const nativeGate = await sendNativeRequest({
|
|
302
|
+
id: crypto.randomUUID(),
|
|
303
|
+
method: 'update.canApply',
|
|
304
|
+
params: {}
|
|
305
|
+
}, null, { skipCompatibility: true }).catch(error => ({
|
|
306
|
+
ok: false,
|
|
307
|
+
error: {
|
|
308
|
+
code: error?.code || 'native_connection_failed',
|
|
309
|
+
message: getErrorMessage(error, 'Native Host update safety check failed.')
|
|
310
|
+
}
|
|
311
|
+
}));
|
|
312
|
+
const pendingState = globalThis.CodexOverleafNativeBridge?.getPendingState?.() || {};
|
|
313
|
+
const backgroundBlockers = Number(pendingState.executionRequests || 0) > 0
|
|
314
|
+
? ['background_execution_pending']
|
|
315
|
+
: [];
|
|
316
|
+
const blockers = [
|
|
317
|
+
...backgroundBlockers,
|
|
318
|
+
...(globalThis.CodexOverleafUpdateStatus?.collectBlockers(probes, nativeGate) || ['busy'])
|
|
319
|
+
];
|
|
320
|
+
if (!blockers.length) {
|
|
321
|
+
return { ok: true };
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
ok: false,
|
|
325
|
+
error: {
|
|
326
|
+
code: 'update_not_idle',
|
|
327
|
+
message: 'The update paused because Overleaf or the Native Host became busy. It will retry at the next safe point.',
|
|
328
|
+
blockers
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function probeManagedUpdateTab(tabId) {
|
|
334
|
+
if (!Number.isInteger(tabId)) {
|
|
335
|
+
return { idle: false, blockers: ['tab_unavailable'] };
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
return await Promise.race([
|
|
339
|
+
chrome.tabs.sendMessage(tabId, { type: 'codex-overleaf/update-idle-probe' }),
|
|
340
|
+
new Promise(resolve => setTimeout(() => resolve({
|
|
341
|
+
idle: false,
|
|
342
|
+
blockers: ['tab_probe_timeout']
|
|
343
|
+
}), 3500))
|
|
344
|
+
]);
|
|
345
|
+
} catch (_error) {
|
|
346
|
+
return { idle: false, blockers: ['tab_probe_unavailable'] };
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function isManagedEditorTab(tab) {
|
|
351
|
+
try {
|
|
352
|
+
const url = new URL(tab?.url || '');
|
|
353
|
+
return url.protocol === 'https:' &&
|
|
354
|
+
(url.hostname === 'www.overleaf.com' || url.hostname === 'overleaf.com') &&
|
|
355
|
+
/^\/project\/[^/]+(?:\/|$)/.test(url.pathname);
|
|
356
|
+
} catch (_error) {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function isManagedBootstrapRuntime() {
|
|
362
|
+
if (typeof chrome.runtime?.getManifest !== 'function') {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
return chrome.runtime.getManifest().background?.service_worker === 'bootstrap/background.js';
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function repairManagedUpdatePhaseMetadata(state) {
|
|
369
|
+
const timeoutMs = MANAGED_UPDATE_PHASE_TIMEOUT_MS[state?.state] || 0;
|
|
370
|
+
if (!timeoutMs || Number(state.deadlineAt || 0) > 0) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const now = Date.now();
|
|
374
|
+
await chrome.storage.local.set({
|
|
375
|
+
[MANAGED_UPDATE_STATE_KEY]: {
|
|
376
|
+
...state,
|
|
377
|
+
phaseStartedAt: now,
|
|
378
|
+
deadlineAt: now + timeoutMs,
|
|
379
|
+
heartbeatAt: now
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function reconcileManagedUpdateTransaction() {
|
|
385
|
+
const response = await sendNativeRequest({
|
|
386
|
+
id: crypto.randomUUID(),
|
|
387
|
+
method: 'update.status',
|
|
388
|
+
params: {}
|
|
389
|
+
}, null, { skipCompatibility: true }).catch(() => null);
|
|
390
|
+
if (!response?.ok) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const transaction = response.result?.transaction || null;
|
|
395
|
+
const state = await getManagedUpdateState();
|
|
396
|
+
if (transaction?.state === 'rolled_back') {
|
|
397
|
+
await setManagedUpdateState({
|
|
398
|
+
...state,
|
|
399
|
+
state: 'rolled_back',
|
|
400
|
+
currentVersion: transaction.sourceVersion || state.currentVersion,
|
|
401
|
+
transactionId: transaction.id || state.transactionId,
|
|
402
|
+
code: transaction.reasonCode || state.code || 'update_rolled_back'
|
|
403
|
+
});
|
|
404
|
+
await reloadManagedUpdateTabs();
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (transaction?.state === 'committed') {
|
|
408
|
+
const targetVersion = transaction.targetVersion || state.latestVersion || state.currentVersion;
|
|
409
|
+
await setManagedUpdateState({
|
|
410
|
+
...state,
|
|
411
|
+
state: 'committed',
|
|
412
|
+
currentVersion: targetVersion,
|
|
413
|
+
latestVersion: targetVersion,
|
|
414
|
+
transactionId: '',
|
|
415
|
+
blocker: '',
|
|
416
|
+
blockers: [],
|
|
417
|
+
code: '',
|
|
418
|
+
message: ''
|
|
419
|
+
});
|
|
420
|
+
await reloadManagedUpdateTabs();
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (transaction?.state === 'staged' && ['applying', 'awaiting_health'].includes(state.state)) {
|
|
424
|
+
await setManagedUpdateState({
|
|
425
|
+
...state,
|
|
426
|
+
state: 'staged',
|
|
427
|
+
transactionId: transaction.id || state.transactionId,
|
|
428
|
+
blocker: '',
|
|
429
|
+
blockers: [],
|
|
430
|
+
code: '',
|
|
431
|
+
message: '',
|
|
432
|
+
phaseStartedAt: 0,
|
|
433
|
+
deadlineAt: 0,
|
|
434
|
+
heartbeatAt: 0
|
|
435
|
+
});
|
|
436
|
+
setTimeout(() => {
|
|
437
|
+
void globalThis.CodexOverleafManagedUpdateExecutor?.installAuthorizedUpdate?.().catch?.(() => {});
|
|
438
|
+
}, 250);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (!transaction && ['applying', 'awaiting_health', 'rolling_back'].includes(state.state)) {
|
|
442
|
+
await setManagedUpdateState({
|
|
443
|
+
...state,
|
|
444
|
+
state: 'failed',
|
|
445
|
+
code: 'update_transaction_lost',
|
|
446
|
+
message: 'The managed update transaction could not be recovered. Use the manual update command.',
|
|
447
|
+
phaseStartedAt: 0,
|
|
448
|
+
deadlineAt: 0,
|
|
449
|
+
heartbeatAt: 0
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function getManagedUpdateState() {
|
|
455
|
+
const stored = await chrome.storage.local.get(MANAGED_UPDATE_STATE_KEY);
|
|
456
|
+
return stored?.[MANAGED_UPDATE_STATE_KEY] || {
|
|
457
|
+
state: 'idle',
|
|
458
|
+
managed: true,
|
|
459
|
+
currentVersion: chrome.runtime.getManifest().version,
|
|
460
|
+
latestVersion: chrome.runtime.getManifest().version
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async function setManagedUpdateState(state) {
|
|
465
|
+
await chrome.storage.local.set({ [MANAGED_UPDATE_STATE_KEY]: state });
|
|
466
|
+
return state;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async function reloadManagedUpdateTabs() {
|
|
470
|
+
const stored = await chrome.storage.local.get(MANAGED_UPDATE_TABS_KEY);
|
|
471
|
+
const pendingIds = Array.isArray(stored?.[MANAGED_UPDATE_TABS_KEY])
|
|
472
|
+
? stored[MANAGED_UPDATE_TABS_KEY]
|
|
473
|
+
: [];
|
|
474
|
+
const openTabs = await chrome.tabs.query({ url: MANAGED_OVERLEAF_MATCHES }).catch(() => []);
|
|
475
|
+
const openIds = new Set(openTabs.map(tab => tab.id).filter(Number.isInteger));
|
|
476
|
+
const reloadIds = [...new Set(pendingIds.filter(tabId => Number.isInteger(tabId) && openIds.has(tabId)))];
|
|
477
|
+
await chrome.storage.local.remove(MANAGED_UPDATE_TABS_KEY);
|
|
478
|
+
await Promise.allSettled(reloadIds.map(tabId => chrome.tabs.reload(tabId)));
|
|
479
|
+
}
|
|
480
|
+
|
|
168
481
|
function getNativeCompatibilityBlock(request = {}) {
|
|
169
482
|
const evidence = getNativeCompatibilityEvidence(request);
|
|
170
483
|
if (!evidence) {
|
|
@@ -366,15 +679,20 @@ if (!globalThis.CodexOverleafCompatibility) {
|
|
|
366
679
|
}
|
|
367
680
|
const pendingRequest = pending.get(id);
|
|
368
681
|
if (message?.event) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
pendingRequest.eventForwarded
|
|
372
|
-
|
|
682
|
+
const { sequence, write } = appendRunJournalEvent(id, message.event);
|
|
683
|
+
Promise.resolve(write).catch(() => {}).then(() => {
|
|
684
|
+
pendingRequest.eventForwarded = (
|
|
685
|
+
forwardNativeEvent(pendingRequest.tabId, id, message.event, sequence) ||
|
|
686
|
+
pendingRequest.eventForwarded
|
|
687
|
+
);
|
|
688
|
+
});
|
|
373
689
|
return;
|
|
374
690
|
}
|
|
375
691
|
pendingRequest.finalResponseReceived = true;
|
|
376
692
|
pending.delete(id);
|
|
377
|
-
|
|
693
|
+
finalizeRunJournal(id, message).catch(() => {}).finally(() => {
|
|
694
|
+
pendingRequest.resolve(message);
|
|
695
|
+
});
|
|
378
696
|
});
|
|
379
697
|
nativePort.onDisconnect.addListener(() => {
|
|
380
698
|
if (nativePort !== port) {
|
|
@@ -432,6 +750,13 @@ if (!globalThis.CodexOverleafCompatibility) {
|
|
|
432
750
|
|
|
433
751
|
function rejectInterruptedNativeRequest(pendingId, pendingRequest, errorMessage) {
|
|
434
752
|
pending.delete(pendingId);
|
|
753
|
+
finalizeRunJournal(pendingId, {
|
|
754
|
+
ok: false,
|
|
755
|
+
error: {
|
|
756
|
+
code: 'native_execution_interrupted',
|
|
757
|
+
message: errorMessage || 'Native host disconnected'
|
|
758
|
+
}
|
|
759
|
+
}).catch(() => {});
|
|
435
760
|
if (pendingRequest.retryClass === 'no_silent_retry') {
|
|
436
761
|
pendingRequest.reject(createNativeRequestError(
|
|
437
762
|
'native_execution_interrupted',
|
|
@@ -522,7 +847,7 @@ if (!globalThis.CodexOverleafCompatibility) {
|
|
|
522
847
|
}
|
|
523
848
|
}
|
|
524
849
|
|
|
525
|
-
function forwardNativeEvent(tabId, id, event) {
|
|
850
|
+
function forwardNativeEvent(tabId, id, event, journalSeq = 0) {
|
|
526
851
|
if (typeof tabId !== 'number') {
|
|
527
852
|
return false;
|
|
528
853
|
}
|
|
@@ -530,12 +855,186 @@ if (!globalThis.CodexOverleafCompatibility) {
|
|
|
530
855
|
chrome.tabs.sendMessage(tabId, {
|
|
531
856
|
type: 'codex-overleaf/native-event',
|
|
532
857
|
id,
|
|
533
|
-
event
|
|
858
|
+
event,
|
|
859
|
+
journalSeq
|
|
534
860
|
}, () => {
|
|
535
861
|
void chrome.runtime.lastError;
|
|
536
862
|
});
|
|
537
863
|
return true;
|
|
538
864
|
}
|
|
865
|
+
|
|
866
|
+
function createRunJournal(id, pendingRequest) {
|
|
867
|
+
const params = pendingRequest.request?.params || {};
|
|
868
|
+
const journal = {
|
|
869
|
+
requestId: id,
|
|
870
|
+
projectKey: String(params.projectId || params.project?.projectId || ''),
|
|
871
|
+
clientRunId: String(params.clientRunId || ''),
|
|
872
|
+
sessionId: String(params.clientSessionId || ''),
|
|
873
|
+
task: sanitizeJournalString(params.task).slice(0, 12000),
|
|
874
|
+
mode: String(params.mode || ''),
|
|
875
|
+
model: String(params.model || ''),
|
|
876
|
+
createdAt: new Date().toISOString(),
|
|
877
|
+
updatedAt: new Date().toISOString(),
|
|
878
|
+
terminal: false,
|
|
879
|
+
ownerLost: false,
|
|
880
|
+
nextSequence: 1,
|
|
881
|
+
events: []
|
|
882
|
+
};
|
|
883
|
+
runJournals.set(id, journal);
|
|
884
|
+
pruneInMemoryRunJournals();
|
|
885
|
+
return queueJournalWrite(id);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function appendRunJournalEvent(id, event) {
|
|
889
|
+
const journal = runJournals.get(id);
|
|
890
|
+
if (!journal) {
|
|
891
|
+
return { sequence: 0, write: Promise.resolve() };
|
|
892
|
+
}
|
|
893
|
+
const sequence = journal.nextSequence++;
|
|
894
|
+
journal.events.push({ sequence, event: sanitizeJournalValue(event) });
|
|
895
|
+
journal.updatedAt = new Date().toISOString();
|
|
896
|
+
trimRunJournal(journal);
|
|
897
|
+
return { sequence, write: queueJournalWrite(id) };
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function finalizeRunJournal(id, response) {
|
|
901
|
+
const journal = runJournals.get(id);
|
|
902
|
+
if (!journal) {
|
|
903
|
+
return Promise.resolve();
|
|
904
|
+
}
|
|
905
|
+
journal.terminal = true;
|
|
906
|
+
journal.updatedAt = new Date().toISOString();
|
|
907
|
+
journal.final = {
|
|
908
|
+
ok: response?.ok === true,
|
|
909
|
+
code: String(response?.error?.code || ''),
|
|
910
|
+
status: String(response?.result?.status || '')
|
|
911
|
+
};
|
|
912
|
+
return queueJournalWrite(id);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function trimRunJournal(journal) {
|
|
916
|
+
if (journal.events.length > RUN_JOURNAL_MAX_EVENTS) {
|
|
917
|
+
journal.events.splice(0, journal.events.length - RUN_JOURNAL_MAX_EVENTS);
|
|
918
|
+
}
|
|
919
|
+
while (journal.events.length && JSON.stringify(journal).length * 2 > RUN_JOURNAL_MAX_BYTES) {
|
|
920
|
+
journal.events.shift();
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function sanitizeJournalValue(value, depth = 0) {
|
|
925
|
+
if (typeof value === 'string') {
|
|
926
|
+
return sanitizeJournalString(value);
|
|
927
|
+
}
|
|
928
|
+
if (value === null || value === undefined || typeof value === 'number' || typeof value === 'boolean') {
|
|
929
|
+
return value;
|
|
930
|
+
}
|
|
931
|
+
if (depth > 10) {
|
|
932
|
+
return '[nested value omitted]';
|
|
933
|
+
}
|
|
934
|
+
if (Array.isArray(value)) {
|
|
935
|
+
return value.slice(0, 200).map(item => sanitizeJournalValue(item, depth + 1));
|
|
936
|
+
}
|
|
937
|
+
if (typeof value === 'object') {
|
|
938
|
+
return Object.fromEntries(Object.entries(value).slice(0, 200).map(([key, item]) => [
|
|
939
|
+
sanitizeJournalString(key).slice(0, 160),
|
|
940
|
+
sanitizeJournalValue(item, depth + 1)
|
|
941
|
+
]));
|
|
942
|
+
}
|
|
943
|
+
return String(value);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function sanitizeJournalString(value) {
|
|
947
|
+
return String(value || '')
|
|
948
|
+
.replace(/\b(?:sk|hf|glpat|npm|ghp|github_pat|AKIA|AIza)[_-]?[A-Za-z0-9_-]{12,}\b/g, '[REDACTED]')
|
|
949
|
+
.replace(/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, '[REDACTED]')
|
|
950
|
+
.replace(/(?:file:\/\/)?\/(?:Users|home|private|var|opt|Library)\/[^\s"'<>]+/g, '[local path]')
|
|
951
|
+
.slice(0, 131072);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function queueJournalWrite(id) {
|
|
955
|
+
const storage = chrome.storage?.session;
|
|
956
|
+
if (!storage || !runJournals.has(id)) {
|
|
957
|
+
return Promise.resolve();
|
|
958
|
+
}
|
|
959
|
+
const previous = journalWrites.get(id) || Promise.resolve();
|
|
960
|
+
const snapshot = JSON.parse(JSON.stringify(runJournals.get(id)));
|
|
961
|
+
const next = previous.catch(() => {}).then(() => storage.set({
|
|
962
|
+
[RUN_JOURNAL_PREFIX + id]: snapshot
|
|
963
|
+
}));
|
|
964
|
+
journalWrites.set(id, next);
|
|
965
|
+
return next.finally(() => {
|
|
966
|
+
if (journalWrites.get(id) === next) {
|
|
967
|
+
journalWrites.delete(id);
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
async function listRunJournals(projectKey) {
|
|
973
|
+
await Promise.allSettled(Array.from(journalWrites.values()));
|
|
974
|
+
const storage = chrome.storage?.session;
|
|
975
|
+
if (!storage) {
|
|
976
|
+
return [];
|
|
977
|
+
}
|
|
978
|
+
const values = await storage.get(null);
|
|
979
|
+
const entries = Object.entries(values || {})
|
|
980
|
+
.filter(([key, value]) => key.startsWith(RUN_JOURNAL_PREFIX)
|
|
981
|
+
&& (!projectKey || value?.projectKey === projectKey)
|
|
982
|
+
&& (value?.terminal || value?.ownerLost))
|
|
983
|
+
.sort((a, b) => String(a[1]?.createdAt || '').localeCompare(String(b[1]?.createdAt || '')));
|
|
984
|
+
return entries.slice(-16).map(([_key, value]) => value);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function pruneInMemoryRunJournals() {
|
|
988
|
+
if (runJournals.size <= 16) {
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
const oldest = Array.from(runJournals.values())
|
|
992
|
+
.filter(journal => journal?.terminal || journal?.ownerLost)
|
|
993
|
+
.sort((a, b) => String(a.createdAt || '').localeCompare(String(b.createdAt || '')))
|
|
994
|
+
.slice(0, runJournals.size - 16);
|
|
995
|
+
for (const journal of oldest) {
|
|
996
|
+
runJournals.delete(journal.requestId);
|
|
997
|
+
journalWrites.delete(journal.requestId);
|
|
998
|
+
chrome.storage?.session?.remove?.(RUN_JOURNAL_PREFIX + journal.requestId).catch?.(() => {});
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
async function acknowledgeRunJournal(id) {
|
|
1003
|
+
if (!id) {
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
await (journalWrites.get(id) || Promise.resolve()).catch(() => {});
|
|
1007
|
+
runJournals.delete(id);
|
|
1008
|
+
journalWrites.delete(id);
|
|
1009
|
+
await chrome.storage?.session?.remove?.(RUN_JOURNAL_PREFIX + id);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
async function interruptOwnerlessRun(binding) {
|
|
1013
|
+
ownerCancelTimers.delete(binding.requestId);
|
|
1014
|
+
if (Array.from(ownerBindings.values()).some(value => value.requestId === binding.requestId)) {
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
const journal = runJournals.get(binding.requestId);
|
|
1018
|
+
if (journal) {
|
|
1019
|
+
journal.ownerLost = true;
|
|
1020
|
+
journal.updatedAt = new Date().toISOString();
|
|
1021
|
+
await queueJournalWrite(binding.requestId).catch(() => {});
|
|
1022
|
+
}
|
|
1023
|
+
if (!pending.has(binding.requestId)) {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
try {
|
|
1027
|
+
sendNativeCancel({
|
|
1028
|
+
method: 'codex.cancel',
|
|
1029
|
+
params: {
|
|
1030
|
+
requestId: binding.requestId,
|
|
1031
|
+
projectKey: binding.projectKey || undefined
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
} catch (_error) {
|
|
1035
|
+
// The original request disconnect path will settle the journal.
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
539
1038
|
})();
|
|
540
1039
|
|
|
541
1040
|
(function loadConsentDrivenUpdateRuntime(root) {
|