codex-overleaf-link 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +155 -392
  2. package/README.zh-CN.md +154 -393
  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 +283 -55
  9. package/extension/src/backgroundBootstrapMigration.js +181 -0
  10. package/extension/src/backgroundSharedSessions.js +129 -16
  11. package/extension/src/backgroundUpdateCoordinator.js +242 -29
  12. package/extension/src/backgroundUpdateSafety.js +173 -0
  13. package/extension/src/content/activeTurnControl.js +171 -8
  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 +104 -47
  17. package/extension/src/content/composerPanel.js +110 -12
  18. package/extension/src/content/contentRuntime.js +1555 -926
  19. package/extension/src/content/contextTray.js +8 -2
  20. package/extension/src/content/contextUsageView.js +267 -0
  21. package/extension/src/content/diagnosticsController.js +122 -96
  22. package/extension/src/content/diagnosticsExport.js +19 -5
  23. package/extension/src/content/diagnosticsPanel.js +48 -0
  24. package/extension/src/content/directoryCleanupRecovery.js +54 -0
  25. package/extension/src/content/generated/content.bundle.js +11077 -3412
  26. package/extension/src/content/generated/content.bundle.meta.json +14 -3
  27. package/extension/src/content/globalPreferencesController.js +1 -1
  28. package/extension/src/content/legacyGlobalRegistry.js +10 -0
  29. package/extension/src/content/localSkillsPanel.js +104 -14
  30. package/extension/src/content/markdownDomRenderer.js +5 -3
  31. package/extension/src/content/markdownText.js +47 -11
  32. package/extension/src/content/modelPicker.js +8 -4
  33. package/extension/src/content/nativeCompatibilityController.js +74 -30
  34. package/extension/src/content/otWarmMirror.js +5 -6
  35. package/extension/src/content/otWarmMirrorController.js +17 -20
  36. package/extension/src/content/pageBridgeClient.js +17 -4
  37. package/extension/src/content/panelMaintenance.js +46 -14
  38. package/extension/src/content/panelRenderer.js +57 -27
  39. package/extension/src/content/pendingInputView.js +1 -1
  40. package/extension/src/content/projectProviderSelection.js +13 -4
  41. package/extension/src/content/projectSessionCleanup.js +109 -62
  42. package/extension/src/content/projectSettingsCoordinator.js +67 -6
  43. package/extension/src/content/providerSettingsCoordinator.js +46 -8
  44. package/extension/src/content/providerSettingsDialog.js +73 -14
  45. package/extension/src/content/recentProjects.js +15 -8
  46. package/extension/src/content/runActivitySummary.js +160 -18
  47. package/extension/src/content/runCompletionMeta.js +532 -0
  48. package/extension/src/content/runController.js +39 -15
  49. package/extension/src/content/runFailureNotice.js +29 -13
  50. package/extension/src/content/runGuidanceController.js +72 -27
  51. package/extension/src/content/runGuidanceView.js +59 -10
  52. package/extension/src/content/runPresence.js +18 -8
  53. package/extension/src/content/runQueueScheduler.js +157 -47
  54. package/extension/src/content/runRecoveryActions.js +147 -0
  55. package/extension/src/content/runResultActions.js +28 -143
  56. package/extension/src/content/runReviewController.js +633 -0
  57. package/extension/src/content/runScrollLayout.js +21 -2
  58. package/extension/src/content/runTimelineView.js +367 -175
  59. package/extension/src/content/scopedPersistencePanelState.js +134 -20
  60. package/extension/src/content/selectionContextView.js +1 -1
  61. package/extension/src/content/sensitivePreflight.js +153 -0
  62. package/extension/src/content/sessionForkController.js +80 -17
  63. package/extension/src/content/sessionManager.js +50 -4
  64. package/extension/src/content/sessionMenuView.js +54 -18
  65. package/extension/src/content/sessionPersistence.js +25 -5
  66. package/extension/src/content/settingsPanel.js +22 -5
  67. package/extension/src/content/settingsWorkbench.js +132 -89
  68. package/extension/src/content/sharedSessionViewSync.js +49 -11
  69. package/extension/src/content/subagentActivityView.js +23 -7
  70. package/extension/src/content/updateIdle.js +4 -1
  71. package/extension/src/content/updateNotice.js +118 -12
  72. package/extension/src/content/updateSafetyReceiver.js +58 -0
  73. package/extension/src/content/writebackController.js +53 -4
  74. package/extension/src/content/writebackOrchestrator.js +116 -94
  75. package/extension/src/content/writingStyleSettings.js +35 -11
  76. package/extension/src/page/binaryAssetUploader.js +136 -14
  77. package/extension/src/page/compileBridge.js +193 -180
  78. package/extension/src/page/fileMover.js +256 -0
  79. package/extension/src/page/overleafCapabilities.js +4 -1
  80. package/extension/src/page/ownedDirectoryCleanup.js +94 -0
  81. package/extension/src/page/referenceProjects.js +1 -0
  82. package/extension/src/page/reviewContext.js +113 -0
  83. package/extension/src/page/saveState.js +6 -4
  84. package/extension/src/page/snapshotRouter.js +84 -39
  85. package/extension/src/page/textCoordinates.js +17 -1
  86. package/extension/src/page/textFileCreator.js +114 -26
  87. package/extension/src/page/trackedChangeCapture.js +12 -2
  88. package/extension/src/page/trackedChangeOwnership.js +19 -1
  89. package/extension/src/page/trackedChangeReplay.js +41 -21
  90. package/extension/src/page/trackedChangesLifecycle.js +217 -199
  91. package/extension/src/page/treeOperations.js +7 -10
  92. package/extension/src/page/updateSafetyProbe.js +41 -0
  93. package/extension/src/page/writebackReceiptJournal.js +2 -0
  94. package/extension/src/page/writebackRouter.js +103 -155
  95. package/extension/src/pageBridge.js +126 -69
  96. package/extension/src/popup.js +62 -10
  97. package/extension/src/shared/agentTranscript.js +373 -17
  98. package/extension/src/shared/auditRecords.js +4 -0
  99. package/extension/src/shared/compatibility.js +22 -6
  100. package/extension/src/shared/compileAdapter.js +129 -66
  101. package/extension/src/shared/composerAttachmentDraft.js +156 -0
  102. package/extension/src/shared/contextUsage.js +125 -0
  103. package/extension/src/shared/directoryCleanupProof.js +43 -0
  104. package/extension/src/shared/failureReasons.js +349 -4
  105. package/extension/src/shared/globalPreferences.js +30 -13
  106. package/extension/src/shared/governanceRules.js +4 -37
  107. package/extension/src/shared/i18n.js +590 -24
  108. package/extension/src/shared/lineReferences.js +38 -31
  109. package/extension/src/shared/managedUpdateProjection.js +2 -2
  110. package/extension/src/shared/nativeRequestIdentity.js +1 -0
  111. package/extension/src/shared/pageRpcContract.js +5 -4
  112. package/extension/src/shared/panelIcons.js +39 -0
  113. package/extension/src/shared/projectFiles.js +21 -44
  114. package/extension/src/shared/providerProfiles.js +5 -3
  115. package/extension/src/shared/reviewing.js +130 -1
  116. package/extension/src/shared/runActivityModel.js +70 -4
  117. package/extension/src/shared/runExecutionSnapshot.js +13 -0
  118. package/extension/src/shared/runInputQueue.js +48 -5
  119. package/extension/src/shared/selectionContext.js +12 -3
  120. package/extension/src/shared/sessionState.js +132 -28
  121. package/extension/src/shared/sessionTitle.js +14 -1
  122. package/extension/src/shared/settlementFacts.js +10 -2
  123. package/extension/src/shared/sharedSessionBridge.js +118 -27
  124. package/extension/src/shared/sharedSessionTransfer.js +140 -0
  125. package/extension/src/shared/staleGuard.js +166 -52
  126. package/extension/src/shared/storageDb.js +90 -26
  127. package/extension/src/shared/storageMigration.js +78 -112
  128. package/extension/src/shared/storageRunActions.js +267 -30
  129. package/extension/src/shared/subagentActivityModel.js +1 -1
  130. package/extension/src/shared/undoOperations.js +253 -32
  131. package/extension/src/shared/updateConsent.js +6 -4
  132. package/extension/src/shared/updateRuntimeIdentity.js +17 -3
  133. package/extension/src/shared/writebackIntent.js +16 -0
  134. package/extension/src/shared/writebackOperationEvidence.js +4 -15
  135. package/extension/src/shared/writebackSettlement.js +58 -40
  136. package/extension/styles/panel.css +1123 -493
  137. package/native-host/src/anthropicBridgeRequest.js +4 -2
  138. package/native-host/src/anthropicBridgeResponse.js +6 -2
  139. package/native-host/src/anthropicMessagesBridge.js +24 -16
  140. package/native-host/src/bootstrapMigration.js +296 -0
  141. package/native-host/src/bootstrapMigrationContract.js +101 -0
  142. package/native-host/src/bootstrapUpdate.js +269 -0
  143. package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
  144. package/native-host/src/chatBridgeRequest.js +60 -6
  145. package/native-host/src/chatBridgeResponse.js +20 -1
  146. package/native-host/src/chatCompletionsBridge.js +48 -15
  147. package/native-host/src/codexConfigPaths.js +106 -0
  148. package/native-host/src/codexHome.js +66 -73
  149. package/native-host/src/codexHomeScope.js +124 -0
  150. package/native-host/src/codexInheritedAuth.js +90 -0
  151. package/native-host/src/codexLocalSkills.js +301 -0
  152. package/native-host/src/codexPromptAssembly.js +114 -127
  153. package/native-host/src/codexProviderTest.js +24 -26
  154. package/native-host/src/codexSessionRunner.js +234 -198
  155. package/native-host/src/codexSessionTiming.js +18 -8
  156. package/native-host/src/codexSkillIsolation.js +170 -0
  157. package/native-host/src/codexThreadFork.js +19 -3
  158. package/native-host/src/index.js +19 -7
  159. package/native-host/src/managedInstall.js +110 -35
  160. package/native-host/src/managedInstallRecovery.js +140 -0
  161. package/native-host/src/managedRuntimeEvidence.js +57 -0
  162. package/native-host/src/mirrorWorkspace.js +92 -22
  163. package/native-host/src/nativeDoctor.js +15 -0
  164. package/native-host/src/nativeHelperLifecycle.js +21 -0
  165. package/native-host/src/nativeHostRegistration.js +33 -0
  166. package/native-host/src/nativeRunLifecycle.js +71 -0
  167. package/native-host/src/processStopBarrier.js +42 -7
  168. package/native-host/src/projectPathPlatform.js +30 -0
  169. package/native-host/src/providerContextError.js +20 -0
  170. package/native-host/src/providerHttp.js +80 -0
  171. package/native-host/src/providerProfile.js +4 -2
  172. package/native-host/src/providerReasoning.js +2 -7
  173. package/native-host/src/providerRuntime.js +1 -0
  174. package/native-host/src/providerStore.js +72 -14
  175. package/native-host/src/responsesPassthroughBridge.js +266 -52
  176. package/native-host/src/runtimeInstaller.js +21 -1
  177. package/native-host/src/selectionScope.js +36 -8
  178. package/native-host/src/subagentAdmission.js +26 -0
  179. package/native-host/src/subagentBroker.js +79 -66
  180. package/native-host/src/subagentTelemetry.js +19 -10
  181. package/native-host/src/subagentWorkspacePath.js +5 -2
  182. package/native-host/src/taskRunnerRuntime.js +92 -57
  183. package/native-host/src/textPatch.js +473 -377
  184. package/native-host/src/tomlStatements.js +140 -0
  185. package/native-host/src/updateManager.js +409 -46
  186. package/native-host/src/writingStyleRuntime.js +57 -29
  187. package/native-host/src/writingStyleStore.js +73 -0
  188. package/package.json +5 -8
  189. 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;
