codex-overleaf-link 2.5.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -26
- package/README.zh-CN.md +39 -27
- package/extension/bootstrap/background.js +52 -9
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/bootstrap/popup.html +1 -0
- package/extension/bootstrap/popupFallback.js +55 -0
- package/extension/bootstrap/updateStatus.js +1 -0
- package/extension/src/background.js +206 -53
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +74 -14
- package/extension/src/backgroundUpdateCoordinator.js +118 -21
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +8 -2
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +93 -45
- package/extension/src/content/composerPanel.js +18 -0
- package/extension/src/content/contentRuntime.js +661 -676
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +30 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +6879 -2786
- package/extension/src/content/generated/content.bundle.meta.json +11 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +6 -0
- package/extension/src/content/localSkillsPanel.js +102 -12
- package/extension/src/content/markdownText.js +5 -6
- package/extension/src/content/modelPicker.js +7 -4
- package/extension/src/content/nativeCompatibilityController.js +45 -20
- package/extension/src/content/otWarmMirror.js +4 -5
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +15 -2
- package/extension/src/content/panelMaintenance.js +40 -12
- package/extension/src/content/panelRenderer.js +6 -0
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +39 -2
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +71 -13
- package/extension/src/content/recentProjects.js +11 -5
- package/extension/src/content/runActivitySummary.js +40 -6
- package/extension/src/content/runCompletionMeta.js +344 -0
- package/extension/src/content/runController.js +37 -14
- package/extension/src/content/runFailureNotice.js +21 -12
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runQueueScheduler.js +148 -42
- package/extension/src/content/runRecoveryActions.js +146 -0
- package/extension/src/content/runResultActions.js +8 -125
- package/extension/src/content/runReviewController.js +574 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +146 -153
- package/extension/src/content/scopedPersistencePanelState.js +51 -6
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +140 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +1 -1
- package/extension/src/content/settingsPanel.js +7 -3
- package/extension/src/content/settingsWorkbench.js +5 -0
- package/extension/src/content/sharedSessionViewSync.js +45 -8
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +60 -8
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +82 -73
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/ownedDirectoryCleanup.js +94 -0
- package/extension/src/page/referenceProjects.js +1 -0
- package/extension/src/page/reviewContext.js +113 -0
- package/extension/src/page/saveState.js +6 -4
- package/extension/src/page/snapshotRouter.js +84 -39
- package/extension/src/page/textFileCreator.js +105 -25
- package/extension/src/page/trackedChangeCapture.js +12 -2
- package/extension/src/page/trackedChangeOwnership.js +19 -1
- package/extension/src/page/trackedChangeReplay.js +41 -21
- package/extension/src/page/trackedChangesLifecycle.js +217 -199
- package/extension/src/page/treeOperations.js +7 -10
- package/extension/src/page/updateSafetyProbe.js +41 -0
- package/extension/src/page/writebackReceiptJournal.js +2 -0
- package/extension/src/page/writebackRouter.js +80 -79
- package/extension/src/pageBridge.js +19 -3
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +23 -0
- package/extension/src/shared/auditRecords.js +2 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +79 -57
- package/extension/src/shared/composerAttachmentDraft.js +151 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +309 -3
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +298 -2
- package/extension/src/shared/managedUpdateProjection.js +2 -2
- package/extension/src/shared/nativeRequestIdentity.js +1 -0
- package/extension/src/shared/pageRpcContract.js +5 -4
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +72 -27
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +37 -14
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +118 -13
- package/extension/src/shared/storageDb.js +58 -26
- package/extension/src/shared/storageMigration.js +71 -111
- package/extension/src/shared/storageRunActions.js +209 -26
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +136 -13
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +1 -1
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +38 -38
- package/extension/styles/panel.css +19 -5
- package/native-host/src/anthropicBridgeRequest.js +4 -2
- package/native-host/src/anthropicBridgeResponse.js +6 -2
- package/native-host/src/anthropicMessagesBridge.js +24 -16
- package/native-host/src/bootstrapMigration.js +296 -0
- package/native-host/src/bootstrapMigrationContract.js +101 -0
- package/native-host/src/bootstrapUpdate.js +233 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeResponse.js +10 -0
- package/native-host/src/chatCompletionsBridge.js +21 -15
- package/native-host/src/codexConfigPaths.js +106 -0
- package/native-host/src/codexHome.js +66 -73
- package/native-host/src/codexHomeScope.js +124 -0
- package/native-host/src/codexInheritedAuth.js +90 -0
- package/native-host/src/codexLocalSkills.js +301 -0
- package/native-host/src/codexPromptAssembly.js +31 -12
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +110 -183
- package/native-host/src/codexSessionTiming.js +18 -8
- package/native-host/src/codexSkillIsolation.js +170 -0
- package/native-host/src/codexThreadFork.js +19 -3
- package/native-host/src/index.js +19 -7
- package/native-host/src/managedInstall.js +110 -35
- package/native-host/src/managedInstallRecovery.js +140 -0
- package/native-host/src/managedRuntimeEvidence.js +57 -0
- package/native-host/src/mirrorWorkspace.js +92 -22
- package/native-host/src/nativeDoctor.js +15 -0
- package/native-host/src/nativeHelperLifecycle.js +21 -0
- package/native-host/src/nativeHostRegistration.js +33 -0
- package/native-host/src/nativeRunLifecycle.js +71 -0
- package/native-host/src/processStopBarrier.js +42 -7
- package/native-host/src/projectPathPlatform.js +30 -0
- package/native-host/src/providerHttp.js +80 -0
- package/native-host/src/providerProfile.js +4 -2
- package/native-host/src/providerReasoning.js +2 -7
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/providerStore.js +72 -14
- package/native-host/src/responsesPassthroughBridge.js +266 -52
- package/native-host/src/runtimeInstaller.js +21 -1
- package/native-host/src/selectionScope.js +36 -8
- package/native-host/src/subagentAdmission.js +26 -0
- package/native-host/src/subagentBroker.js +79 -66
- package/native-host/src/subagentTelemetry.js +19 -10
- package/native-host/src/subagentWorkspacePath.js +5 -2
- package/native-host/src/taskRunnerRuntime.js +77 -54
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +297 -39
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +1 -1
- package/scripts/install-managed.mjs +1 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
const api = factory();
|
|
3
|
+
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
4
|
+
if (root) root.CodexOverleafReviewContext = api;
|
|
5
|
+
})(typeof window !== 'undefined' ? window : globalThis, function () {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const cancelledOwners = new Map();
|
|
9
|
+
function cancelOwner(token) {
|
|
10
|
+
if (typeof token !== 'string' || !token || token.length > 256) return false;
|
|
11
|
+
for (const [id, until] of cancelledOwners) if (until <= Date.now()) cancelledOwners.delete(id);
|
|
12
|
+
cancelledOwners.set(token, Date.now() + 120000); return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// One context per request. The small response reserve lets an expired review
|
|
16
|
+
// return its known partial evidence before the caller's transport timer fires.
|
|
17
|
+
function create(deps, input = {}) {
|
|
18
|
+
const now = deps.now || Date.now;
|
|
19
|
+
const timers = typeof deps.window?.setTimeout === 'function' ? deps.window : globalThis;
|
|
20
|
+
const started = now(), limit = input.deadlineAt == null ? started + 120000 : input.deadlineAt;
|
|
21
|
+
const deadlineAt = Number.isFinite(limit) ? limit - Math.min(250, Math.max(0, limit - started) / 10) : started;
|
|
22
|
+
const cancellation = deps.readWriteCancellationSequence?.();
|
|
23
|
+
const appliedLists = new Set(), observations = new Map();
|
|
24
|
+
let stopped = null, mutationStarted = false;
|
|
25
|
+
const params = { ...input, deadlineAt };
|
|
26
|
+
const failure = (code, reason) => ({ ok: false, code, reason, changedDocument: false,
|
|
27
|
+
failure: { code: code === 'codex_cancelled' ? 'codex_run_cancelled' : 'tracked_changes_remain',
|
|
28
|
+
stage: 'reviewing', severity: 'blocked', terminalState: 'blocked', changedDocument: false,
|
|
29
|
+
userMessage: reason, nextAction: 'Check the retained per-file results in Overleaf before retrying.',
|
|
30
|
+
evidence: { originalCode: code, writeStarted: false } } });
|
|
31
|
+
function check() {
|
|
32
|
+
if (stopped) return stopped;
|
|
33
|
+
if (params.reviewOwnerToken && cancelledOwners.has(params.reviewOwnerToken))
|
|
34
|
+
return stopped = failure('codex_cancelled', 'The Undo owner disconnected; no further review writes were started.');
|
|
35
|
+
if (!Number.isFinite(limit) || now() >= deadlineAt)
|
|
36
|
+
return stopped = failure('writeback_deadline_exceeded', 'The review time budget expired; no further review writes were started.');
|
|
37
|
+
if (typeof deps.readWriteCancellationSequence === 'function' && deps.readWriteCancellationSequence() !== cancellation)
|
|
38
|
+
return stopped = failure('codex_cancelled', 'The review was cancelled; no further review writes were started.');
|
|
39
|
+
const project = deps.getProjectId?.() || /\/project\/([^/?#]+)/.exec(deps.window?.location?.pathname || '')?.[1];
|
|
40
|
+
if (!params.runProjectId || (project && project !== params.runProjectId))
|
|
41
|
+
return stopped = failure('aborted_project_changed', 'The original review project is no longer active.');
|
|
42
|
+
const blocked = deps.checkWritebackRunProjectId?.(params);
|
|
43
|
+
if (blocked) return stopped = blocked.skipped?.[0]?.result || blocked;
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function assert() {
|
|
47
|
+
const blocked = check();
|
|
48
|
+
if (blocked) throw Object.assign(new Error(blocked.reason || blocked.code), { reviewFailure: blocked });
|
|
49
|
+
}
|
|
50
|
+
async function wait(work) {
|
|
51
|
+
assert();
|
|
52
|
+
let timer;
|
|
53
|
+
try {
|
|
54
|
+
const timeout = new Promise((_, reject) => {
|
|
55
|
+
timer = timers.setTimeout(() => {
|
|
56
|
+
const blocked = check() || failure('writeback_deadline_exceeded', 'The review time budget expired.');
|
|
57
|
+
stopped = blocked;
|
|
58
|
+
reject(Object.assign(new Error(blocked.reason), { reviewFailure: blocked }));
|
|
59
|
+
}, Math.max(0, deadlineAt - now()));
|
|
60
|
+
});
|
|
61
|
+
const result = await Promise.race([Promise.resolve().then(() => { assert(); return work(); }), timeout]);
|
|
62
|
+
assert();
|
|
63
|
+
return result;
|
|
64
|
+
} finally { if (timer != null) timers.clearTimeout(timer); }
|
|
65
|
+
}
|
|
66
|
+
const remember = entry => {
|
|
67
|
+
if (entry.result?.changedDocument === true || entry.result?.failure?.changedDocument === true) mutationStarted = true;
|
|
68
|
+
const ref = entry.trackedChange || { path: entry.operation?.path || '', key: 'snapshot-undo:' + (entry.operation?.path || ''), id: '' };
|
|
69
|
+
observations.set(ref.path + '\0' + (ref.key || ref.id || ''), { trackedChange: ref, result: entry.result });
|
|
70
|
+
};
|
|
71
|
+
const scoped = { ...deps, reviewGuard: check, reviewAwait: work => wait(work),
|
|
72
|
+
reviewDeadlineAt: deadlineAt,
|
|
73
|
+
createReviewApplied() { const list = []; appliedLists.add(list); return list; } };
|
|
74
|
+
for (const name of ['openFileByPath', 'setReviewingEnabled', 'verifyActiveEditorText']) {
|
|
75
|
+
if (typeof deps[name] === 'function') scoped[name] = (...args) => wait(() => deps[name](...args));
|
|
76
|
+
}
|
|
77
|
+
scoped.delay = ms => wait(() => deps.delay(Math.min(ms, Math.max(0, deadlineAt - now()))));
|
|
78
|
+
if (typeof deps.readActiveEditorText === 'function') scoped.readActiveEditorText = (...args) => { assert(); return deps.readActiveEditorText(...args); };
|
|
79
|
+
if (typeof deps.clickNode === 'function') scoped.clickNode = (...args) => { assert(); mutationStarted = true; return deps.clickNode(...args); };
|
|
80
|
+
for (const name of ['applyOperationsCore', 'applyOperationsWithNoTraceUndo']) {
|
|
81
|
+
if (typeof deps[name] !== 'function') continue;
|
|
82
|
+
scoped[name] = async (operations, options = {}) => {
|
|
83
|
+
assert();
|
|
84
|
+
const result = await deps[name](operations, { ...options, deadlineAt,
|
|
85
|
+
reviewGuard: check, reviewAwait: work => wait(work),
|
|
86
|
+
onOperationResult(entry) { remember(entry); options.onOperationResult?.(entry); } });
|
|
87
|
+
for (const entry of result.applied || []) remember(entry);
|
|
88
|
+
// Preserve a completed in-flight result even if cancellation just arrived.
|
|
89
|
+
return result;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (typeof deps.confirmReviewWriteback === 'function') scoped.confirmReviewWriteback = async (original, result) => {
|
|
93
|
+
for (const entry of result.applied || []) remember(entry);
|
|
94
|
+
return deps.confirmReviewWriteback({ ...original, deadlineAt, reviewGuard: check, reviewAwait: work => wait(work) }, result);
|
|
95
|
+
};
|
|
96
|
+
async function run(work) {
|
|
97
|
+
try { assert(); return await work(scoped, params); }
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (!error?.reviewFailure) throw error;
|
|
100
|
+
for (const list of appliedLists) for (const entry of list) remember(entry);
|
|
101
|
+
const applied = [...observations.values()].filter(entry => entry.result?.ok !== false);
|
|
102
|
+
const path = deps.getActiveFilePath?.() || '';
|
|
103
|
+
const changedDocument = mutationStarted || applied.some(entry => entry.result?.changedDocument !== false);
|
|
104
|
+
const blocked = { ...error.reviewFailure, changedDocument,
|
|
105
|
+
failure: { ...error.reviewFailure.failure, changedDocument,
|
|
106
|
+
evidence: { ...error.reviewFailure.failure?.evidence, writeStarted: changedDocument } } };
|
|
107
|
+
return { ok: false, applied, skipped: [{ trackedChange: { path }, result: blocked }] };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return Object.freeze({ run, check, assert, wait, params, scoped });
|
|
111
|
+
}
|
|
112
|
+
return Object.freeze({ create, cancelOwner });
|
|
113
|
+
});
|
|
@@ -311,16 +311,18 @@
|
|
|
311
311
|
expected.set(path, entry.result.verifiedContent);
|
|
312
312
|
}
|
|
313
313
|
}
|
|
314
|
-
const deadline = now() + Math.max(0, timeoutMs)
|
|
314
|
+
const deadline = Math.min(now() + Math.max(0, timeoutMs), params.deadlineAt == null ? Infinity
|
|
315
|
+
: Number.isFinite(params.deadlineAt) ? params.deadlineAt : now());
|
|
315
316
|
let attempts = 0;
|
|
316
317
|
if (expected.size && typeof readSnapshot === 'function') {
|
|
317
318
|
do {
|
|
318
|
-
if (!params.runProjectId || getProjectId() !== params.runProjectId) break;
|
|
319
|
+
if (now() >= deadline || params.reviewGuard?.() || !params.runProjectId || getProjectId() !== params.runProjectId) break;
|
|
319
320
|
try {
|
|
320
|
-
const
|
|
321
|
+
const read = () => readSnapshot({ force: true, maxAgeMs: 0,
|
|
321
322
|
zipTimeoutMs: Math.max(1, Math.min(8000, deadline - now())) });
|
|
323
|
+
const snapshot = params.reviewAwait ? await params.reviewAwait(read) : await read();
|
|
322
324
|
attempts++;
|
|
323
|
-
if (getProjectId() !== params.runProjectId) break;
|
|
325
|
+
if (now() >= deadline || params.reviewGuard?.() || getProjectId() !== params.runProjectId) break;
|
|
324
326
|
if (snapshot?.ok === true && Array.from(expected).every(([path, content]) =>
|
|
325
327
|
snapshot.files?.some(file => file.path === path && file.source === 'overleaf-zip'
|
|
326
328
|
&& file.content === content))) {
|
|
@@ -97,9 +97,10 @@
|
|
|
97
97
|
};
|
|
98
98
|
}),
|
|
99
99
|
capabilities: {
|
|
100
|
-
fullProjectSnapshot:
|
|
100
|
+
fullProjectSnapshot: zipSnapshot.pathIdentityComplete !== false,
|
|
101
|
+
pathIdentityComplete: zipSnapshot.pathIdentityComplete,
|
|
101
102
|
method: 'overleaf-zip-file-list',
|
|
102
|
-
skipped: [],
|
|
103
|
+
skipped: zipSnapshot.skipped || [],
|
|
103
104
|
diagnostics: {
|
|
104
105
|
zipEndpoint: zipSnapshot.endpoint,
|
|
105
106
|
zipTiming: zipSnapshot.diagnostics,
|
|
@@ -243,7 +244,7 @@
|
|
|
243
244
|
if (zipSnapshot.ok && zipSnapshot.files.length) {
|
|
244
245
|
const mergedFiles = mergeSnapshotFiles(
|
|
245
246
|
zipSnapshot.files,
|
|
246
|
-
filterZipSnapshotOverlayFiles(zipSnapshot.files, lightweightSnapshot?.files || [])
|
|
247
|
+
filterZipSnapshotOverlayFiles(zipSnapshot.files, lightweightSnapshot?.files || [], zipSnapshot.skipped)
|
|
247
248
|
);
|
|
248
249
|
const files = filterRequestedSnapshotFiles(mergedFiles, params, activePath);
|
|
249
250
|
const requestedPathsComplete = params.restrictToRequestedPathsOnly === true
|
|
@@ -255,7 +256,9 @@
|
|
|
255
256
|
activePath,
|
|
256
257
|
files,
|
|
257
258
|
capabilities: {
|
|
258
|
-
fullProjectSnapshot: params.restrictToRequestedPathsOnly
|
|
259
|
+
fullProjectSnapshot: params.restrictToRequestedPathsOnly !== true && zipSnapshot.pathIdentityComplete !== false,
|
|
260
|
+
pathIdentityComplete: zipSnapshot.pathIdentityComplete,
|
|
261
|
+
contentComplete: !(zipSnapshot.skipped || []).length,
|
|
259
262
|
requestedPathsComplete,
|
|
260
263
|
method: 'overleaf-zip',
|
|
261
264
|
skipped: zipSnapshot.skipped || [],
|
|
@@ -316,7 +319,8 @@
|
|
|
316
319
|
80
|
|
317
320
|
);
|
|
318
321
|
const files = [];
|
|
319
|
-
const skipped = [];
|
|
322
|
+
const skipped = [...(zipSnapshot?.skipped || [])];
|
|
323
|
+
const ambiguousPaths = new Set(skipped.filter(file => file.reason === 'zip_path_duplicate').map(file => file.path));
|
|
320
324
|
if (zipSnapshot && !zipSnapshot.ok) {
|
|
321
325
|
skipped.push({
|
|
322
326
|
path: 'project.zip',
|
|
@@ -326,6 +330,7 @@
|
|
|
326
330
|
let previousSignature = '';
|
|
327
331
|
|
|
328
332
|
for (const filePath of projectPaths) {
|
|
333
|
+
if (ambiguousPaths.has(filePath)) continue;
|
|
329
334
|
if (filePath === snapshotActivePath) {
|
|
330
335
|
const ready = await waitForActiveEditorText(filePath, 5000);
|
|
331
336
|
if (!ready.ok) {
|
|
@@ -404,7 +409,7 @@
|
|
|
404
409
|
await waitForActiveEditorText(activePath, 5000);
|
|
405
410
|
}
|
|
406
411
|
|
|
407
|
-
if (snapshotActivePath && !files.length && (!lightweightOnly || params.allowZipFallback === false)) {
|
|
412
|
+
if (snapshotActivePath && !ambiguousPaths.has(snapshotActivePath) && !files.length && (!lightweightOnly || params.allowZipFallback === false)) {
|
|
408
413
|
files.push({
|
|
409
414
|
path: snapshotActivePath,
|
|
410
415
|
content: readActiveEditorText(),
|
|
@@ -419,7 +424,8 @@
|
|
|
419
424
|
activePath: snapshotActivePath,
|
|
420
425
|
files,
|
|
421
426
|
capabilities: {
|
|
422
|
-
fullProjectSnapshot: params.restrictToRequestedPathsOnly
|
|
427
|
+
fullProjectSnapshot: params.restrictToRequestedPathsOnly !== true && requestedPathsComplete && zipSnapshot?.pathIdentityComplete !== false,
|
|
428
|
+
pathIdentityComplete: zipSnapshot?.pathIdentityComplete,
|
|
423
429
|
requestedPathsComplete,
|
|
424
430
|
method: resolvePageSnapshotMethod(files, lightweightOnly),
|
|
425
431
|
skipped,
|
|
@@ -438,15 +444,14 @@
|
|
|
438
444
|
|
|
439
445
|
async function buildZipOnlyProjectSnapshot(params = {}) {
|
|
440
446
|
const activePath = getPassiveActiveFilePath();
|
|
441
|
-
|
|
447
|
+
// Capture read evidence together with the path/text before awaiting ZIP.
|
|
448
|
+
// A later editor identity cannot prove that an earlier empty read succeeded.
|
|
449
|
+
const activeOverlay = params.serverOnly === true ? [] : readActiveEditorOverlay(activePath);
|
|
442
450
|
|
|
443
451
|
const zipSnapshot = await fetchProjectZipSnapshot(params);
|
|
444
452
|
if (zipSnapshot.ok && zipSnapshot.files.length) {
|
|
445
|
-
const activeOverlay = params.serverOnly === true ? [] : buildActiveEditorOverlay(activePath, activeText, {
|
|
446
|
-
allowedPaths: zipSnapshot.files.map(file => file.path)
|
|
447
|
-
});
|
|
448
453
|
const files = filterRequestedSnapshotFiles(
|
|
449
|
-
mergeSnapshotFiles(zipSnapshot.files, activeOverlay),
|
|
454
|
+
mergeSnapshotFiles(zipSnapshot.files, filterZipSnapshotOverlayFiles(zipSnapshot.files, activeOverlay, zipSnapshot.skipped)),
|
|
450
455
|
params,
|
|
451
456
|
activePath
|
|
452
457
|
);
|
|
@@ -459,7 +464,9 @@
|
|
|
459
464
|
activePath,
|
|
460
465
|
files,
|
|
461
466
|
capabilities: {
|
|
462
|
-
fullProjectSnapshot: params.restrictToRequestedPathsOnly
|
|
467
|
+
fullProjectSnapshot: params.restrictToRequestedPathsOnly !== true && zipSnapshot.pathIdentityComplete !== false,
|
|
468
|
+
pathIdentityComplete: zipSnapshot.pathIdentityComplete,
|
|
469
|
+
contentComplete: !(zipSnapshot.skipped || []).length,
|
|
463
470
|
requestedPathsComplete,
|
|
464
471
|
method: 'overleaf-zip',
|
|
465
472
|
skipped: zipSnapshot.skipped || [],
|
|
@@ -478,16 +485,18 @@
|
|
|
478
485
|
id: getProjectId(),
|
|
479
486
|
url: window.location.href,
|
|
480
487
|
activePath,
|
|
481
|
-
files:
|
|
488
|
+
files: filterZipSnapshotOverlayFiles([], activeOverlay, zipSnapshot.skipped, true),
|
|
482
489
|
capabilities: {
|
|
483
490
|
fullProjectSnapshot: false,
|
|
491
|
+
pathIdentityComplete: zipSnapshot.pathIdentityComplete,
|
|
484
492
|
method: params.serverOnly === true ? 'overleaf-zip-unavailable'
|
|
485
|
-
:
|
|
493
|
+
: activeOverlay.length
|
|
486
494
|
? 'active-editor-zip-only-fallback'
|
|
487
495
|
: 'zip-only-fallback-empty',
|
|
488
|
-
|
|
496
|
+
contentComplete: false,
|
|
497
|
+
skipped: [...(zipSnapshot.skipped || []), {
|
|
489
498
|
path: 'project.zip',
|
|
490
|
-
reason: zipSnapshot.reason
|
|
499
|
+
reason: zipSnapshot.reason || 'ZIP file identities were incomplete'
|
|
491
500
|
}],
|
|
492
501
|
diagnostics: {
|
|
493
502
|
docRecordCount: 0,
|
|
@@ -500,9 +509,11 @@
|
|
|
500
509
|
};
|
|
501
510
|
}
|
|
502
511
|
|
|
503
|
-
function filterZipSnapshotOverlayFiles(zipFiles = [], overlayFiles = []) {
|
|
512
|
+
function filterZipSnapshotOverlayFiles(zipFiles = [], overlayFiles = [], skipped = [], allowMissing = false) {
|
|
504
513
|
const zipPaths = new Set((zipFiles || []).map(file => file.path).filter(Boolean));
|
|
505
|
-
|
|
514
|
+
const ambiguousPaths = new Set((skipped || []).filter(file => file.reason === 'zip_path_duplicate').map(file => file.path));
|
|
515
|
+
return (overlayFiles || []).filter(file => !ambiguousPaths.has(file?.path)
|
|
516
|
+
&& (allowMissing || file?.source !== 'active-editor' || zipPaths.has(file.path)));
|
|
506
517
|
}
|
|
507
518
|
|
|
508
519
|
function getRequestedSnapshotPaths(params = {}, activePath = '') {
|
|
@@ -520,19 +531,32 @@
|
|
|
520
531
|
return (files || []).filter(file => requestedSet.has(file?.path));
|
|
521
532
|
}
|
|
522
533
|
|
|
523
|
-
function
|
|
524
|
-
if (!activePath
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
534
|
+
function readActiveEditorOverlay(activePath) {
|
|
535
|
+
if (!activePath) return [];
|
|
536
|
+
try {
|
|
537
|
+
const content = readActiveEditorText();
|
|
538
|
+
if (typeof content !== 'string') return [];
|
|
539
|
+
if (!content.trim()) {
|
|
540
|
+
// The legacy reader also returns '' when no surface exists. A readable
|
|
541
|
+
// identity must independently confirm blank text before it can erase ZIP.
|
|
542
|
+
const identity = deps.getActiveEditorIdentity?.();
|
|
543
|
+
let observed;
|
|
544
|
+
if (identity?.type === 'codemirror-view' && typeof identity.doc?.toString === 'function') {
|
|
545
|
+
observed = identity.doc.toString();
|
|
546
|
+
} else if (identity?.type === 'textarea') {
|
|
547
|
+
observed = identity.node?.value;
|
|
548
|
+
} else if (identity?.type === 'contenteditable') {
|
|
549
|
+
const node = identity.node;
|
|
550
|
+
if (typeof node?.innerText === 'string' || typeof node?.textContent === 'string') {
|
|
551
|
+
observed = node.innerText || node.textContent || '';
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (typeof observed !== 'string' || observed !== content) return [];
|
|
555
|
+
} else if (!window.CodexOverleafProjectFiles.isUsableProjectFileContent(content)) return [];
|
|
556
|
+
return [{ path: activePath, content, source: 'active-editor', kind: 'text' }];
|
|
557
|
+
} catch (_error) {
|
|
528
558
|
return [];
|
|
529
559
|
}
|
|
530
|
-
return [{
|
|
531
|
-
path: activePath,
|
|
532
|
-
content: activeText,
|
|
533
|
-
source: 'active-editor',
|
|
534
|
-
kind: 'text'
|
|
535
|
-
}];
|
|
536
560
|
}
|
|
537
561
|
|
|
538
562
|
function mergeSnapshotFiles(primaryFiles = [], overrideFiles = []) {
|
|
@@ -541,7 +565,7 @@
|
|
|
541
565
|
if (!file?.path || file.kind === 'binary' || typeof file.content !== 'string') {
|
|
542
566
|
continue;
|
|
543
567
|
}
|
|
544
|
-
if (!window.CodexOverleafProjectFiles.isUsableProjectFileContent(file.content)) {
|
|
568
|
+
if (file.content.trim() && !window.CodexOverleafProjectFiles.isUsableProjectFileContent(file.content)) {
|
|
545
569
|
continue;
|
|
546
570
|
}
|
|
547
571
|
filesByPath.set(file.path, {
|
|
@@ -691,7 +715,7 @@
|
|
|
691
715
|
});
|
|
692
716
|
const extractMs = Date.now() - extractStartedAt;
|
|
693
717
|
const files = extracted.files || [];
|
|
694
|
-
if (!files.length) {
|
|
718
|
+
if (!files.length && extracted.pathIdentityComplete !== false) {
|
|
695
719
|
attempts.push({
|
|
696
720
|
endpoint,
|
|
697
721
|
status: response.status,
|
|
@@ -715,6 +739,7 @@
|
|
|
715
739
|
return {
|
|
716
740
|
ok: true,
|
|
717
741
|
endpoint,
|
|
742
|
+
pathIdentityComplete: extracted.pathIdentityComplete,
|
|
718
743
|
skipped: extracted.skipped || [],
|
|
719
744
|
files: files.map(file => ({
|
|
720
745
|
...file,
|
|
@@ -841,6 +866,9 @@
|
|
|
841
866
|
const entryCount = view.getUint16(eocdOffset + 10, true);
|
|
842
867
|
const centralDirectoryOffset = view.getUint32(eocdOffset + 16, true);
|
|
843
868
|
const decoder = new TextDecoder('utf-8');
|
|
869
|
+
const nameDecoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
870
|
+
const seenPaths = new Set();
|
|
871
|
+
let pathIdentityComplete = true;
|
|
844
872
|
const files = [];
|
|
845
873
|
const skipped = [];
|
|
846
874
|
let binaryBytes = 0;
|
|
@@ -859,7 +887,25 @@
|
|
|
859
887
|
const commentLength = view.getUint16(offset + 32, true);
|
|
860
888
|
const localHeaderOffset = view.getUint32(offset + 42, true);
|
|
861
889
|
const rawName = bytes.slice(offset + 46, offset + 46 + fileNameLength);
|
|
862
|
-
|
|
890
|
+
let name = '';
|
|
891
|
+
try { name = nameDecoder.decode(rawName); } catch (_error) { /* unknown identity is incomplete */ }
|
|
892
|
+
const directory = name.endsWith('/');
|
|
893
|
+
const path = normalizeSafeProjectPath(directory ? name.slice(0, -1) : name);
|
|
894
|
+
if (!path || seenPaths.has(path)) {
|
|
895
|
+
if (path) {
|
|
896
|
+
const previousIndex = files.findIndex(file => file.path === path);
|
|
897
|
+
if (previousIndex >= 0) files.splice(previousIndex, 1);
|
|
898
|
+
}
|
|
899
|
+
pathIdentityComplete = false;
|
|
900
|
+
skipped.push({ path: name, reason: path ? 'zip_path_duplicate' : 'zip_path_invalid', unknownPath: !name });
|
|
901
|
+
offset += 46 + fileNameLength + extraLength + commentLength;
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
seenPaths.add(path);
|
|
905
|
+
if (directory) {
|
|
906
|
+
offset += 46 + fileNameLength + extraLength + commentLength;
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
863
909
|
const isText = window.CodexOverleafProjectFiles.isTextProjectPath(path);
|
|
864
910
|
const shouldInclude = path && !path.endsWith('/') && (isText || options.includeBinaryFiles);
|
|
865
911
|
|
|
@@ -904,7 +950,7 @@
|
|
|
904
950
|
offset += 46 + fileNameLength + extraLength + commentLength;
|
|
905
951
|
}
|
|
906
952
|
|
|
907
|
-
return { files, skipped };
|
|
953
|
+
return { files, skipped, pathIdentityComplete };
|
|
908
954
|
}
|
|
909
955
|
|
|
910
956
|
function uint8ArrayToBase64(bytes) {
|
|
@@ -1035,11 +1081,10 @@
|
|
|
1035
1081
|
}
|
|
1036
1082
|
|
|
1037
1083
|
function fallbackNormalizeSafeProjectPath(value) {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
return '';
|
|
1041
|
-
|
|
1042
|
-
return text;
|
|
1084
|
+
if (typeof value !== 'string' || !value || value.length > 2048 || value.trim() !== value
|
|
1085
|
+
|| /[\u0000-\u001f\u007f]/.test(value) || value.startsWith('/')
|
|
1086
|
+
|| value.includes('\\') || /^[A-Za-z]:/.test(value)) return '';
|
|
1087
|
+
return value.split('/').some(part => !part || part === '.' || part === '..') ? '' : value;
|
|
1043
1088
|
}
|
|
1044
1089
|
|
|
1045
1090
|
return { create };
|