codex-overleaf-link 2.5.0 → 2.5.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 +38 -26
- package/README.zh-CN.md +39 -27
- package/extension/bootstrap/background.js +52 -9
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/bootstrap/popup.html +1 -0
- package/extension/bootstrap/popupFallback.js +55 -0
- package/extension/bootstrap/updateStatus.js +1 -0
- package/extension/src/background.js +206 -53
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +74 -14
- package/extension/src/backgroundUpdateCoordinator.js +118 -21
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +8 -2
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +93 -45
- package/extension/src/content/composerPanel.js +18 -0
- package/extension/src/content/contentRuntime.js +661 -676
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +30 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +6879 -2786
- package/extension/src/content/generated/content.bundle.meta.json +11 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +6 -0
- package/extension/src/content/localSkillsPanel.js +102 -12
- package/extension/src/content/markdownText.js +5 -6
- package/extension/src/content/modelPicker.js +7 -4
- package/extension/src/content/nativeCompatibilityController.js +45 -20
- package/extension/src/content/otWarmMirror.js +4 -5
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +15 -2
- package/extension/src/content/panelMaintenance.js +40 -12
- package/extension/src/content/panelRenderer.js +6 -0
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +39 -2
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +71 -13
- package/extension/src/content/recentProjects.js +11 -5
- package/extension/src/content/runActivitySummary.js +40 -6
- package/extension/src/content/runCompletionMeta.js +344 -0
- package/extension/src/content/runController.js +37 -14
- package/extension/src/content/runFailureNotice.js +21 -12
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runQueueScheduler.js +148 -42
- package/extension/src/content/runRecoveryActions.js +146 -0
- package/extension/src/content/runResultActions.js +8 -125
- package/extension/src/content/runReviewController.js +574 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +146 -153
- package/extension/src/content/scopedPersistencePanelState.js +51 -6
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +140 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +1 -1
- package/extension/src/content/settingsPanel.js +7 -3
- package/extension/src/content/settingsWorkbench.js +5 -0
- package/extension/src/content/sharedSessionViewSync.js +45 -8
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +60 -8
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +82 -73
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/ownedDirectoryCleanup.js +94 -0
- package/extension/src/page/referenceProjects.js +1 -0
- package/extension/src/page/reviewContext.js +113 -0
- package/extension/src/page/saveState.js +6 -4
- package/extension/src/page/snapshotRouter.js +84 -39
- package/extension/src/page/textFileCreator.js +105 -25
- package/extension/src/page/trackedChangeCapture.js +12 -2
- package/extension/src/page/trackedChangeOwnership.js +19 -1
- package/extension/src/page/trackedChangeReplay.js +41 -21
- package/extension/src/page/trackedChangesLifecycle.js +217 -199
- package/extension/src/page/treeOperations.js +7 -10
- package/extension/src/page/updateSafetyProbe.js +41 -0
- package/extension/src/page/writebackReceiptJournal.js +2 -0
- package/extension/src/page/writebackRouter.js +80 -79
- package/extension/src/pageBridge.js +19 -3
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +23 -0
- package/extension/src/shared/auditRecords.js +2 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +79 -57
- package/extension/src/shared/composerAttachmentDraft.js +151 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +309 -3
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +298 -2
- package/extension/src/shared/managedUpdateProjection.js +2 -2
- package/extension/src/shared/nativeRequestIdentity.js +1 -0
- package/extension/src/shared/pageRpcContract.js +5 -4
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +72 -27
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +37 -14
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +118 -13
- package/extension/src/shared/storageDb.js +58 -26
- package/extension/src/shared/storageMigration.js +71 -111
- package/extension/src/shared/storageRunActions.js +209 -26
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +136 -13
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +1 -1
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +38 -38
- package/extension/styles/panel.css +19 -5
- package/native-host/src/anthropicBridgeRequest.js +4 -2
- package/native-host/src/anthropicBridgeResponse.js +6 -2
- package/native-host/src/anthropicMessagesBridge.js +24 -16
- package/native-host/src/bootstrapMigration.js +296 -0
- package/native-host/src/bootstrapMigrationContract.js +101 -0
- package/native-host/src/bootstrapUpdate.js +233 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeResponse.js +10 -0
- package/native-host/src/chatCompletionsBridge.js +21 -15
- package/native-host/src/codexConfigPaths.js +106 -0
- package/native-host/src/codexHome.js +66 -73
- package/native-host/src/codexHomeScope.js +124 -0
- package/native-host/src/codexInheritedAuth.js +90 -0
- package/native-host/src/codexLocalSkills.js +301 -0
- package/native-host/src/codexPromptAssembly.js +31 -12
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +110 -183
- package/native-host/src/codexSessionTiming.js +18 -8
- package/native-host/src/codexSkillIsolation.js +170 -0
- package/native-host/src/codexThreadFork.js +19 -3
- package/native-host/src/index.js +19 -7
- package/native-host/src/managedInstall.js +110 -35
- package/native-host/src/managedInstallRecovery.js +140 -0
- package/native-host/src/managedRuntimeEvidence.js +57 -0
- package/native-host/src/mirrorWorkspace.js +92 -22
- package/native-host/src/nativeDoctor.js +15 -0
- package/native-host/src/nativeHelperLifecycle.js +21 -0
- package/native-host/src/nativeHostRegistration.js +33 -0
- package/native-host/src/nativeRunLifecycle.js +71 -0
- package/native-host/src/processStopBarrier.js +42 -7
- package/native-host/src/projectPathPlatform.js +30 -0
- package/native-host/src/providerHttp.js +80 -0
- package/native-host/src/providerProfile.js +4 -2
- package/native-host/src/providerReasoning.js +2 -7
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/providerStore.js +72 -14
- package/native-host/src/responsesPassthroughBridge.js +266 -52
- package/native-host/src/runtimeInstaller.js +21 -1
- package/native-host/src/selectionScope.js +36 -8
- package/native-host/src/subagentAdmission.js +26 -0
- package/native-host/src/subagentBroker.js +79 -66
- package/native-host/src/subagentTelemetry.js +19 -10
- package/native-host/src/subagentWorkspacePath.js +5 -2
- package/native-host/src/taskRunnerRuntime.js +77 -54
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +297 -39
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +1 -1
- package/scripts/install-managed.mjs +1 -0
|
@@ -26,6 +26,8 @@ if (!globalThis.CodexOverleafStorageRunActions) {
|
|
|
26
26
|
// registry and shared-module imports do not exist in this context.
|
|
27
27
|
const storageDependencies = [
|
|
28
28
|
['CodexOverleafModuleRegistry', 'content/moduleRegistryKernel.js'],
|
|
29
|
+
['CodexOverleafProjectFiles', 'shared/projectFiles.js'],
|
|
30
|
+
['CodexOverleafDirectoryCleanupProof', 'shared/directoryCleanupProof.js'],
|
|
29
31
|
['CodexOverleafUndoOperations', 'shared/undoOperations.js'],
|
|
30
32
|
['CodexOverleafSelectionContext', 'shared/selectionContext.js'],
|
|
31
33
|
['CodexOverleafWritebackIntent', 'shared/writebackIntent.js'],
|
|
@@ -38,6 +40,10 @@ if (!globalThis.CodexOverleafStorageRunActions) {
|
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
}
|
|
43
|
+
if (!globalThis.CodexOverleafSharedSessionTransfer) {
|
|
44
|
+
const runtimeBase = String(globalThis.__CODEX_OVERLEAF_RUNTIME_BASE__ || '');
|
|
45
|
+
importScripts(runtimeBase ? chrome.runtime.getURL(`${runtimeBase}/src/shared/sharedSessionTransfer.js`) : 'shared/sharedSessionTransfer.js');
|
|
46
|
+
}
|
|
41
47
|
if (!globalThis.CodexOverleafSharedSessionsBackground) {
|
|
42
48
|
const runtimeBase = String(globalThis.__CODEX_OVERLEAF_RUNTIME_BASE__ || '');
|
|
43
49
|
importScripts(runtimeBase ? chrome.runtime.getURL(`${runtimeBase}/src/backgroundSharedSessions.js`) : 'backgroundSharedSessions.js');
|
|
@@ -47,6 +53,14 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
47
53
|
const runtimeBase = String(globalThis.__CODEX_OVERLEAF_RUNTIME_BASE__ || '');
|
|
48
54
|
importScripts(runtimeBase ? chrome.runtime.getURL(`${runtimeBase}/src/shared/updateRuntimeIdentity.js`) : 'shared/updateRuntimeIdentity.js');
|
|
49
55
|
}
|
|
56
|
+
if (!globalThis.CodexOverleafBootstrapMigration) {
|
|
57
|
+
const runtimeBase = String(globalThis.__CODEX_OVERLEAF_RUNTIME_BASE__ || '');
|
|
58
|
+
importScripts(runtimeBase ? chrome.runtime.getURL(`${runtimeBase}/src/backgroundBootstrapMigration.js`) : 'backgroundBootstrapMigration.js');
|
|
59
|
+
}
|
|
60
|
+
if (!globalThis.CodexOverleafBackgroundUpdateSafety) {
|
|
61
|
+
const runtimeBase = String(globalThis.__CODEX_OVERLEAF_RUNTIME_BASE__ || '');
|
|
62
|
+
importScripts(runtimeBase ? chrome.runtime.getURL(`${runtimeBase}/src/backgroundUpdateSafety.js`) : 'backgroundUpdateSafety.js');
|
|
63
|
+
}
|
|
50
64
|
|
|
51
65
|
(function initBackground() {
|
|
52
66
|
'use strict';
|
|
@@ -83,7 +97,12 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
83
97
|
'codex.providers.delete',
|
|
84
98
|
'skills.list',
|
|
85
99
|
'skills.install',
|
|
86
|
-
'skills.remove'
|
|
100
|
+
'skills.remove',
|
|
101
|
+
'writing-style.get',
|
|
102
|
+
'writing-style.set',
|
|
103
|
+
'codex.thread.fork',
|
|
104
|
+
'asset.readChunk',
|
|
105
|
+
'asset.release'
|
|
87
106
|
]);
|
|
88
107
|
const RECOVERABLE_COMPATIBILITY_METHODS = new Set([
|
|
89
108
|
'bridge.ping',
|
|
@@ -99,12 +118,38 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
99
118
|
const pending = new Map();
|
|
100
119
|
const runJournals = new Map();
|
|
101
120
|
const journalWrites = new Map();
|
|
121
|
+
const journalLoads = new Map();
|
|
102
122
|
const ownerBindings = new Map();
|
|
103
123
|
const ownerCancelTimers = new Map();
|
|
104
124
|
const RUN_JOURNAL_PREFIX = 'codex-overleaf:run-journal:';
|
|
105
125
|
const RUN_JOURNAL_MAX_EVENTS = 300;
|
|
106
126
|
const RUN_JOURNAL_MAX_BYTES = 512 * 1024;
|
|
107
127
|
const managedBootstrapRuntime = isManagedBootstrapRuntime();
|
|
128
|
+
const migrationSafety = globalThis.CodexOverleafBackgroundUpdateSafety.create({
|
|
129
|
+
request: payload => sendNativeRequest(payload, null, { skipCompatibility: true }),
|
|
130
|
+
isExecutionLocked: () => managedBootstrapRuntime && managedUpdateExecutionLocked
|
|
131
|
+
});
|
|
132
|
+
let extensionInstallIdentity = { managed: managedBootstrapRuntime };
|
|
133
|
+
let installIdentityRead;
|
|
134
|
+
function readExtensionInstallIdentity() {
|
|
135
|
+
if (!managedBootstrapRuntime) return Promise.resolve(extensionInstallIdentity);
|
|
136
|
+
if (!installIdentityRead) installIdentityRead = (async () => {
|
|
137
|
+
try {
|
|
138
|
+
const response = await fetch(chrome.runtime.getURL('.codex-overleaf-managed-extension.json'));
|
|
139
|
+
const marker = response.ok ? await response.json() : null;
|
|
140
|
+
if (marker?.managedBy === 'codex-overleaf-link' && marker.kind === 'extension'
|
|
141
|
+
&& marker.installIdentity?.extensionId === getCurrentExtensionId())
|
|
142
|
+
extensionInstallIdentity = { ...marker.installIdentity, managed: true };
|
|
143
|
+
} catch (_) { /* Old or missing marker: never guess the installation root. */ }
|
|
144
|
+
return extensionInstallIdentity;
|
|
145
|
+
})();
|
|
146
|
+
return installIdentityRead;
|
|
147
|
+
}
|
|
148
|
+
async function withExtensionInstallIdentity(request, response) {
|
|
149
|
+
return request?.method === 'bridge.ping'
|
|
150
|
+
? { ...response, extensionInstallIdentity: await readExtensionInstallIdentity() } : response;
|
|
151
|
+
}
|
|
152
|
+
|
|
108
153
|
globalThis.CodexOverleafNativeBridge = Object.freeze({
|
|
109
154
|
requestInternal: payload => requestManagedInternal(payload),
|
|
110
155
|
getPendingState: () => ({
|
|
@@ -114,6 +159,14 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
114
159
|
});
|
|
115
160
|
|
|
116
161
|
if (managedBootstrapRuntime) {
|
|
162
|
+
globalThis.CodexOverleafBootstrapMigration.init({
|
|
163
|
+
request: payload => sendNativeRequest(payload, null, { skipCompatibility: true }),
|
|
164
|
+
verifySafety: verifyManagedUpdateSafety,
|
|
165
|
+
protectReloadTabs: transaction => migrationSafety.protectReloadTabs(transaction),
|
|
166
|
+
releaseReloadTabs: (transaction, state) => migrationSafety.releaseReloadTabs(transaction, state),
|
|
167
|
+
hasReplacedReloadDocument: transaction => migrationSafety.hasReplacedReloadDocument(transaction),
|
|
168
|
+
setExecutionLocked: value => { managedUpdateExecutionLocked = value; }
|
|
169
|
+
});
|
|
117
170
|
chrome.storage?.onChanged?.addListener((changes, area) => {
|
|
118
171
|
if (area !== 'local' || !changes[MANAGED_UPDATE_STATE_KEY]?.newValue) {
|
|
119
172
|
return;
|
|
@@ -193,13 +246,11 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
193
246
|
}
|
|
194
247
|
|
|
195
248
|
sendNativeRequest(message.payload, sender)
|
|
249
|
+
.then(result => withExtensionInstallIdentity(message.payload, result))
|
|
196
250
|
.then(result => sendResponse(result))
|
|
197
251
|
.catch(error => {
|
|
198
252
|
const userFacingError = toUserFacingNativeError(error);
|
|
199
|
-
|
|
200
|
-
ok: false,
|
|
201
|
-
error: userFacingError
|
|
202
|
-
});
|
|
253
|
+
withExtensionInstallIdentity(message.payload, { ok: false, error: userFacingError }).then(sendResponse);
|
|
203
254
|
});
|
|
204
255
|
|
|
205
256
|
return true;
|
|
@@ -221,13 +272,24 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
221
272
|
return;
|
|
222
273
|
}
|
|
223
274
|
if (message.type === 'bind') {
|
|
224
|
-
|
|
275
|
+
const binding = {
|
|
225
276
|
requestId,
|
|
226
277
|
projectKey: String(message.projectKey || ''),
|
|
227
|
-
documentId: String(portConnection.sender?.documentId || '')
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
278
|
+
documentId: String(portConnection.sender?.documentId || ''),
|
|
279
|
+
tabId: portConnection.sender?.tab?.id,
|
|
280
|
+
clientRunId: String(message.clientRunId || ''),
|
|
281
|
+
sessionId: String(message.sessionId || '')
|
|
282
|
+
};
|
|
283
|
+
ownerBindings.set(portConnection, binding);
|
|
284
|
+
restoreRunJournal(binding).then(journal => {
|
|
285
|
+
if (ownerBindings.get(portConnection) !== binding || !journal || runJournals.get(requestId) !== journal) return;
|
|
286
|
+
clearTimeout(ownerCancelTimers.get(requestId));
|
|
287
|
+
ownerCancelTimers.delete(requestId);
|
|
288
|
+
if (!journal.ownerLost) return;
|
|
289
|
+
journal.ownerLost = false;
|
|
290
|
+
journal.updatedAt = new Date().toISOString();
|
|
291
|
+
return queueJournalWrite(requestId);
|
|
292
|
+
}).catch(() => {});
|
|
231
293
|
}
|
|
232
294
|
});
|
|
233
295
|
portConnection.onDisconnect.addListener(() => {
|
|
@@ -306,6 +368,7 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
306
368
|
resolve,
|
|
307
369
|
reject,
|
|
308
370
|
tabId: sender?.tab?.id,
|
|
371
|
+
documentId: String(sender?.documentId || ''),
|
|
309
372
|
request,
|
|
310
373
|
method: request.method,
|
|
311
374
|
retryClass: getNativeRetryClass(request.method),
|
|
@@ -328,6 +391,10 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
328
391
|
}
|
|
329
392
|
|
|
330
393
|
async function requestManagedInternal(payload) {
|
|
394
|
+
if (managedBootstrapRuntime && ['update.status', 'update.confirm'].includes(payload?.method)) {
|
|
395
|
+
payload = { ...payload, params: { ...payload.params,
|
|
396
|
+
bootstrapRevision: String(globalThis.__CODEX_OVERLEAF_BOOTSTRAP_REVISION__ || '') } };
|
|
397
|
+
}
|
|
331
398
|
if (managedBootstrapRuntime && payload?.method === 'update.apply') {
|
|
332
399
|
const gate = await verifyManagedUpdateSafety();
|
|
333
400
|
if (!gate.ok) {
|
|
@@ -345,15 +412,25 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
345
412
|
throw error;
|
|
346
413
|
}
|
|
347
414
|
}
|
|
348
|
-
|
|
415
|
+
const response = await sendNativeRequest(payload, null, { skipCompatibility: true });
|
|
416
|
+
if (payload?.method === 'update.confirm') {
|
|
417
|
+
globalThis.CodexOverleafBootstrapMigration.observeConfirmation(payload.params, response);
|
|
418
|
+
}
|
|
419
|
+
return response;
|
|
349
420
|
}
|
|
350
421
|
|
|
351
|
-
async function verifyManagedUpdateSafety() {
|
|
352
|
-
const tabs = await chrome.tabs.query({ url: MANAGED_OVERLEAF_MATCHES }).catch(() =>
|
|
353
|
-
const editorTabs = tabs.filter(tab => (
|
|
422
|
+
async function verifyManagedUpdateSafety(migrationOwner) {
|
|
423
|
+
const tabs = await chrome.tabs.query({ url: MANAGED_OVERLEAF_MATCHES }).catch(() => null);
|
|
424
|
+
const editorTabs = (Array.isArray(tabs) ? tabs : []).filter(tab => (
|
|
354
425
|
isManagedEditorTab(tab) && !tab.discarded && tab.status !== 'unloaded'
|
|
355
426
|
));
|
|
356
|
-
const probes = await Promise.all(editorTabs.map(tab =>
|
|
427
|
+
const probes = await Promise.all(editorTabs.map(async tab => {
|
|
428
|
+
const result = await probeManagedUpdateTab(tab.id);
|
|
429
|
+
return migrationOwner && result?.blockers?.includes('tab_probe_unavailable')
|
|
430
|
+
? migrationSafety.probe(tab, migrationOwner) : result;
|
|
431
|
+
}));
|
|
432
|
+
// An unknown inventory cannot establish an idle safe point, even when Native is idle.
|
|
433
|
+
if (!Array.isArray(tabs)) probes.push({ idle: false, blockers: ['tab_inventory_unavailable'] });
|
|
357
434
|
const nativeGate = await sendNativeRequest({
|
|
358
435
|
id: crypto.randomUUID(),
|
|
359
436
|
method: 'update.canApply',
|
|
@@ -365,6 +442,12 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
365
442
|
message: getErrorMessage(error, 'Native Host update safety check failed.')
|
|
366
443
|
}
|
|
367
444
|
}));
|
|
445
|
+
if (migrationOwner) {
|
|
446
|
+
const after = await chrome.tabs.query({ url: MANAGED_OVERLEAF_MATCHES }).catch(() => null);
|
|
447
|
+
const inventory = values => JSON.stringify((values || []).map(tab => [tab.id, tab.url, tab.discarded, tab.status]).sort());
|
|
448
|
+
if (!after || inventory(after) !== inventory(tabs)) probes.push({ idle: false, blockers: ['tab_probe_unavailable'] });
|
|
449
|
+
else if (!await migrationSafety.revalidate(editorTabs, migrationOwner)) probes.push({ idle: false, blockers: ['tab_probe_unavailable'] });
|
|
450
|
+
}
|
|
368
451
|
const pendingState = globalThis.CodexOverleafNativeBridge?.getPendingState?.() || {};
|
|
369
452
|
const backgroundBlockers = Number(pendingState.executionRequests || 0) > 0
|
|
370
453
|
? ['background_execution_pending']
|
|
@@ -391,13 +474,15 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
391
474
|
return { idle: false, blockers: ['tab_unavailable'] };
|
|
392
475
|
}
|
|
393
476
|
try {
|
|
394
|
-
|
|
477
|
+
const response = await Promise.race([
|
|
395
478
|
chrome.tabs.sendMessage(tabId, { type: 'codex-overleaf/update-idle-probe' }),
|
|
396
479
|
new Promise(resolve => setTimeout(() => resolve({
|
|
397
480
|
idle: false,
|
|
398
481
|
blockers: ['tab_probe_timeout']
|
|
399
482
|
}), 3500))
|
|
400
483
|
]);
|
|
484
|
+
return typeof response?.idle === 'boolean' ? response
|
|
485
|
+
: { idle: false, blockers: ['tab_probe_unavailable'] };
|
|
401
486
|
} catch (_error) {
|
|
402
487
|
return { idle: false, blockers: ['tab_probe_unavailable'] };
|
|
403
488
|
}
|
|
@@ -434,6 +519,11 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
434
519
|
}
|
|
435
520
|
|
|
436
521
|
async function reconcileManagedUpdateTransaction() {
|
|
522
|
+
const migration = await globalThis.CodexOverleafBootstrapMigration.ready();
|
|
523
|
+
if (!migration.continueInitialization) return;
|
|
524
|
+
await globalThis.__CODEX_OVERLEAF_BOOTSTRAP_READY__;
|
|
525
|
+
const coordinator = globalThis.CodexOverleafUpdateCoordinator;
|
|
526
|
+
const recoveryOwner = await coordinator?.captureRecoveryOwner?.();
|
|
437
527
|
const response = await sendNativeRequest({
|
|
438
528
|
id: crypto.randomUUID(),
|
|
439
529
|
method: 'update.status',
|
|
@@ -445,22 +535,7 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
445
535
|
|
|
446
536
|
const transaction = response.result?.transaction || null;
|
|
447
537
|
if (transaction?.state === 'superseded') {
|
|
448
|
-
|
|
449
|
-
await chrome.storage.local.remove([
|
|
450
|
-
MANAGED_UPDATE_CONSENT_KEY,
|
|
451
|
-
MANAGED_UPDATE_TABS_KEY
|
|
452
|
-
]);
|
|
453
|
-
await setManagedUpdateState({
|
|
454
|
-
state: 'idle',
|
|
455
|
-
managed: true,
|
|
456
|
-
currentVersion: activeVersion,
|
|
457
|
-
latestVersion: activeVersion,
|
|
458
|
-
transactionId: '',
|
|
459
|
-
blocker: '',
|
|
460
|
-
blockers: [],
|
|
461
|
-
code: '',
|
|
462
|
-
message: ''
|
|
463
|
-
});
|
|
538
|
+
await coordinator?.settleSupersededTransaction?.(response.result, recoveryOwner);
|
|
464
539
|
return;
|
|
465
540
|
}
|
|
466
541
|
const state = await getManagedUpdateState();
|
|
@@ -521,6 +596,16 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
521
596
|
return null;
|
|
522
597
|
}
|
|
523
598
|
|
|
599
|
+
const params = request.params || {};
|
|
600
|
+
if (request.method === 'codex.run' && params.focusFilesAreLiteral === true
|
|
601
|
+
&& (params.restrictToFocusFiles === true || (Array.isArray(params.focusFiles) && params.focusFiles.length))
|
|
602
|
+
&& evidence?.native?.capabilities?.literalProjectPaths !== true) {
|
|
603
|
+
return { ...createNativeUpdateRequiredBlock(request.method, {
|
|
604
|
+
...evidence, status: 'native_capability_missing', classification: 'incompatible'
|
|
605
|
+
}), message: 'This Native Host does not support literal project paths. Reload the extension after installing the coordinated build.',
|
|
606
|
+
missingCapabilities: ['literalProjectPaths'] };
|
|
607
|
+
}
|
|
608
|
+
|
|
524
609
|
if (isNativeMethodAllowedByCompatibility(request.method, evidence)) {
|
|
525
610
|
return null;
|
|
526
611
|
}
|
|
@@ -621,12 +706,13 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
621
706
|
const command = CodexOverleafCompatibility?.buildInstallCommand?.(
|
|
622
707
|
getNativeRecommendedVersion(evidence),
|
|
623
708
|
evidence.native?.platform || evidence.platform,
|
|
624
|
-
getCurrentExtensionId()
|
|
709
|
+
getCurrentExtensionId(),
|
|
710
|
+
extensionInstallIdentity
|
|
625
711
|
);
|
|
626
712
|
if (command) {
|
|
627
713
|
return command;
|
|
628
714
|
}
|
|
629
|
-
return evidence.updateCommand || evidence.installCommand;
|
|
715
|
+
return managedBootstrapRuntime ? '' : evidence.updateCommand || evidence.installCommand;
|
|
630
716
|
}
|
|
631
717
|
|
|
632
718
|
function getCurrentExtensionId() {
|
|
@@ -911,6 +997,8 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
911
997
|
task: sanitizeJournalString(params.task).slice(0, 12000),
|
|
912
998
|
mode: String(params.mode || ''),
|
|
913
999
|
model: String(params.model || ''),
|
|
1000
|
+
ownerTabId: pendingRequest.tabId,
|
|
1001
|
+
ownerDocumentId: pendingRequest.documentId,
|
|
914
1002
|
createdAt: new Date().toISOString(),
|
|
915
1003
|
updatedAt: new Date().toISOString(),
|
|
916
1004
|
terminal: false,
|
|
@@ -918,6 +1006,7 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
918
1006
|
nextSequence: 1,
|
|
919
1007
|
events: []
|
|
920
1008
|
};
|
|
1009
|
+
invalidateJournalLoad(id);
|
|
921
1010
|
runJournals.set(id, journal);
|
|
922
1011
|
pruneInMemoryRunJournals();
|
|
923
1012
|
return queueJournalWrite(id);
|
|
@@ -982,9 +1071,9 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
982
1071
|
}
|
|
983
1072
|
|
|
984
1073
|
function sanitizeJournalString(value) {
|
|
1074
|
+
// Journal replay is conversation recovery. Credential configuration is not
|
|
1075
|
+
// copied by createRunJournal; quoted values in model events stay intact.
|
|
985
1076
|
return String(value || '')
|
|
986
|
-
.replace(/\b(?:sk|hf|glpat|npm|ghp|github_pat|AKIA|AIza)[_-]?[A-Za-z0-9_-]{12,}\b/g, '[REDACTED]')
|
|
987
|
-
.replace(/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, '[REDACTED]')
|
|
988
1077
|
.replace(/(?:file:\/\/)?\/(?:Users|home|private|var|opt|Library)\/[^\s"'<>]+/g, '[local path]')
|
|
989
1078
|
.slice(0, 131072);
|
|
990
1079
|
}
|
|
@@ -994,11 +1083,15 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
994
1083
|
if (!storage || !runJournals.has(id)) {
|
|
995
1084
|
return Promise.resolve();
|
|
996
1085
|
}
|
|
1086
|
+
const journal = runJournals.get(id);
|
|
1087
|
+
const snapshot = JSON.parse(JSON.stringify(journal));
|
|
1088
|
+
return queueJournalStorage(id, () => runJournals.get(id) === journal
|
|
1089
|
+
? storage.set({ [RUN_JOURNAL_PREFIX + id]: snapshot }) : undefined);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
function queueJournalStorage(id, operation) {
|
|
997
1093
|
const previous = journalWrites.get(id) || Promise.resolve();
|
|
998
|
-
const
|
|
999
|
-
const next = previous.catch(() => {}).then(() => storage.set({
|
|
1000
|
-
[RUN_JOURNAL_PREFIX + id]: snapshot
|
|
1001
|
-
}));
|
|
1094
|
+
const next = previous.catch(() => {}).then(operation);
|
|
1002
1095
|
journalWrites.set(id, next);
|
|
1003
1096
|
return next.finally(() => {
|
|
1004
1097
|
if (journalWrites.get(id) === next) {
|
|
@@ -1007,7 +1100,67 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
1007
1100
|
});
|
|
1008
1101
|
}
|
|
1009
1102
|
|
|
1103
|
+
function invalidateJournalLoad(id) {
|
|
1104
|
+
const load = journalLoads.get(id);
|
|
1105
|
+
if (load) load.cancelled = true;
|
|
1106
|
+
journalLoads.delete(id);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
function journalMatchesBinding(journal, binding) {
|
|
1110
|
+
return journal?.requestId === binding.requestId && Boolean(binding.projectKey)
|
|
1111
|
+
&& journal.projectKey === binding.projectKey
|
|
1112
|
+
&& (!Number.isInteger(journal.ownerTabId) || journal.ownerTabId === binding.tabId)
|
|
1113
|
+
&& (!journal.ownerDocumentId || journal.ownerDocumentId === binding.documentId)
|
|
1114
|
+
&& (!binding.clientRunId || journal.clientRunId === binding.clientRunId)
|
|
1115
|
+
&& (!binding.sessionId || journal.sessionId === binding.sessionId);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
function hasRunOwner(binding) {
|
|
1119
|
+
return Array.from(ownerBindings.values()).some(owner => owner.requestId === binding.requestId
|
|
1120
|
+
&& owner.projectKey === binding.projectKey && owner.tabId === binding.tabId
|
|
1121
|
+
&& owner.documentId === binding.documentId && owner.clientRunId === binding.clientRunId
|
|
1122
|
+
&& owner.sessionId === binding.sessionId);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
async function restoreRunJournal(binding) {
|
|
1126
|
+
const id = binding.requestId;
|
|
1127
|
+
if (runJournals.has(id)) {
|
|
1128
|
+
const journal = runJournals.get(id);
|
|
1129
|
+
return journalMatchesBinding(journal, binding) ? journal : null;
|
|
1130
|
+
}
|
|
1131
|
+
if (!chrome.storage?.session || !binding.projectKey) return null;
|
|
1132
|
+
let load = journalLoads.get(id);
|
|
1133
|
+
if (!load) {
|
|
1134
|
+
load = { cancelled: false };
|
|
1135
|
+
load.promise = (async () => {
|
|
1136
|
+
await (journalWrites.get(id) || Promise.resolve()).catch(() => {});
|
|
1137
|
+
if (load.cancelled) return null;
|
|
1138
|
+
const values = await chrome.storage.session.get(RUN_JOURNAL_PREFIX + id);
|
|
1139
|
+
const stored = values?.[RUN_JOURNAL_PREFIX + id];
|
|
1140
|
+
if (load.cancelled || !journalMatchesBinding(stored, binding) || !Array.isArray(stored.events)) return null;
|
|
1141
|
+
const journal = runJournals.get(id) || stored;
|
|
1142
|
+
trimRunJournal(journal);
|
|
1143
|
+
runJournals.set(id, journal);
|
|
1144
|
+
return journal;
|
|
1145
|
+
})().finally(() => {
|
|
1146
|
+
if (journalLoads.get(id) === load) journalLoads.delete(id);
|
|
1147
|
+
});
|
|
1148
|
+
journalLoads.set(id, load);
|
|
1149
|
+
}
|
|
1150
|
+
const journal = await load.promise;
|
|
1151
|
+
return journalMatchesBinding(journal, binding) ? journal : null;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function removeRunJournal(id) {
|
|
1155
|
+
invalidateJournalLoad(id);
|
|
1156
|
+
runJournals.delete(id);
|
|
1157
|
+
// Retain the chain until removal completes; old queued writes cannot follow it.
|
|
1158
|
+
return queueJournalStorage(id, () => !runJournals.has(id)
|
|
1159
|
+
? chrome.storage?.session?.remove?.(RUN_JOURNAL_PREFIX + id) : undefined);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1010
1162
|
async function listRunJournals(projectKey) {
|
|
1163
|
+
await Promise.allSettled(Array.from(journalLoads.values(), load => load.promise));
|
|
1011
1164
|
await Promise.allSettled(Array.from(journalWrites.values()));
|
|
1012
1165
|
const storage = chrome.storage?.session;
|
|
1013
1166
|
if (!storage) {
|
|
@@ -1017,7 +1170,8 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
1017
1170
|
const entries = Object.entries(values || {})
|
|
1018
1171
|
.filter(([key, value]) => key.startsWith(RUN_JOURNAL_PREFIX)
|
|
1019
1172
|
&& (!projectKey || value?.projectKey === projectKey)
|
|
1020
|
-
&& (value?.terminal || value?.ownerLost)
|
|
1173
|
+
&& (value?.terminal || value?.ownerLost)
|
|
1174
|
+
&& !Array.from(ownerBindings.values()).some(binding => journalMatchesBinding(value, binding)))
|
|
1021
1175
|
.sort((a, b) => String(a[1]?.createdAt || '').localeCompare(String(b[1]?.createdAt || '')));
|
|
1022
1176
|
return entries.slice(-16).map(([_key, value]) => value);
|
|
1023
1177
|
}
|
|
@@ -1031,9 +1185,7 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
1031
1185
|
.sort((a, b) => String(a.createdAt || '').localeCompare(String(b.createdAt || '')))
|
|
1032
1186
|
.slice(0, runJournals.size - 16);
|
|
1033
1187
|
for (const journal of oldest) {
|
|
1034
|
-
|
|
1035
|
-
journalWrites.delete(journal.requestId);
|
|
1036
|
-
chrome.storage?.session?.remove?.(RUN_JOURNAL_PREFIX + journal.requestId).catch?.(() => {});
|
|
1188
|
+
removeRunJournal(journal.requestId).catch(() => {});
|
|
1037
1189
|
}
|
|
1038
1190
|
}
|
|
1039
1191
|
|
|
@@ -1041,27 +1193,26 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
1041
1193
|
if (!id) {
|
|
1042
1194
|
return;
|
|
1043
1195
|
}
|
|
1044
|
-
await (
|
|
1045
|
-
runJournals.delete(id);
|
|
1046
|
-
journalWrites.delete(id);
|
|
1047
|
-
await chrome.storage?.session?.remove?.(RUN_JOURNAL_PREFIX + id);
|
|
1196
|
+
await removeRunJournal(id);
|
|
1048
1197
|
}
|
|
1049
1198
|
|
|
1050
1199
|
async function interruptOwnerlessRun(binding) {
|
|
1051
1200
|
ownerCancelTimers.delete(binding.requestId);
|
|
1052
|
-
if (
|
|
1201
|
+
if (hasRunOwner(binding)) {
|
|
1053
1202
|
return;
|
|
1054
1203
|
}
|
|
1055
|
-
const journal =
|
|
1204
|
+
const journal = await restoreRunJournal(binding).catch(() => null);
|
|
1205
|
+
if (hasRunOwner(binding)) return;
|
|
1206
|
+
if (journal && runJournals.get(binding.requestId) !== journal) return;
|
|
1056
1207
|
if (journal) {
|
|
1057
1208
|
journal.ownerLost = true;
|
|
1058
1209
|
journal.updatedAt = new Date().toISOString();
|
|
1059
1210
|
await queueJournalWrite(binding.requestId).catch(() => {});
|
|
1060
1211
|
}
|
|
1061
|
-
if (
|
|
1062
|
-
|
|
1063
|
-
}
|
|
1212
|
+
if (hasRunOwner(binding)) return;
|
|
1213
|
+
if (journal && runJournals.get(binding.requestId) !== journal) return;
|
|
1064
1214
|
try {
|
|
1215
|
+
if (!pending.has(binding.requestId) || !journalMatchesBinding(journal, binding)) return;
|
|
1065
1216
|
sendNativeCancel({
|
|
1066
1217
|
method: 'codex.cancel',
|
|
1067
1218
|
params: {
|
|
@@ -1071,6 +1222,8 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
|
|
|
1071
1222
|
});
|
|
1072
1223
|
} catch (_error) {
|
|
1073
1224
|
// The original request disconnect path will settle the journal.
|
|
1225
|
+
} finally {
|
|
1226
|
+
pruneInMemoryRunJournals();
|
|
1074
1227
|
}
|
|
1075
1228
|
}
|
|
1076
1229
|
})();
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
(function initBootstrapMigration(root) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const UPDATE_STATE_KEY = 'codex-overleaf-managed-update-state-v1';
|
|
5
|
+
const RETRY_ALARM = 'codex-overleaf-bootstrap-migration-retry';
|
|
6
|
+
const RETRY_MS = 4000;
|
|
7
|
+
const MAX_WAIT_MS = 2 * 60 * 1000;
|
|
8
|
+
let readyPromise;
|
|
9
|
+
let lockedOwner;
|
|
10
|
+
let setExecutionLocked;
|
|
11
|
+
|
|
12
|
+
function init(options) {
|
|
13
|
+
if (readyPromise) return readyPromise;
|
|
14
|
+
readyPromise = prepare(options);
|
|
15
|
+
return readyPromise;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function prepare(options) {
|
|
19
|
+
let transaction;
|
|
20
|
+
let reloadProtected = false;
|
|
21
|
+
let rollbackState = 'bootstrap_pending';
|
|
22
|
+
const revision = String(root.__CODEX_OVERLEAF_BOOTSTRAP_REVISION__ || '');
|
|
23
|
+
const request = async (method, params = {}) => {
|
|
24
|
+
const response = await options.request({ id: crypto.randomUUID(), method,
|
|
25
|
+
params: { ...params, bootstrapRevision: revision } });
|
|
26
|
+
if (!response?.ok) throw Object.assign(new Error(response?.error?.message || 'Managed update request failed.'),
|
|
27
|
+
{ code: response?.error?.code || 'update_health_failed' });
|
|
28
|
+
return response.result || {};
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
const status = await request('update.status');
|
|
32
|
+
transaction = status.transaction;
|
|
33
|
+
if (transaction?.state !== 'bootstrap_pending') return { continueInitialization: true };
|
|
34
|
+
options.setExecutionLocked(true);
|
|
35
|
+
lockedOwner = { id: transaction.id, targetVersion: transaction.targetVersion, revision };
|
|
36
|
+
setExecutionLocked = options.setExecutionLocked;
|
|
37
|
+
if (!transaction.id || transaction.targetVersion !== root.CodexOverleafCompatibility?.BUILD_TARGET_VERSION) {
|
|
38
|
+
throw codedError('The running update does not match the pending Bootstrap transaction.');
|
|
39
|
+
}
|
|
40
|
+
reloadProtected = await options.protectReloadTabs(transaction);
|
|
41
|
+
if (!reloadProtected) throw codedError('The previous page refresh list could not be protected.');
|
|
42
|
+
await writeState({ state: 'awaiting_health', transactionId: transaction.id,
|
|
43
|
+
latestVersion: transaction.targetVersion, code: '', message: '' });
|
|
44
|
+
|
|
45
|
+
// The first reload may leave old content contexts alive. Recheck the same
|
|
46
|
+
// idle/save contract before replacing their loader and reloading again.
|
|
47
|
+
const deadline = Date.now() + MAX_WAIT_MS;
|
|
48
|
+
while (true) {
|
|
49
|
+
const gate = await options.verifySafety({ transactionId: transaction.id, targetVersion: transaction.targetVersion,
|
|
50
|
+
sourceVersion: transaction.sourceVersion, expectedState: 'bootstrap_pending' });
|
|
51
|
+
if (gate?.ok) break;
|
|
52
|
+
if (Date.now() >= deadline) throw codedError('The Bootstrap update did not reach a safe point.');
|
|
53
|
+
await writeState({ blockers: gate?.error?.blockers || ['busy'] });
|
|
54
|
+
await waitForRetry();
|
|
55
|
+
const refreshed = await request('update.status');
|
|
56
|
+
if (refreshed.transaction?.id !== transaction.id || refreshed.transaction?.state !== 'bootstrap_pending') {
|
|
57
|
+
throw Object.assign(codedError('The pending Bootstrap transaction changed before activation.'), { restoredStatus: refreshed });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const result = await request('update.bootstrap', { transactionId: transaction.id });
|
|
62
|
+
if (result.transactionId !== transaction.id || result.targetVersion !== transaction.targetVersion) {
|
|
63
|
+
throw codedError('The Bootstrap activation result does not match the pending update.');
|
|
64
|
+
}
|
|
65
|
+
await writeState({ blockers: [], blocker: '' });
|
|
66
|
+
if (result.state === 'reload_required') {
|
|
67
|
+
// Only one reload per worker. Native owns the persistent attempt limit
|
|
68
|
+
// so a broken target cannot create a loop across worker restarts.
|
|
69
|
+
chrome.runtime.reload();
|
|
70
|
+
return { continueInitialization: false, state: result.state };
|
|
71
|
+
}
|
|
72
|
+
if (result.state !== 'awaiting_health' || revision !== '3.1' || result.bootstrapRevision !== revision) {
|
|
73
|
+
throw codedError('The updated Bootstrap did not provide its loaded revision.');
|
|
74
|
+
}
|
|
75
|
+
rollbackState = 'awaiting_health';
|
|
76
|
+
if (!await options.releaseReloadTabs(transaction, 'awaiting_health')) throw codedError('The original page identities could not be confirmed.');
|
|
77
|
+
return { continueInitialization: true, state: result.state };
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (!transaction || transaction.state !== 'bootstrap_pending') return { continueInitialization: true };
|
|
80
|
+
if (!reloadProtected) {
|
|
81
|
+
await writeState({ state: 'failed', recoveryPending: true, code: 'update_recovery_pending' }).catch(() => {});
|
|
82
|
+
return { continueInitialization: false, state: 'failed' };
|
|
83
|
+
}
|
|
84
|
+
const code = /^[a-z0-9_]{1,80}$/.test(error?.code || '') ? error.code : 'update_health_failed';
|
|
85
|
+
await writeState({ state: 'rolling_back', code }).catch(() => {});
|
|
86
|
+
let restored = false;
|
|
87
|
+
try {
|
|
88
|
+
let status = error.restoredStatus;
|
|
89
|
+
let rollback = status && { transactionId: status.transaction?.id, state: status.transaction?.state,
|
|
90
|
+
version: status.activeVersion, restoredPair: status.restoredPair };
|
|
91
|
+
if (!hasRestoredSource(transaction, rollback, status)) {
|
|
92
|
+
rollback = await request('update.rollback', { transactionId: transaction.id,
|
|
93
|
+
expectedState: rollbackState, reasonCode: code });
|
|
94
|
+
status = await request('update.status');
|
|
95
|
+
}
|
|
96
|
+
if (!hasRestoredSource(transaction, rollback, status)) throw codedError('Bootstrap rollback is unconfirmed.');
|
|
97
|
+
await writeState({ state: 'rolled_back', currentVersion: transaction.sourceVersion,
|
|
98
|
+
transactionId: transaction.id, recoveryPending: false, blockers: [], blocker: '', code });
|
|
99
|
+
restored = true;
|
|
100
|
+
while (true) {
|
|
101
|
+
try {
|
|
102
|
+
// A new document must never inherit the old document's refresh
|
|
103
|
+
// authorization. The source worker sees an empty legacy list;
|
|
104
|
+
// the guarded evidence remains for a later natural/user refresh.
|
|
105
|
+
if (await options.hasReplacedReloadDocument(transaction)) break;
|
|
106
|
+
const gate = await options.verifySafety({ transactionId: transaction.id, targetVersion: transaction.targetVersion,
|
|
107
|
+
sourceVersion: transaction.sourceVersion, expectedState: 'rolled_back' });
|
|
108
|
+
if (gate?.ok && await options.releaseReloadTabs(transaction, 'rolled_back')) break;
|
|
109
|
+
} catch (_) { /* Restored files stay restored while a page probe retries. */ }
|
|
110
|
+
await waitForRetry();
|
|
111
|
+
}
|
|
112
|
+
chrome.runtime.reload();
|
|
113
|
+
return { continueInitialization: false, state: 'rolled_back' };
|
|
114
|
+
} catch (_rollbackError) {
|
|
115
|
+
if (restored) return { continueInitialization: false, state: 'rolled_back' };
|
|
116
|
+
await writeState({ state: 'failed', recoveryPending: true, transactionId: transaction.id,
|
|
117
|
+
code: 'update_recovery_pending', message: 'The installed update result has not been confirmed.' }).catch(() => {});
|
|
118
|
+
return { continueInitialization: false, state: 'failed' };
|
|
119
|
+
}
|
|
120
|
+
} finally {
|
|
121
|
+
await chrome.alarms?.clear?.(RETRY_ALARM).catch?.(() => {});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function writeState(patch) {
|
|
126
|
+
const stored = await chrome.storage.local.get(UPDATE_STATE_KEY);
|
|
127
|
+
const state = root.CodexOverleafManagedUpdateProjection.transition(stored[UPDATE_STATE_KEY] || {}, patch,
|
|
128
|
+
{ currentVersion: chrome.runtime.getManifest().version });
|
|
129
|
+
await chrome.storage.local.set({ [UPDATE_STATE_KEY]: state });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function waitForRetry() {
|
|
133
|
+
return new Promise(resolve => {
|
|
134
|
+
let settled = false;
|
|
135
|
+
let timer;
|
|
136
|
+
const finish = () => {
|
|
137
|
+
if (settled) return;
|
|
138
|
+
settled = true;
|
|
139
|
+
clearTimeout(timer);
|
|
140
|
+
chrome.alarms?.onAlarm?.removeListener?.(onAlarm);
|
|
141
|
+
resolve();
|
|
142
|
+
};
|
|
143
|
+
const onAlarm = alarm => { if (alarm?.name === RETRY_ALARM) finish(); };
|
|
144
|
+
chrome.alarms?.onAlarm?.addListener?.(onAlarm);
|
|
145
|
+
chrome.alarms?.create?.(RETRY_ALARM, { delayInMinutes: 0.5 });
|
|
146
|
+
timer = setTimeout(finish, RETRY_MS);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function hasRestoredSource(transaction, rollback, status) {
|
|
151
|
+
const pair = status?.restoredPair;
|
|
152
|
+
return rollback?.transactionId === transaction.id && rollback.state === 'rolled_back'
|
|
153
|
+
&& rollback.version === transaction.sourceVersion && status?.transaction?.id === transaction.id
|
|
154
|
+
&& status.transaction.state === 'rolled_back' && status.activeVersion === transaction.sourceVersion
|
|
155
|
+
&& status.installedAligned === true && pair?.extensionVersion === transaction.sourceVersion
|
|
156
|
+
&& pair?.nativeVersion === transaction.sourceVersion
|
|
157
|
+
&& /^[0-9a-f]{64}$/.test(pair.extensionSha256 || '') && /^[0-9a-f]{64}$/.test(pair.nativeSha256 || '')
|
|
158
|
+
&& /^[0-9a-f]{64}$/.test(pair.bootstrapSha256 || '')
|
|
159
|
+
&& ['extensionVersion', 'nativeVersion', 'extensionSha256', 'nativeSha256', 'bootstrapSha256']
|
|
160
|
+
.every(key => pair[key] === rollback.restoredPair?.[key]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function codedError(message) {
|
|
164
|
+
return Object.assign(new Error(message), { code: 'update_health_failed' });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function observeConfirmation(params, response) {
|
|
168
|
+
if (!lockedOwner || !response?.ok || response.result?.state !== 'committed'
|
|
169
|
+
|| response.result.version !== lockedOwner.targetVersion || params?.transactionId !== lockedOwner.id
|
|
170
|
+
|| params.extensionVersion !== lockedOwner.targetVersion || params.nativeVersion !== lockedOwner.targetVersion
|
|
171
|
+
|| params.bootstrapRevision !== lockedOwner.revision || lockedOwner.revision !== '3.1') return;
|
|
172
|
+
lockedOwner = null;
|
|
173
|
+
setExecutionLocked(false);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
root.CodexOverleafBootstrapMigration = Object.freeze({
|
|
177
|
+
init,
|
|
178
|
+
observeConfirmation,
|
|
179
|
+
ready: () => readyPromise || Promise.resolve({ continueInitialization: true })
|
|
180
|
+
});
|
|
181
|
+
})(globalThis);
|