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
@@ -42,6 +42,9 @@
42
42
  if (!/^[a-f0-9]{64}$/i.test(String(params.sha256 || ''))) {
43
43
  return failure('binary_asset_hash_invalid', 'Asset SHA-256 is missing or invalid.');
44
44
  }
45
+ if (params.overwrite === true && !/^[a-f0-9]{64}$/i.test(String(params.previousSha256 || ''))) {
46
+ return failure('binary_asset_baseline_missing', 'The original binary version is unavailable. Read the project again before replacing this file.');
47
+ }
45
48
  pruneReceipts();
46
49
  if (transfers.has(transferId) || receipts.has(transferId)) {
47
50
  return failure('binary_upload_transfer_conflict', 'Asset transfer ID is already in use.');
@@ -54,9 +57,11 @@
54
57
  path: projectPath,
55
58
  size,
56
59
  sha256: String(params.sha256).toLowerCase(),
60
+ previousSha256: params.overwrite === true ? String(params.previousSha256).toLowerCase() : '',
57
61
  mimeType: String(params.mimeType || 'application/octet-stream'),
58
62
  projectId: treeOperations.getProjectId(),
59
63
  overwrite: params.overwrite === true,
64
+ createdDirectories: [], folderChanged: false, fileUploadAttempted: false,
60
65
  chunks: [],
61
66
  received: 0
62
67
  });
@@ -96,11 +101,19 @@
96
101
  transfer.promise = commitOnce(params).catch(error => ({
97
102
  ...failure(error.code || 'binary_upload_failed', error.message),
98
103
  changedDocument: transfer.mutationAttempted === true
99
- })).then(result => {
100
- transfer.result = result;
104
+ })).then(async result => {
105
+ if (result.ok !== true && transfer.createdDirectories.length && typeof deps.cleanupCreatedDirectories === 'function') {
106
+ try {
107
+ const cleanup = await deps.cleanupCreatedDirectories(transfer.createdDirectories, {
108
+ projectId: transfer.projectId, isCurrent: () => treeOperations.getProjectId() === transfer.projectId && receipts.get(id) === transfer
109
+ });
110
+ transfer.createdDirectories = cleanup.remaining || transfer.createdDirectories;
111
+ } catch (_error) { /* Retain ownership proofs when cleanup cannot be confirmed. */ }
112
+ }
113
+ transfer.result = { ...result, ...directoryFacts(transfer, result.ok !== true) };
101
114
  transfer.finishedAt = Date.now();
102
115
  transfer.chunks = [];
103
- return result;
116
+ return transfer.result;
104
117
  });
105
118
  return transfer.promise;
106
119
  }
@@ -112,11 +125,12 @@
112
125
  || treeOperations.getProjectId() !== transfer.projectId) {
113
126
  return failure('binary_upload_receipt_missing', 'Asset upload receipt is unavailable for this project.');
114
127
  }
115
- if (transfer.result?.ok !== true && transfer.mutationAttempted === true) {
128
+ if (transfer.result?.ok !== true && transfer.fileUploadAttempted === true) {
116
129
  const evidence = await waitForPath(transfer, {}, 2500).catch(() => null);
117
130
  if (evidence?.hashVerified === true) {
118
131
  transfer.result = { ok: true, written: true, path: transfer.path,
119
- changedDocument: true, method: 'remote-hash-reconciliation' };
132
+ changedDocument: true, method: 'remote-hash-reconciliation', ...directoryFacts(transfer),
133
+ entityId: evidence.observed?.id || readTreeEntityId(evidence.observed), sha256: transfer.sha256 };
120
134
  }
121
135
  }
122
136
  return { ok: true, transferId: transfer.id, runProjectId: transfer.projectId,
@@ -156,17 +170,22 @@
156
170
  assertTransferProject(transfer);
157
171
  const overwriteVerified = hashVerified || result?.confirmedByTransport === true;
158
172
  if (result?.ok && observed && overwriteVerified) {
159
- return { ok: true, written: true, path: transfer.path, method: result.method, changedDocument: true };
173
+ return { ok: true, written: true, path: transfer.path, method: result.method, changedDocument: true,
174
+ entityId: observed.id || observed._id || observed.entityId || observed.fileId || '', sha256: transfer.sha256 };
160
175
  }
161
176
  if (result?.ok && observed && !overwriteVerified) errors.push('Overleaf did not expose enough evidence to verify the binary replacement.');
162
177
  if (result?.reason) errors.push(result.reason);
163
178
  } catch (error) {
179
+ if (error?.code?.startsWith('binary_asset_baseline_')) return {
180
+ ...failure(error.code, error.message), changedDocument: transfer.mutationAttempted === true
181
+ };
164
182
  errors.push(error.message);
165
183
  }