@@ -136,6 +189,17 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
136
189
  .catch(error => sendResponse({ ok: false, journals: [], error: getErrorMessage(error, 'Journal read failed.') }));
137
190
  return true;
138
191
  }
192
+ if (message?.type === 'codex-overleaf/run-journal/known') {
193
+ if (!isAllowedOverleafSender(sender)) {
194
+ sendResponse({ ok: false, known: [] });
195
+ return undefined;
196
+ }
197
+ knownRunJournals(Array.isArray(message.requestIds) ? message.requestIds.map(String).slice(0, 64) : [],
198
+ Array.isArray(message.owners) ? message.owners.slice(0, 64) : [])
199
+ .then(result => sendResponse({ ok: true, ...result }))
200
+ .catch(error => sendResponse({ ok: false, known: [], error: getErrorMessage(error, 'Journal read failed.') }));
201
+ return true;
202
+ }
139
203
  if (message?.type === 'codex-overleaf/run-journal/ack') {
140
204
  if (!isAllowedOverleafSender(sender)) {
141
205
  sendResponse({ ok: false });
@@ -193,13 +257,11 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
193
257
  }
194
258
 
195
259
  sendNativeRequest(message.payload, sender)
260
+ .then(result => withExtensionInstallIdentity(message.payload, result))
196
261
  .then(result => sendResponse(result))
197
262
  .catch(error => {
198
263
  const userFacingError = toUserFacingNativeError(error);
199
- sendResponse({
200
- ok: false,
201
- error: userFacingError
202
- });
264
+ withExtensionInstallIdentity(message.payload, { ok: false, error: userFacingError }).then(sendResponse);
203
265
  });
204
266
 
205
267
  return true;
@@ -215,25 +277,50 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
215
277
  return;
216
278
  }
