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.
Files changed (168) hide show
  1. package/README.md +38 -26
  2. package/README.zh-CN.md +39 -27
  3. package/extension/bootstrap/background.js +52 -9
  4. package/extension/bootstrap/manifest.template.json +1 -1
  5. package/extension/bootstrap/popup.html +1 -0
  6. package/extension/bootstrap/popupFallback.js +55 -0
  7. package/extension/bootstrap/updateStatus.js +1 -0
  8. package/extension/src/background.js +206 -53
  9. package/extension/src/backgroundBootstrapMigration.js +181 -0
  10. package/extension/src/backgroundSharedSessions.js +74 -14
  11. package/extension/src/backgroundUpdateCoordinator.js +118 -21
  12. package/extension/src/backgroundUpdateSafety.js +173 -0
  13. package/extension/src/content/activeTurnControl.js +8 -2
  14. package/extension/src/content/applyResultFormatters.js +46 -73
  15. package/extension/src/content/assetTransferBroker.js +72 -14
  16. package/extension/src/content/composerAttachments.js +93 -45
  17. package/extension/src/content/composerPanel.js +18 -0
  18. package/extension/src/content/contentRuntime.js +661 -676
  19. package/extension/src/content/diagnosticsController.js +122 -96
  20. package/extension/src/content/diagnosticsExport.js +19 -5
  21. package/extension/src/content/diagnosticsPanel.js +30 -0
  22. package/extension/src/content/directoryCleanupRecovery.js +54 -0
  23. package/extension/src/content/generated/content.bundle.js +6879 -2786
  24. package/extension/src/content/generated/content.bundle.meta.json +11 -3
  25. package/extension/src/content/globalPreferencesController.js +1 -1
  26. package/extension/src/content/legacyGlobalRegistry.js +6 -0
  27. package/extension/src/content/localSkillsPanel.js +102 -12
  28. package/extension/src/content/markdownText.js +5 -6
  29. package/extension/src/content/modelPicker.js +7 -4
  30. package/extension/src/content/nativeCompatibilityController.js +45 -20
  31. package/extension/src/content/otWarmMirror.js +4 -5
  32. package/extension/src/content/otWarmMirrorController.js +17 -20
  33. package/extension/src/content/pageBridgeClient.js +15 -2
  34. package/extension/src/content/panelMaintenance.js +40 -12
  35. package/extension/src/content/panelRenderer.js +6 -0
  36. package/extension/src/content/projectProviderSelection.js +13 -4
  37. package/extension/src/content/projectSessionCleanup.js +109 -62
  38. package/extension/src/content/projectSettingsCoordinator.js +39 -2
  39. package/extension/src/content/providerSettingsCoordinator.js +46 -8
  40. package/extension/src/content/providerSettingsDialog.js +71 -13
  41. package/extension/src/content/recentProjects.js +11 -5
  42. package/extension/src/content/runActivitySummary.js +40 -6
  43. package/extension/src/content/runCompletionMeta.js +344 -0
  44. package/extension/src/content/runController.js +37 -14
  45. package/extension/src/content/runFailureNotice.js +21 -12
  46. package/extension/src/content/runGuidanceController.js +72 -27
  47. package/extension/src/content/runGuidanceView.js +59 -10
  48. package/extension/src/content/runQueueScheduler.js +148 -42
  49. package/extension/src/content/runRecoveryActions.js +146 -0
  50. package/extension/src/content/runResultActions.js +8 -125
  51. package/extension/src/content/runReviewController.js +574 -0
  52. package/extension/src/content/runScrollLayout.js +21 -2
  53. package/extension/src/content/runTimelineView.js +146 -153
  54. package/extension/src/content/scopedPersistencePanelState.js +51 -6
  55. package/extension/src/content/selectionContextView.js +1 -1
  56. package/extension/src/content/sensitivePreflight.js +140 -0
  57. package/extension/src/content/sessionForkController.js +80 -17
  58. package/extension/src/content/sessionManager.js +1 -1
  59. package/extension/src/content/settingsPanel.js +7 -3
  60. package/extension/src/content/settingsWorkbench.js +5 -0
  61. package/extension/src/content/sharedSessionViewSync.js +45 -8
  62. package/extension/src/content/updateIdle.js +4 -1
  63. package/extension/src/content/updateNotice.js +60 -8
  64. package/extension/src/content/updateSafetyReceiver.js +58 -0
  65. package/extension/src/content/writebackController.js +53 -4
  66. package/extension/src/content/writebackOrchestrator.js +82 -73
  67. package/extension/src/content/writingStyleSettings.js +35 -11
  68. package/extension/src/page/binaryAssetUploader.js +136 -14
  69. package/extension/src/page/fileMover.js +256 -0
  70. package/extension/src/page/ownedDirectoryCleanup.js +94 -0
  71. package/extension/src/page/referenceProjects.js +1 -0
  72. package/extension/src/page/reviewContext.js +113 -0
  73. package/extension/src/page/saveState.js +6 -4
  74. package/extension/src/page/snapshotRouter.js +84 -39
  75. package/extension/src/page/textFileCreator.js +105 -25
  76. package/extension/src/page/trackedChangeCapture.js +12 -2
  77. package/extension/src/page/trackedChangeOwnership.js +19 -1
  78. package/extension/src/page/trackedChangeReplay.js +41 -21
  79. package/extension/src/page/trackedChangesLifecycle.js +217 -199
  80. package/extension/src/page/treeOperations.js +7 -10
  81. package/extension/src/page/updateSafetyProbe.js +41 -0
  82. package/extension/src/page/writebackReceiptJournal.js +2 -0
  83. package/extension/src/page/writebackRouter.js +80 -79
  84. package/extension/src/pageBridge.js +19 -3
  85. package/extension/src/popup.js +62 -10
  86. package/extension/src/shared/agentTranscript.js +23 -0
  87. package/extension/src/shared/auditRecords.js +2 -0
  88. package/extension/src/shared/compatibility.js +22 -6
  89. package/extension/src/shared/compileAdapter.js +79 -57
  90. package/extension/src/shared/composerAttachmentDraft.js +151 -0
  91. package/extension/src/shared/directoryCleanupProof.js +43 -0
  92. package/extension/src/shared/failureReasons.js +309 -3
  93. package/extension/src/shared/globalPreferences.js +30 -13
  94. package/extension/src/shared/governanceRules.js +4 -37
  95. package/extension/src/shared/i18n.js +298 -2
  96. package/extension/src/shared/managedUpdateProjection.js +2 -2
  97. package/extension/src/shared/nativeRequestIdentity.js +1 -0
  98. package/extension/src/shared/pageRpcContract.js +5 -4
  99. package/extension/src/shared/projectFiles.js +21 -44
  100. package/extension/src/shared/providerProfiles.js +5 -3
  101. package/extension/src/shared/runInputQueue.js +48 -5
  102. package/extension/src/shared/selectionContext.js +12 -3
  103. package/extension/src/shared/sessionState.js +72 -27
  104. package/extension/src/shared/settlementFacts.js +10 -2
  105. package/extension/src/shared/sharedSessionBridge.js +37 -14
  106. package/extension/src/shared/sharedSessionTransfer.js +140 -0
  107. package/extension/src/shared/staleGuard.js +118 -13
  108. package/extension/src/shared/storageDb.js +58 -26
  109. package/extension/src/shared/storageMigration.js +71 -111
  110. package/extension/src/shared/storageRunActions.js +209 -26
  111. package/extension/src/shared/subagentActivityModel.js +1 -1
  112. package/extension/src/shared/undoOperations.js +136 -13
  113. package/extension/src/shared/updateConsent.js +6 -4
  114. package/extension/src/shared/updateRuntimeIdentity.js +1 -1
  115. package/extension/src/shared/writebackIntent.js +16 -0
  116. package/extension/src/shared/writebackOperationEvidence.js +4 -15
  117. package/extension/src/shared/writebackSettlement.js +38 -38
  118. package/extension/styles/panel.css +19 -5
  119. package/native-host/src/anthropicBridgeRequest.js +4 -2
  120. package/native-host/src/anthropicBridgeResponse.js +6 -2
  121. package/native-host/src/anthropicMessagesBridge.js +24 -16
  122. package/native-host/src/bootstrapMigration.js +296 -0
  123. package/native-host/src/bootstrapMigrationContract.js +101 -0
  124. package/native-host/src/bootstrapUpdate.js +233 -0
  125. package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
  126. package/native-host/src/chatBridgeResponse.js +10 -0
  127. package/native-host/src/chatCompletionsBridge.js +21 -15
  128. package/native-host/src/codexConfigPaths.js +106 -0
  129. package/native-host/src/codexHome.js +66 -73
  130. package/native-host/src/codexHomeScope.js +124 -0
  131. package/native-host/src/codexInheritedAuth.js +90 -0
  132. package/native-host/src/codexLocalSkills.js +301 -0
  133. package/native-host/src/codexPromptAssembly.js +31 -12
  134. package/native-host/src/codexProviderTest.js +24 -26
  135. package/native-host/src/codexSessionRunner.js +110 -183
  136. package/native-host/src/codexSessionTiming.js +18 -8
  137. package/native-host/src/codexSkillIsolation.js +170 -0
  138. package/native-host/src/codexThreadFork.js +19 -3
  139. package/native-host/src/index.js +19 -7
  140. package/native-host/src/managedInstall.js +110 -35
  141. package/native-host/src/managedInstallRecovery.js +140 -0
  142. package/native-host/src/managedRuntimeEvidence.js +57 -0
  143. package/native-host/src/mirrorWorkspace.js +92 -22
  144. package/native-host/src/nativeDoctor.js +15 -0
  145. package/native-host/src/nativeHelperLifecycle.js +21 -0
  146. package/native-host/src/nativeHostRegistration.js +33 -0
  147. package/native-host/src/nativeRunLifecycle.js +71 -0
  148. package/native-host/src/processStopBarrier.js +42 -7
  149. package/native-host/src/projectPathPlatform.js +30 -0
  150. package/native-host/src/providerHttp.js +80 -0
  151. package/native-host/src/providerProfile.js +4 -2
  152. package/native-host/src/providerReasoning.js +2 -7
  153. package/native-host/src/providerRuntime.js +1 -0
  154. package/native-host/src/providerStore.js +72 -14
  155. package/native-host/src/responsesPassthroughBridge.js +266 -52
  156. package/native-host/src/runtimeInstaller.js +21 -1
  157. package/native-host/src/selectionScope.js +36 -8
  158. package/native-host/src/subagentAdmission.js +26 -0
  159. package/native-host/src/subagentBroker.js +79 -66
  160. package/native-host/src/subagentTelemetry.js +19 -10
  161. package/native-host/src/subagentWorkspacePath.js +5 -2
  162. package/native-host/src/taskRunnerRuntime.js +77 -54
  163. package/native-host/src/tomlStatements.js +140 -0
  164. package/native-host/src/updateManager.js +297 -39
  165. package/native-host/src/writingStyleRuntime.js +57 -29
  166. package/native-host/src/writingStyleStore.js +73 -0
  167. package/package.json +1 -1
  168. 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
- sendResponse({
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
- ownerBindings.set(portConnection, {
275
+ const binding = {
225
276
  requestId,
226
277
  projectKey: String(message.projectKey || ''),
227
- documentId: String(portConnection.sender?.documentId || '')
228
- });
229
- clearTimeout(ownerCancelTimers.get(requestId));
230
- ownerCancelTimers.delete(requestId);
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
- return sendNativeRequest(payload, null, { skipCompatibility: true });
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 => probeManagedUpdateTab(tab.id)));
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
- return await Promise.race([
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
- const activeVersion = response.result?.activeVersion || chrome.runtime.getManifest().version;
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 snapshot = JSON.parse(JSON.stringify(runJournals.get(id)));
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
- runJournals.delete(journal.requestId);
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 (journalWrites.get(id) || Promise.resolve()).catch(() => {});
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 (Array.from(ownerBindings.values()).some(value => value.requestId === binding.requestId)) {
1201
+ if (hasRunOwner(binding)) {
1053
1202
  return;
1054
1203
  }
1055
- const journal = runJournals.get(binding.requestId);
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 (!pending.has(binding.requestId)) {
1062
- return;
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);