166
184
  // Once submission may have happened, changing transports can duplicate
167
185
  // or overwrite a successful upload. Reconcile the same attempt instead.
168
186
  if (transfer.mutationAttempted === true) {
169
- const evidence = await waitForPath(transfer, {}, 12000).catch(() => null);
187
+ const evidence = transfer.fileUploadAttempted === true
188
+ ? await waitForPath(transfer, {}, 12000).catch(() => null) : null;
170
189
  if (evidence?.hashVerified === true) {
171
190
  return { ok: true, written: true, path: transfer.path,
172
191
  method: 'remote-hash-reconciliation', changedDocument: true };
@@ -194,6 +213,12 @@
194
213
  }
195
214
  }
196
215
 
216
+ function directoryFacts(transfer, requireCleanup = false) {
217
+ return { createdDirectories: transfer.createdDirectories.map(proof => ({ ...proof })),
218
+ folderChanged: transfer.folderChanged === true, fileUploadAttempted: transfer.fileUploadAttempted === true,
219
+ directoryCleanupRequired: requireCleanup && transfer.createdDirectories.length > 0 };
220
+ }
221
+
197
222
  async function uploadWithMultipart(file, transfer) {
198
223
  const projectId = treeOperations.getProjectId();
199
224
  if (!projectId || typeof windowRef.fetch !== 'function') return { ok: false, reason: 'Project upload endpoint is unavailable.' };
@@ -208,7 +233,11 @@
208
233
  const headers = csrf ? { 'X-CSRF-Token': csrf } : {};
209
234
  const query = folderId ? `?folder_id=${encodeURIComponent(folderId)}` : '';
210
235
  assertTransferProject(transfer);
236
+ await assertBinaryBaseline(transfer);
237
+ if (parentPath && await findFolderId(parentPath) !== folderId) throw baselineError('The target folder changed before replacement.');
238
+ assertTransferProject(transfer);
211
239
  transfer.mutationAttempted = true;
240
+ transfer.fileUploadAttempted = true;
212
241
  const response = await windowRef.fetch(`/project/${encodeURIComponent(projectId)}/upload${query}`, {
213
242
  method: 'POST', body: form, credentials: 'same-origin', headers
214
243
  });
@@ -231,15 +260,24 @@
231
260
 
232
261
  async function uploadWithDom(file, transfer) {
233
262
  const parentPath = transfer.path.split('/').slice(0, -1).join('/');
234
- if (typeof deps.prepareUploadParent !== 'function') {
263
+ if (typeof deps.prepareUploadParent !== 'function' || typeof deps.isUploadParentCurrent !== 'function') {
235
264
  return { ok: false, reason: 'Verified folder selection is unavailable for asset upload.' };
236
265
  }
237
- await deps.prepareUploadParent(parentPath, {
266
+ transfer.uploadParent = await deps.prepareUploadParent(parentPath, {
238
267
  createMissing: true,
239
- onMutation: () => { transfer.mutationAttempted = true; },
268
+ onMutation: () => { transfer.mutationAttempted = true; transfer.folderChanged = true; },
269
+ onCreatedDirectory: proof => {
270
+ if (proof?.v !== 1 || proof.projectId !== transfer.projectId || typeof proof.path !== 'string'
271
+ || !transfer.path.startsWith(proof.path + '/')) return;
272
+ const next = { v: 1, projectId: proof.projectId, path: proof.path, nativeFolderId: String(proof.nativeFolderId || ''),
273
+ ownershipConfirmed: proof.ownershipConfirmed === true, observedAfterCreate: proof.observedAfterCreate === true };
274
+ const index = transfer.createdDirectories.findIndex(value => value.path === proof.path);
275
+ if (index >= 0) transfer.createdDirectories[index] = next; else transfer.createdDirectories.push(next);
276
+ },
240
277
  isCurrent: () => treeOperations.getProjectId() === transfer.projectId && transfers.get(transfer.id) === transfer
241
278
  });
242
279
  assertTransferProject(transfer);
280
+ transfer.uploadParentPath = parentPath;
243
281
  let input = findFileInput();
244
282
  if (!input) {
245
283
  const button = findUploadButton();
@@ -251,8 +289,13 @@
251
289
  const data = new windowRef.DataTransfer();
252
290
  data.items.add(file);
253
291
  assertTransferProject(transfer);
292
+ await assertBinaryBaseline(transfer);
293
+ assertTransferProject(transfer);
294
+ if (deps.isUploadParentCurrent(parentPath, transfer.uploadParent) !== true) throw baselineError('The selected upload folder changed before submission.');
295
+ if (input.isConnected === false || findFileInput() !== input) throw baselineError('The upload input changed before submission.');
254
296
  assignFilesToInput(input, data.files, windowRef);
255
297
  transfer.mutationAttempted = true;
298
+ transfer.fileUploadAttempted = true;
256
299
  input.dispatchEvent(new windowRef.Event('input', { bubbles: true }));
257
300
  input.dispatchEvent(new windowRef.Event('change', { bubbles: true }));
258
301
  await clickReplaceIfShown(transfer);
@@ -265,10 +308,14 @@
265
308
  for (const name of ['uploadFile', 'uploadAsset', 'createBinaryFile', 'createFile', 'addFile']) {
266
309
  if (typeof manager[name] !== 'function') continue;
267
310
  try {
311
+ await assertBinaryBaseline(transfer);
312
+ assertTransferProject(transfer);
313
+ if (treeOperations.findFileTreeManager() !== manager) throw baselineError('The upload manager changed before submission.');
268
314
  transfer.mutationAttempted = true;
315
+ transfer.fileUploadAttempted = true;
269
316
  await manager[name](transfer.path, file);
270
317
  return { ok: true, method: `fileTreeManager.${name}`, confirmedByTransport: true };
271
- } catch (error) { return { ok: false, reason: error.message }; }
318
+ } catch (error) { if (error?.code?.startsWith('binary_asset_baseline_')) throw error; return { ok: false, reason: error.message }; }
272
319
  }
273
320
  return { ok: false, reason: 'Legacy file-tree manager has no compatible asset method.' };
274
321
  }
@@ -322,9 +369,84 @@
322
369
  await delay(180);
323
370
  assertTransferProject(transfer);
324
371
  if (!transfer.overwrite) return;
325
- const button = Array.from(documentRef.querySelectorAll('[role="dialog"] button')).find(node => !isCodexOwnedNode(node)
326
- && /replace|overwrite|替换|覆盖/i.test(node.textContent || ''));
327
- if (button) button.click();
372
+ const candidates = () => Array.from(documentRef.querySelectorAll('[role="dialog"] button')).filter(node => !isCodexOwnedNode(node)
373
+ && /^(replace|overwrite|替换|覆盖)(?:\b|\s|$)/i.test(String(node.textContent || '').trim()));
374
+ const buttons = candidates();
375
+ if (!buttons.length) return;
376
+ const matchesTarget = node => {
377
+ const dialog = node.closest?.('[role="dialog"]');
378
+ const text = String(dialog?.textContent || '');
379
+ return [transfer.path, transfer.path.split('/').pop()].some(path =>
380
+ new RegExp('(^|[^\\w./-])' + path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '($|[^\\w./-])').test(text));
381
+ };
382
+ const intended = buttons.filter(matchesTarget);
383
+ if (intended.length !== 1) throw baselineError('The replacement dialog could not be bound to the target file.');
384
+ const button = intended[0], dialog = button.closest('[role="dialog"]');
385
+ await assertBinaryBaseline(transfer);
386
+ assertTransferProject(transfer);
387
+ if (deps.isUploadParentCurrent(transfer.uploadParentPath, transfer.uploadParent) !== true) throw baselineError('The selected upload folder changed before replacement confirmation.');
388
+ const current = candidates().filter(matchesTarget);
389
+ if (current.length !== 1 || current[0] !== button || button.isConnected === false
390
+ || button.closest('[role="dialog"]') !== dialog) throw baselineError('The replacement dialog changed before confirmation.');
391
+ button.click();
392
+ }
393
+ async function binaryIdentity(transfer) {
394
+ assertTransferProject(transfer);
395
+ const nativeRoot = documentRef.querySelector?.('[data-testid="file-tree-list-root"]');
396
+ // The generic adapter may resolve a root basename to another folder.
397
+ // Only the native ancestry or an exact metadata path can bind old bytes.
398
+ const node = nativeRoot ? findNativeTreeNode(transfer.path, 'file') : null;
399
+ let id = readTreeEntityId(node);
400
+ if (!id) {
401
+ const list = await snapshotRouter?.buildProjectFileList({ force: true, maxAgeMs: 0, preferLightweight: true, allowZipFallback: false });
402
+ const file = list?.files?.find(file => file.path === transfer.path);
403
+ id = file?.id || file?._id || file?.entityId || file?.fileId || '';
404
+ }
405
+ assertTransferProject(transfer);
406
+ return id;
407
+ }
408
+ async function assertBinaryBaseline(transfer) {
409
+ if (!transfer.overwrite) return;
410
+ const { id, digest } = await readBaselineDigest(transfer);
411
+ assertTransferProject(transfer);
412
+ if (digest !== transfer.previousSha256) throw baselineError('The binary file changed since Codex read it. Read the project again before replacing it.');
413
+ transfer.previousEntityId = id;
414
+ }
415
+ function baselineError(message) { return Object.assign(new Error(message), { code: 'binary_asset_baseline_changed' }); }
416
+ async function readBaselineDigest(transfer) {
417
+ const controller = new AbortController();
418
+ let timer, reader;
419
+ const timeout = new Promise((_resolve, reject) => { timer = setTimeout(() => {
420
+ controller.abort(); reader?.cancel().catch(() => {});
421
+ reject(baselineError('Reading the original binary file timed out.'));
422
+ }, 8000); });
423
+ try {
424
+ return await Promise.race([timeout, (async () => {
425
+ const id = await binaryIdentity(transfer);
426
+ if (controller.signal.aborted) throw baselineError('Reading the original binary file timed out.');
427
+ if (!id || (transfer.previousEntityId && transfer.previousEntityId !== id)) throw baselineError('The binary file identity changed before replacement.');
428
+ const response = await windowRef.fetch(`/project/${encodeURIComponent(transfer.projectId)}/file/${encodeURIComponent(id)}`, {
429
+ credentials: 'same-origin', cache: 'no-store', signal: controller.signal
430
+ });
431
+ if (!response.ok || Number(response.headers?.get?.('content-length')) > MAX_ASSET_BYTES) throw baselineError('The original binary file is unavailable or exceeds the read limit.');
432
+ let bytes;
433
+ if (response.body?.getReader) {
434
+ reader = response.body.getReader(); const chunks = []; let size = 0;
435
+ for (;;) { const chunk = await reader.read(); if (chunk.done) break;
436
+ size += chunk.value.byteLength;
437
+ if (size > MAX_ASSET_BYTES) throw baselineError('The original binary file exceeds the read limit.');
438
+ chunks.push(chunk.value);
439
+ }
440
+ bytes = joinChunks(chunks, size);
441
+ } else bytes = new Uint8Array(await response.arrayBuffer());
442
+ if (bytes.byteLength > MAX_ASSET_BYTES) throw baselineError('The original binary file exceeds the read limit.');
443
+ const digest = await sha256Hex(bytes, windowRef.crypto);
444
+ if (await binaryIdentity(transfer) !== id) throw baselineError('The binary file identity changed while its original bytes were read.');
445
+ assertTransferProject(transfer);
446
+ if (controller.signal.aborted) throw baselineError('Reading the original binary file timed out.');
447
+ return { id, digest };
448
+ })()]);
449
+ } finally { clearTimeout(timer); controller.abort(); reader?.cancel().catch(() => {}); }
328
450
  }
329
451
  function readTreeEntityId(node) {
330
452
  const owner = node?.closest?.('[data-entity-id], [data-file-id], [data-folder-id], [data-id]');