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.
- package/README.md +155 -392
- package/README.zh-CN.md +154 -393
- package/extension/bootstrap/background.js +52 -9
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/bootstrap/popup.html +1 -0
- package/extension/bootstrap/popupFallback.js +55 -0
- package/extension/bootstrap/updateStatus.js +1 -0
- package/extension/src/background.js +283 -55
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +129 -16
- package/extension/src/backgroundUpdateCoordinator.js +242 -29
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +171 -8
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +104 -47
- package/extension/src/content/composerPanel.js +110 -12
- package/extension/src/content/contentRuntime.js +1555 -926
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +48 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +11077 -3412
- package/extension/src/content/generated/content.bundle.meta.json +14 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +10 -0
- package/extension/src/content/localSkillsPanel.js +104 -14
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +47 -11
- package/extension/src/content/modelPicker.js +8 -4
- package/extension/src/content/nativeCompatibilityController.js +74 -30
- package/extension/src/content/otWarmMirror.js +5 -6
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +17 -4
- package/extension/src/content/panelMaintenance.js +46 -14
- package/extension/src/content/panelRenderer.js +57 -27
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +67 -6
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +73 -14
- package/extension/src/content/recentProjects.js +15 -8
- package/extension/src/content/runActivitySummary.js +160 -18
- package/extension/src/content/runCompletionMeta.js +532 -0
- package/extension/src/content/runController.js +39 -15
- package/extension/src/content/runFailureNotice.js +29 -13
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +157 -47
- package/extension/src/content/runRecoveryActions.js +147 -0
- package/extension/src/content/runResultActions.js +28 -143
- package/extension/src/content/runReviewController.js +633 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +367 -175
- package/extension/src/content/scopedPersistencePanelState.js +134 -20
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +153 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +50 -4
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +22 -5
- package/extension/src/content/settingsWorkbench.js +132 -89
- package/extension/src/content/sharedSessionViewSync.js +49 -11
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +118 -12
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +116 -94
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/overleafCapabilities.js +4 -1
- package/extension/src/page/ownedDirectoryCleanup.js +94 -0
- package/extension/src/page/referenceProjects.js +1 -0
- package/extension/src/page/reviewContext.js +113 -0
- package/extension/src/page/saveState.js +6 -4
- package/extension/src/page/snapshotRouter.js +84 -39
- package/extension/src/page/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +114 -26
- package/extension/src/page/trackedChangeCapture.js +12 -2
- package/extension/src/page/trackedChangeOwnership.js +19 -1
- package/extension/src/page/trackedChangeReplay.js +41 -21
- package/extension/src/page/trackedChangesLifecycle.js +217 -199
- package/extension/src/page/treeOperations.js +7 -10
- package/extension/src/page/updateSafetyProbe.js +41 -0
- package/extension/src/page/writebackReceiptJournal.js +2 -0
- package/extension/src/page/writebackRouter.js +103 -155
- package/extension/src/pageBridge.js +126 -69
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +373 -17
- package/extension/src/shared/auditRecords.js +4 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +129 -66
- package/extension/src/shared/composerAttachmentDraft.js +156 -0
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +349 -4
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +590 -24
- package/extension/src/shared/lineReferences.js +38 -31
- package/extension/src/shared/managedUpdateProjection.js +2 -2
- package/extension/src/shared/nativeRequestIdentity.js +1 -0
- package/extension/src/shared/pageRpcContract.js +5 -4
- package/extension/src/shared/panelIcons.js +39 -0
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/reviewing.js +130 -1
- package/extension/src/shared/runActivityModel.js +70 -4
- package/extension/src/shared/runExecutionSnapshot.js +13 -0
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +132 -28
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +118 -27
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +166 -52
- package/extension/src/shared/storageDb.js +90 -26
- package/extension/src/shared/storageMigration.js +78 -112
- package/extension/src/shared/storageRunActions.js +267 -30
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +253 -32
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +17 -3
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +58 -40
- package/extension/styles/panel.css +1123 -493
- package/native-host/src/anthropicBridgeRequest.js +4 -2
- package/native-host/src/anthropicBridgeResponse.js +6 -2
- package/native-host/src/anthropicMessagesBridge.js +24 -16
- package/native-host/src/bootstrapMigration.js +296 -0
- package/native-host/src/bootstrapMigrationContract.js +101 -0
- package/native-host/src/bootstrapUpdate.js +269 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +20 -1
- package/native-host/src/chatCompletionsBridge.js +48 -15
- package/native-host/src/codexConfigPaths.js +106 -0
- package/native-host/src/codexHome.js +66 -73
- package/native-host/src/codexHomeScope.js +124 -0
- package/native-host/src/codexInheritedAuth.js +90 -0
- package/native-host/src/codexLocalSkills.js +301 -0
- package/native-host/src/codexPromptAssembly.js +114 -127
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +234 -198
- package/native-host/src/codexSessionTiming.js +18 -8
- package/native-host/src/codexSkillIsolation.js +170 -0
- package/native-host/src/codexThreadFork.js +19 -3
- package/native-host/src/index.js +19 -7
- package/native-host/src/managedInstall.js +110 -35
- package/native-host/src/managedInstallRecovery.js +140 -0
- package/native-host/src/managedRuntimeEvidence.js +57 -0
- package/native-host/src/mirrorWorkspace.js +92 -22
- package/native-host/src/nativeDoctor.js +15 -0
- package/native-host/src/nativeHelperLifecycle.js +21 -0
- package/native-host/src/nativeHostRegistration.js +33 -0
- package/native-host/src/nativeRunLifecycle.js +71 -0
- package/native-host/src/processStopBarrier.js +42 -7
- package/native-host/src/projectPathPlatform.js +30 -0
- package/native-host/src/providerContextError.js +20 -0
- package/native-host/src/providerHttp.js +80 -0
- package/native-host/src/providerProfile.js +4 -2
- package/native-host/src/providerReasoning.js +2 -7
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/providerStore.js +72 -14
- package/native-host/src/responsesPassthroughBridge.js +266 -52
- package/native-host/src/runtimeInstaller.js +21 -1
- package/native-host/src/selectionScope.js +36 -8
- package/native-host/src/subagentAdmission.js +26 -0
- package/native-host/src/subagentBroker.js +79 -66
- package/native-host/src/subagentTelemetry.js +19 -10
- package/native-host/src/subagentWorkspacePath.js +5 -2
- package/native-host/src/taskRunnerRuntime.js +92 -57
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +409 -46
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +5 -8
- 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.
|
|
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.
|
|
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 =
|
|
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
|
|
326
|
-
&&
|
|
327
|
-
|
|
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]');
|