217
279
  if (message.type === 'release') {
280
+ const current = ownerBindings.get(portConnection);
281
+ if (!current || ['requestId', 'projectKey', 'clientRunId', 'sessionId'].some(key => current[key] !== message[key])
282
+ || (current.browserOnly && current.ownerToken !== message.ownerToken)) return;
218
283
  ownerBindings.delete(portConnection);
219
284
  clearTimeout(ownerCancelTimers.get(requestId));
220
285
  ownerCancelTimers.delete(requestId);
221
286
  return;
222
287
  }
223
288
  if (message.type === 'bind') {
224
- ownerBindings.set(portConnection, {
289
+ const binding = {
225
290
  requestId,
226
291
  projectKey: String(message.projectKey || ''),
227
- documentId: String(portConnection.sender?.documentId || '')
228
- });
229
- clearTimeout(ownerCancelTimers.get(requestId));
230
- ownerCancelTimers.delete(requestId);
292
+ documentId: String(portConnection.sender?.documentId || ''),
293
+ tabId: portConnection.sender?.tab?.id,
294
+ clientRunId: String(message.clientRunId || ''),
295
+ sessionId: String(message.sessionId || ''),
296
+ browserOnly: message.browserOnly === true,
297
+ ownerToken: String(message.ownerToken || '')
298
+ };
299
+ if (binding.browserOnly && (!binding.projectKey || !binding.clientRunId || !binding.sessionId || !binding.ownerToken)) return;
300
+ if (binding.browserOnly && managedUpdateExecutionLocked) {
301
+ try { portConnection.postMessage?.({ type: 'bound', ...binding, ok: false }); } catch (_error) { /* port disconnected */ }
302
+ return;
303
+ }
304
+ ownerBindings.set(portConnection, binding);
305
+ if (binding.browserOnly) {
306
+ try { portConnection.postMessage?.({ type: 'bound', ...binding }); } catch (_error) { /* port disconnected */ }
307
+ return;
308
+ }
309
+ restoreRunJournal(binding).then(journal => {
310
+ if (ownerBindings.get(portConnection) !== binding || !journal || runJournals.get(requestId) !== journal) return;
311
+ clearTimeout(ownerCancelTimers.get(requestId));
312
+ ownerCancelTimers.delete(requestId);
313
+ if (!journal.ownerLost) return;
314
+ journal.ownerLost = false;
315
+ journal.updatedAt = new Date().toISOString();
316
+ return queueJournalWrite(requestId);
317
+ }).catch(() => {});
231
318
  }
232
319
  });
233
320
  portConnection.onDisconnect.addListener(() => {
234
321
  const binding = ownerBindings.get(portConnection);
235
322
  ownerBindings.delete(portConnection);
236
- if (!binding?.requestId) {
323
+ if (!binding?.requestId || binding.browserOnly) {
237
324
  return;
238
325
  }
239
326
  const timer = setTimeout(() => interruptOwnerlessRun(binding), 150);
@@ -306,6 +393,7 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
306
393
  resolve,
307
394
  reject,
308
395
  tabId: sender?.tab?.id,
396
+ documentId: String(sender?.documentId || ''),
309
397
  request,
310
398
  method: request.method,
311
399
  retryClass: getNativeRetryClass(request.method),
@@ -328,7 +416,12 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
328
416
  }
329
417
 
330
418
  async function requestManagedInternal(payload) {
331
- if (managedBootstrapRuntime && payload?.method === 'update.apply') {
419
+ if (managedBootstrapRuntime && ['update.status', 'update.confirm'].includes(payload?.method)) {
420
+ payload = { ...payload, params: { ...payload.params,
421
+ bootstrapRevision: String(globalThis.__CODEX_OVERLEAF_BOOTSTRAP_REVISION__ || '') } };
422
+ }
423
+ if (managedBootstrapRuntime && (payload?.method === 'update.apply'
424
+ || (payload?.method === 'update.rollback' && payload?.params?.reasonCode === 'update_user_rollback'))) {
332
425
  const gate = await verifyManagedUpdateSafety();
333
426
  if (!gate.ok) {
334
427
  return gate;
@@ -345,15 +438,25 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
345
438
  throw error;
346
439
  }
347
440
  }
348
- return sendNativeRequest(payload, null, { skipCompatibility: true });
441
+ const response = await sendNativeRequest(payload, null, { skipCompatibility: true });
442
+ if (payload?.method === 'update.confirm') {
443
+ globalThis.CodexOverleafBootstrapMigration.observeConfirmation(payload.params, response);
444
+ }
445
+ return response;
349
446
  }
350
447
 
351
- async function verifyManagedUpdateSafety() {
352
- const tabs = await chrome.tabs.query({ url: MANAGED_OVERLEAF_MATCHES }).catch(() => []);
353
- const editorTabs = tabs.filter(tab => (
448
+ async function verifyManagedUpdateSafety(migrationOwner) {
449
+ const tabs = await chrome.tabs.query({ url: MANAGED_OVERLEAF_MATCHES }).catch(() => null);
450
+ const editorTabs = (Array.isArray(tabs) ? tabs : []).filter(tab => (
354
451
  isManagedEditorTab(tab) && !tab.discarded && tab.status !== 'unloaded'
355
452
  ));
356
- const probes = await Promise.all(editorTabs.map(tab => probeManagedUpdateTab(tab.id)));
453
+ const probes = await Promise.all(editorTabs.map(async tab => {
454
+ const result = await probeManagedUpdateTab(tab.id);
455
+ return migrationOwner && result?.blockers?.includes('tab_probe_unavailable')
456
+ ? migrationSafety.probe(tab, migrationOwner) : result;
457
+ }));
458
+ // An unknown inventory cannot establish an idle safe point, even when Native is idle.
459
+ if (!Array.isArray(tabs)) probes.push({ idle: false, blockers: ['tab_inventory_unavailable'] });
357
460
  const nativeGate = await sendNativeRequest({
358
461
  id: crypto.randomUUID(),
359
462
  method: 'update.canApply',
@@ -365,8 +468,15 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
365
468
  message: getErrorMessage(error, 'Native Host update safety check failed.')
366
469
  }
367
470
  }));
471
+ if (migrationOwner) {
472
+ const after = await chrome.tabs.query({ url: MANAGED_OVERLEAF_MATCHES }).catch(() => null);
473
+ const inventory = values => JSON.stringify((values || []).map(tab => [tab.id, tab.url, tab.discarded, tab.status]).sort());
474
+ if (!after || inventory(after) !== inventory(tabs)) probes.push({ idle: false, blockers: ['tab_probe_unavailable'] });
475
+ else if (!await migrationSafety.revalidate(editorTabs, migrationOwner)) probes.push({ idle: false, blockers: ['tab_probe_unavailable'] });
476
+ }
368
477
  const pendingState = globalThis.CodexOverleafNativeBridge?.getPendingState?.() || {};
369
478
  const backgroundBlockers = Number(pendingState.executionRequests || 0) > 0
479
+ || Array.from(ownerBindings.values()).some(binding => binding.browserOnly)
370
480
  ? ['background_execution_pending']
371
481
  : [];
372
482
  const blockers = [
@@ -391,13 +501,15 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
391
501
  return { idle: false, blockers: ['tab_unavailable'] };
392
502
  }
393
503
  try {
394
- return await Promise.race([
504
+ const response = await Promise.race([
395
505
  chrome.tabs.sendMessage(tabId, { type: 'codex-overleaf/update-idle-probe' }),
396
506
  new Promise(resolve => setTimeout(() => resolve({
397
507
  idle: false,
398
508
  blockers: ['tab_probe_timeout']
399
509
  }), 3500))
400
510
  ]);
511
+ return typeof response?.idle === 'boolean' ? response
512
+ : { idle: false, blockers: ['tab_probe_unavailable'] };
401
513
  } catch (_error) {
402
514
  return { idle: false, blockers: ['tab_probe_unavailable'] };
403
515
  }
@@ -434,6 +546,11 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
434
546
  }
435
547
 
436
548
  async function reconcileManagedUpdateTransaction() {
549
+ const migration = await globalThis.CodexOverleafBootstrapMigration.ready();
550
+ if (!migration.continueInitialization) return;
551
+ await globalThis.__CODEX_OVERLEAF_BOOTSTRAP_READY__;
552
+ const coordinator = globalThis.CodexOverleafUpdateCoordinator;
553
+ const recoveryOwner = await coordinator?.captureRecoveryOwner?.();
437
554
  const response = await sendNativeRequest({
438
555
  id: crypto.randomUUID(),
439
556
  method: 'update.status',
@@ -445,22 +562,7 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
445
562
 
446
563
  const transaction = response.result?.transaction || null;
447
564
  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
- });
565
+ await coordinator?.settleSupersededTransaction?.(response.result, recoveryOwner);
464
566
  return;
465
567
  }
466
568
  const state = await getManagedUpdateState();
@@ -521,6 +623,16 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
521
623
  return null;
522
624
  }
523
625
 
626
+ const params = request.params || {};
627
+ if (request.method === 'codex.run' && params.focusFilesAreLiteral === true
628
+ && (params.restrictToFocusFiles === true || (Array.isArray(params.focusFiles) && params.focusFiles.length))
629
+ && evidence?.native?.capabilities?.literalProjectPaths !== true) {
630
+ return { ...createNativeUpdateRequiredBlock(request.method, {
631
+ ...evidence, status: 'native_capability_missing', classification: 'incompatible'
632
+ }), message: 'This Native Host does not support literal project paths. Reload the extension after installing the coordinated build.',
633
+ missingCapabilities: ['literalProjectPaths'] };
634
+ }
635
+
524
636
  if (isNativeMethodAllowedByCompatibility(request.method, evidence)) {
525
637
  return null;
526
638
  }
@@ -621,12 +733,13 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
621
733
  const command = CodexOverleafCompatibility?.buildInstallCommand?.(
622
734
  getNativeRecommendedVersion(evidence),
623
735
  evidence.native?.platform || evidence.platform,
624
- getCurrentExtensionId()
736
+ getCurrentExtensionId(),
737
+ extensionInstallIdentity
625
738
  );
626
739
  if (command) {
627
740
  return command;
628
741
  }
629
- return evidence.updateCommand || evidence.installCommand;
742
+ return managedBootstrapRuntime ? '' : evidence.updateCommand || evidence.installCommand;
630
743
  }
631
744
 
632
745
  function getCurrentExtensionId() {
@@ -909,8 +1022,13 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
909
1022
  clientRunId: String(params.clientRunId || ''),
910
1023
  sessionId: String(params.clientSessionId || ''),
911
1024
  task: sanitizeJournalString(params.task).slice(0, 12000),
1025
+ ...(params.operation === 'compact' ? {
1026
+ operation: 'compact', threadId: String(params.threadId || '').slice(0, 160)
1027
+ } : {}),
912
1028
  mode: String(params.mode || ''),
913
1029
  model: String(params.model || ''),
1030
+ ownerTabId: pendingRequest.tabId,
1031
+ ownerDocumentId: pendingRequest.documentId,
914
1032
  createdAt: new Date().toISOString(),
915
1033
  updatedAt: new Date().toISOString(),
916
1034
  terminal: false,
@@ -918,6 +1036,7 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
918
1036
  nextSequence: 1,
919
1037
  events: []
920
1038
  };
1039
+ invalidateJournalLoad(id);
921
1040
  runJournals.set(id, journal);
922
1041
  pruneInMemoryRunJournals();
923
1042
  return queueJournalWrite(id);
@@ -982,9 +1101,9 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
982
1101
  }
983
1102
 
984
1103
  function sanitizeJournalString(value) {
1104
+ // Journal replay is conversation recovery. Credential configuration is not
1105
+ // copied by createRunJournal; quoted values in model events stay intact.
985
1106
  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
1107
  .replace(/(?:file:\/\/)?\/(?:Users|home|private|var|opt|Library)\/[^\s"'<>]+/g, '[local path]')
989
1108
  .slice(0, 131072);
990
1109
  }
@@ -994,11 +1113,15 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
994
1113
  if (!storage || !runJournals.has(id)) {
995
1114
  return Promise.resolve();
996
1115
  }
1116
+ const journal = runJournals.get(id);
1117
+ const snapshot = JSON.parse(JSON.stringify(journal));
1118
+ return queueJournalStorage(id, () => runJournals.get(id) === journal
1119
+ ? storage.set({ [RUN_JOURNAL_PREFIX + id]: snapshot }) : undefined);
1120
+ }
1121
+
1122
+ function queueJournalStorage(id, operation) {
997
1123
  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
- }));
1124
+ const next = previous.catch(() => {}).then(operation);
1002
1125
  journalWrites.set(id, next);
1003
1126
  return next.finally(() => {
1004
1127
  if (journalWrites.get(id) === next) {
@@ -1007,7 +1130,67 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
1007
1130
  });
1008
1131
  }
1009
1132
 
1133
+ function invalidateJournalLoad(id) {
1134
+ const load = journalLoads.get(id);
1135
+ if (load) load.cancelled = true;
1136
+ journalLoads.delete(id);
1137
+ }
1138
+
1139
+ function journalMatchesBinding(journal, binding) {
1140
+ return journal?.requestId === binding.requestId && Boolean(binding.projectKey)
1141
+ && journal.projectKey === binding.projectKey
1142
+ && (!Number.isInteger(journal.ownerTabId) || journal.ownerTabId === binding.tabId)
1143
+ && (!journal.ownerDocumentId || journal.ownerDocumentId === binding.documentId)
1144
+ && (!binding.clientRunId || journal.clientRunId === binding.clientRunId)
1145
+ && (!binding.sessionId || journal.sessionId === binding.sessionId);
1146
+ }
1147
+
1148
+ function hasRunOwner(binding) {
1149
+ return Array.from(ownerBindings.values()).some(owner => owner.requestId === binding.requestId
1150
+ && owner.projectKey === binding.projectKey && owner.tabId === binding.tabId
1151
+ && owner.documentId === binding.documentId && owner.clientRunId === binding.clientRunId
1152
+ && owner.sessionId === binding.sessionId);
1153
+ }
1154
+
1155
+ async function restoreRunJournal(binding) {
1156
+ const id = binding.requestId;
1157
+ if (runJournals.has(id)) {
1158
+ const journal = runJournals.get(id);
1159
+ return journalMatchesBinding(journal, binding) ? journal : null;
1160
+ }
1161
+ if (!chrome.storage?.session || !binding.projectKey) return null;
1162
+ let load = journalLoads.get(id);
1163
+ if (!load) {
1164
+ load = { cancelled: false };
1165
+ load.promise = (async () => {
1166
+ await (journalWrites.get(id) || Promise.resolve()).catch(() => {});
1167
+ if (load.cancelled) return null;
1168
+ const values = await chrome.storage.session.get(RUN_JOURNAL_PREFIX + id);
1169
+ const stored = values?.[RUN_JOURNAL_PREFIX + id];
1170
+ if (load.cancelled || !journalMatchesBinding(stored, binding) || !Array.isArray(stored.events)) return null;
1171
+ const journal = runJournals.get(id) || stored;
1172
+ trimRunJournal(journal);
1173
+ runJournals.set(id, journal);
1174
+ return journal;
1175
+ })().finally(() => {
1176
+ if (journalLoads.get(id) === load) journalLoads.delete(id);
1177
+ });
1178
+ journalLoads.set(id, load);
1179
+ }
1180
+ const journal = await load.promise;
1181
+ return journalMatchesBinding(journal, binding) ? journal : null;
1182
+ }
1183
+
1184
+ function removeRunJournal(id) {
1185
+ invalidateJournalLoad(id);
1186
+ runJournals.delete(id);
1187
+ // Retain the chain until removal completes; old queued writes cannot follow it.
1188
+ return queueJournalStorage(id, () => !runJournals.has(id)
1189
+ ? chrome.storage?.session?.remove?.(RUN_JOURNAL_PREFIX + id) : undefined);
1190
+ }
1191
+
1010
1192
  async function listRunJournals(projectKey) {
1193
+ await Promise.allSettled(Array.from(journalLoads.values(), load => load.promise));
1011
1194
  await Promise.allSettled(Array.from(journalWrites.values()));
1012
1195
  const storage = chrome.storage?.session;
1013
1196
  if (!storage) {
@@ -1017,11 +1200,57 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
1017
1200
  const entries = Object.entries(values || {})
1018
1201
  .filter(([key, value]) => key.startsWith(RUN_JOURNAL_PREFIX)
1019
1202
  && (!projectKey || value?.projectKey === projectKey)
1020
- && (value?.terminal || value?.ownerLost))
1203
+ && (value?.terminal || value?.ownerLost)
1204
+ && !Array.from(ownerBindings.values()).some(binding => journalMatchesBinding(value, binding)))
1021
1205
  .sort((a, b) => String(a[1]?.createdAt || '').localeCompare(String(b[1]?.createdAt || '')));
1022
1206
  return entries.slice(-16).map(([_key, value]) => value);
1023
1207
  }
1024
1208
 
1209
+ // Browser-only retries outlive their original Native journal. Protect only
1210
+ // a live port whose request/project/session/run matches the queried record.
1211
+ async function knownRunJournals(requestIds, owners = []) {
1212
+ await Promise.allSettled(Array.from(journalLoads.values(), load => load.promise));
1213
+ await Promise.allSettled(Array.from(journalWrites.values()));
1214
+ const storage = chrome.storage?.session;
1215
+ if (!storage) throw new Error('Run journal storage is unavailable.');
1216
+ const values = requestIds.length ? await storage.get(requestIds.map(id => RUN_JOURNAL_PREFIX + id)) : {};
1217
+ // A persisted startup snapshot may lag behind its owner's Native dispatch.
1218
+ // Keep that same project/session/run alive through the request-id transition.
1219
+ const matches = (candidate, binding) => (candidate?.preDispatch === true
1220
+ ? ['projectKey', 'clientRunId', 'sessionId'] : ['requestId', 'projectKey', 'clientRunId', 'sessionId'])
1221
+ .every(key => Boolean(candidate?.[key]) && candidate[key] === binding?.[key]);
1222
+ const known = requestIds.filter(id => runJournals.has(id) || Boolean(values?.[RUN_JOURNAL_PREFIX + id])
1223
+ || owners.some(candidate => candidate?.requestId === id && Array.from(ownerBindings.values()).some(binding => matches(candidate, binding))));
1224
+ const missing = owners.filter(candidate => requestIds.includes(candidate?.requestId) && !known.includes(candidate.requestId));
1225
+ if (!missing.length) return { known, ownersChecked: true, preDispatchChecked: true };
1226
+ // A worker restart drops ports before hidden pages can process reconnect
1227
+ // timers. The page's existing health receiver can still report its binding.
1228
+ const unknown = () => ({ known, ownersChecked: false, preDispatchChecked: false, retryAfterMs: 1000 });
1229
+ const tabs = await chrome.tabs.query({ url: MANAGED_OVERLEAF_MATCHES }).catch(() => null);
1230
+ if (!Array.isArray(tabs)) return unknown();
1231
+ const projects = new Set(missing.map(candidate => candidate.projectKey));
1232
+ const relevant = tabs.filter(tab => isManagedEditorTab(tab) && !tab.discarded && tab.status !== 'unloaded'
1233
+ && projects.has(new URL(tab.url).pathname.split('/')[2]));
1234
+ const responses = await Promise.all(relevant.map(async tab => {
1235
+ let timer;
1236
+ try {
1237
+ return await Promise.race([
1238
+ chrome.tabs.sendMessage(tab.id, { type: 'codex-overleaf/runtime-health-probe' }, { frameId: 0 }),
1239
+ new Promise(resolve => { timer = setTimeout(() => resolve(null), 1500); })
1240
+ ]);
1241
+ } catch (_error) { return null; }
1242
+ finally { clearTimeout(timer); }
1243
+ }));
1244
+ const needsStartupInventory = missing.some(candidate => candidate.preDispatch === true);
1245
+ if (responses.some(response => response?.ok !== true || ![1, 2].includes(response.runOwnerProtocol)
1246
+ || (needsStartupInventory && response.runOwnerProtocol !== 2) || !Array.isArray(response.runOwners))) return unknown();
1247
+ for (const candidate of missing) {
1248
+ if (responses.some(response => response.runOwners.some(binding => matches(candidate, binding)))
1249
+ || Array.from(ownerBindings.values()).some(binding => matches(candidate, binding))) known.push(candidate.requestId);
1250
+ }
1251
+ return { known, ownersChecked: true, preDispatchChecked: true };
1252
+ }
1253
+
1025
1254
  function pruneInMemoryRunJournals() {
1026
1255
  if (runJournals.size <= 16) {
1027
1256
  return;
@@ -1031,9 +1260,7 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
1031
1260
  .sort((a, b) => String(a.createdAt || '').localeCompare(String(b.createdAt || '')))
1032
1261
  .slice(0, runJournals.size - 16);
1033
1262
  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?.(() => {});
1263
+ removeRunJournal(journal.requestId).catch(() => {});
1037
1264
  }
1038
1265
  }
1039
1266
 
@@ -1041,27 +1268,26 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
1041
1268
  if (!id) {
1042
1269
  return;
1043
1270
  }
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);
1271
+ await removeRunJournal(id);
1048
1272
  }
1049
1273
 
1050
1274
  async function interruptOwnerlessRun(binding) {
1051
1275
  ownerCancelTimers.delete(binding.requestId);
1052
- if (Array.from(ownerBindings.values()).some(value => value.requestId === binding.requestId)) {
1276
+ if (hasRunOwner(binding)) {
1053
1277
  return;
1054
1278
  }
1055
- const journal = runJournals.get(binding.requestId);
1279
+ const journal = await restoreRunJournal(binding).catch(() => null);
1280
+ if (hasRunOwner(binding)) return;
1281
+ if (journal && runJournals.get(binding.requestId) !== journal) return;
1056
1282
  if (journal) {
1057
1283
  journal.ownerLost = true;
1058
1284
  journal.updatedAt = new Date().toISOString();
1059
1285
  await queueJournalWrite(binding.requestId).catch(() => {});
1060
1286
  }
1061
- if (!pending.has(binding.requestId)) {
1062
- return;
1063
- }
1287
+ if (hasRunOwner(binding)) return;
1288
+ if (journal && runJournals.get(binding.requestId) !== journal) return;
1064
1289
  try {
1290
+ if (!pending.has(binding.requestId) || !journalMatchesBinding(journal, binding)) return;
1065
1291
  sendNativeCancel({
1066
1292
  method: 'codex.cancel',
1067
1293
  params: {
@@ -1071,6 +1297,8 @@ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
1071
1297
  });
1072
1298
  } catch (_error) {
1073
1299
  // The original request disconnect path will settle the journal.
1300
+ } finally {
1301
+ pruneInMemoryRunJournals();
1074
1302
  }
1075
1303
  }
1076
1304
  })();