codex-overleaf-link 2.5.1 → 2.6.5
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 +149 -398
- package/README.zh-CN.md +153 -404
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +148 -3
- package/extension/src/backgroundSharedSessions.js +55 -2
- package/extension/src/backgroundUpdateCoordinator.js +128 -12
- package/extension/src/content/activeTurnControl.js +164 -7
- package/extension/src/content/composerAttachments.js +46 -10
- package/extension/src/content/composerPanel.js +92 -12
- package/extension/src/content/contentRuntime.js +1114 -321
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsController.js +77 -34
- package/extension/src/content/diagnosticsExport.js +1 -1
- package/extension/src/content/diagnosticsPanel.js +147 -42
- package/extension/src/content/generated/content.bundle.js +5761 -1237
- package/extension/src/content/generated/content.bundle.meta.json +6 -3
- package/extension/src/content/legacyGlobalRegistry.js +4 -0
- package/extension/src/content/localSkillsPanel.js +6 -4
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +53 -14
- package/extension/src/content/modelPicker.js +29 -19
- package/extension/src/content/nativeCompatibilityController.js +52 -19
- package/extension/src/content/otWarmMirror.js +1 -1
- package/extension/src/content/pageBridgeClient.js +7 -7
- package/extension/src/content/panelMaintenance.js +10 -2
- package/extension/src/content/panelRenderer.js +55 -28
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectSettingsCoordinator.js +30 -6
- package/extension/src/content/providerSettingsDialog.js +28 -19
- package/extension/src/content/recentProjects.js +30 -19
- package/extension/src/content/runActivitySummary.js +122 -14
- package/extension/src/content/runCompletionMeta.js +194 -6
- package/extension/src/content/runController.js +2 -1
- package/extension/src/content/runFailureNotice.js +8 -1
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +27 -13
- package/extension/src/content/runRecoveryActions.js +2 -1
- package/extension/src/content/runResultActions.js +73 -24
- package/extension/src/content/runReviewController.js +102 -37
- package/extension/src/content/runTimelineView.js +224 -25
- package/extension/src/content/scopedPersistencePanelState.js +124 -46
- package/extension/src/content/selectionContextView.js +30 -3
- package/extension/src/content/sensitivePreflight.js +14 -1
- package/extension/src/content/sessionManager.js +49 -3
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +15 -2
- package/extension/src/content/settingsWorkbench.js +127 -89
- package/extension/src/content/sharedSessionViewSync.js +13 -4
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateNotice.js +60 -6
- package/extension/src/content/writebackOrchestrator.js +301 -72
- package/extension/src/content/writingStyleSettings.js +70 -17
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/overleafCapabilities.js +4 -1
- package/extension/src/page/overleafEditor.js +12 -3
- package/extension/src/page/overleafProjectSnapshot.js +56 -3
- package/extension/src/page/snapshotRouter.js +75 -66
- package/extension/src/page/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +9 -1
- package/extension/src/page/treeOperations.js +200 -19
- package/extension/src/page/writebackRouter.js +172 -113
- package/extension/src/pageBridge.js +124 -73
- package/extension/src/popup.js +1 -1
- package/extension/src/shared/agentTranscript.js +372 -18
- package/extension/src/shared/auditRecords.js +3 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/compileAdapter.js +58 -17
- package/extension/src/shared/composerAttachmentDraft.js +27 -7
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/failureReasons.js +57 -3
- package/extension/src/shared/i18n.js +315 -29
- package/extension/src/shared/lineReferences.js +38 -31
- package/extension/src/shared/pageRpcContract.js +1 -1
- package/extension/src/shared/panelIcons.js +39 -0
- package/extension/src/shared/projectFiles.js +2 -1
- 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/scopedPersistenceBrowserAdapter.js +34 -0
- package/extension/src/shared/sessionState.js +63 -4
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/sharedSessionBridge.js +81 -13
- package/extension/src/shared/staleGuard.js +130 -63
- package/extension/src/shared/storageDb.js +35 -3
- package/extension/src/shared/storageMigration.js +111 -2
- package/extension/src/shared/storageRunActions.js +62 -8
- package/extension/src/shared/undoOperations.js +126 -28
- package/extension/src/shared/updateRuntimeIdentity.js +16 -2
- package/extension/src/shared/writebackSettlement.js +21 -3
- package/extension/styles/panel.css +1293 -549
- package/native-host/src/bootstrapUpdate.js +52 -16
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +10 -1
- package/native-host/src/chatCompletionsBridge.js +29 -2
- package/native-host/src/codexPromptAssembly.js +86 -118
- package/native-host/src/codexSessionRunner.js +127 -17
- package/native-host/src/debugLog.js +27 -3
- package/native-host/src/index.js +42 -11
- package/native-host/src/localSkills.js +1 -1
- package/native-host/src/mirrorWorkspace.js +47 -3
- package/native-host/src/nativeEnvironment.js +27 -5
- package/native-host/src/providerContextError.js +20 -0
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/skills/reviewer-response/SKILL.md +65 -0
- package/native-host/src/taskRunnerRuntime.js +28 -8
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/updateManager.js +144 -21
- package/native-host/src/writingStyleRuntime.js +1 -1
- package/package.json +5 -8
|
@@ -16,27 +16,70 @@
|
|
|
16
16
|
const snapshotCache = createKeyedCache();
|
|
17
17
|
const fileListCache = createKeyedCache();
|
|
18
18
|
|
|
19
|
+
function editorCaptureKey() {
|
|
20
|
+
const capture = deps.captureEditorDocument?.({ path: deps.getActiveFilePath?.(), projectId: deps.getProjectId?.() });
|
|
21
|
+
return capture?.ok ? capture : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function sameEditorCapture(left, right) {
|
|
25
|
+
return Boolean(left?.ok && right?.ok) && left.view === right.view && left.state === right.state && left.doc === right.doc
|
|
26
|
+
&& left.text === right.text && ['path', 'projectId', 'documentId', 'targetDocumentId']
|
|
27
|
+
.every(key => left.documentIdentity[key] === right.documentIdentity[key]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function hasEditorOverlay(snapshot) {
|
|
31
|
+
// Navigation reads carry the open method as their source. Only ZIP and
|
|
32
|
+
// document fetches are independent of the mounted editor.
|
|
33
|
+
return snapshot?.files?.some(file => typeof file.content === 'string'
|
|
34
|
+
&& !/^overleaf-(?:zip|doc-fetch)/.test(file.source || ''));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function hasNavigationReads(snapshot) {
|
|
38
|
+
return snapshot?.files?.some(file => typeof file.content === 'string' && file.source !== 'active-editor'
|
|
39
|
+
&& !/^overleaf-(?:zip|doc-fetch)/.test(file.source || ''));
|
|
40
|
+
}
|
|
41
|
+
|
|
19
42
|
async function getProjectSnapshot(params = {}) {
|
|
20
43
|
const cacheKey = getSnapshotCacheKey(params);
|
|
21
44
|
const maxAgeMs = normalizeSnapshotMaxAge(params.maxAgeMs);
|
|
22
45
|
const now = Date.now();
|
|
23
46
|
const cacheEntry = snapshotCache.entries.get(cacheKey);
|
|
47
|
+
const captureKey = editorCaptureKey();
|
|
48
|
+
const projectId = deps.getProjectId?.();
|
|
24
49
|
|
|
25
|
-
|
|
26
|
-
return
|
|
50
|
+
function currentSnapshot(snapshot) {
|
|
51
|
+
if (deps.getProjectId?.() !== projectId) return withoutEditorFiles(snapshot, true);
|
|
52
|
+
if (!hasEditorOverlay(snapshot) || params.allowEditorNavigation === true || hasNavigationReads(snapshot)) return snapshot;
|
|
53
|
+
const captures = deps.getSnapshotEditorCaptures?.(snapshot) || [];
|
|
54
|
+
if (captures.length === 1 && deps.isCaptureCurrent?.(captures[0])) return snapshot;
|
|
55
|
+
// Fallback captures can be returned once, but never reused from cache.
|
|
56
|
+
if (!snapshot.files.some(file => file.documentIdentity !== undefined)) return snapshot;
|
|
57
|
+
return withoutEditorFiles(snapshot);
|
|
27
58
|
}
|
|
28
59
|
|
|
29
|
-
if (!params.force && cacheEntry?.
|
|
60
|
+
if (!params.force && cacheEntry?.pending && (params.allowEditorNavigation === false || params.zipOnly || params.serverOnly)
|
|
61
|
+
&& (params.serverOnly === true || sameEditorCapture(cacheEntry.pendingCaptureKey, captureKey))) {
|
|
62
|
+
const snapshot = await cacheEntry.pending;
|
|
63
|
+
return currentSnapshot(snapshot);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!params.force && cacheEntry?.value && now - cacheEntry.capturedAt <= maxAgeMs
|
|
67
|
+
&& (!hasEditorOverlay(cacheEntry.value) || (params.allowEditorNavigation !== true
|
|
68
|
+
&& sameEditorCapture(cacheEntry.captureKey, captureKey)))) {
|
|
30
69
|
return withSnapshotCacheMetadata(cacheEntry.value, 'memory', cacheEntry.capturedAt);
|
|
31
70
|
}
|
|
32
71
|
|
|
33
72
|
const pending = deps.buildProjectSnapshot(params)
|
|
34
73
|
.then(snapshot => {
|
|
74
|
+
const captures = deps.getSnapshotEditorCaptures?.(snapshot) || [];
|
|
75
|
+
const finalCapture = captures.length === 1 ? captures[0] : null;
|
|
76
|
+
snapshot = currentSnapshot(snapshot);
|
|
35
77
|
const capturedAt = Date.now();
|
|
36
78
|
const currentEntry = snapshotCache.entries.get(cacheKey);
|
|
37
79
|
if (currentEntry?.pending === pending) {
|
|
38
80
|
snapshotCache.entries.set(cacheKey, {
|
|
39
81
|
value: snapshot,
|
|
82
|
+
captureKey: params.allowEditorNavigation === true || hasNavigationReads(snapshot) ? null : finalCapture,
|
|
40
83
|
capturedAt,
|
|
41
84
|
pending: null
|
|
42
85
|
});
|
|
@@ -52,12 +95,22 @@
|
|
|
52
95
|
|
|
53
96
|
snapshotCache.entries.set(cacheKey, {
|
|
54
97
|
value: cacheEntry?.value || null,
|
|
98
|
+
captureKey: cacheEntry?.captureKey,
|
|
99
|
+
pendingCaptureKey: captureKey,
|
|
55
100
|
capturedAt: cacheEntry?.capturedAt || 0,
|
|
56
101
|
pending
|
|
57
102
|
});
|
|
58
103
|
return pending;
|
|
59
104
|
}
|
|
60
105
|
|
|
106
|
+
function withoutEditorFiles(snapshot, projectChanged = false) {
|
|
107
|
+
const files = projectChanged ? [] : (snapshot?.files || []).filter(file => /^overleaf-(?:zip|doc-fetch)/.test(file.source || ''));
|
|
108
|
+
const removed = (snapshot?.files || []).filter(file => !files.includes(file));
|
|
109
|
+
return { ...snapshot, files, capabilities: { ...snapshot?.capabilities, fullProjectSnapshot: false,
|
|
110
|
+
requestedPathsComplete: false, skipped: [...(snapshot?.capabilities?.skipped || []),
|
|
111
|
+
...removed.map(file => ({ path: file.path, reason: 'document_identity_mismatch' }))] } };
|
|
112
|
+
}
|
|
113
|
+
|
|
61
114
|
async function getProjectFileList(params = {}) {
|
|
62
115
|
const cacheKey = getProjectFileListCacheKey(params);
|
|
63
116
|
const maxAgeMs = normalizeFileListMaxAge(params.maxAgeMs);
|
|
@@ -17,12 +17,22 @@
|
|
|
17
17
|
const normalizeSafeProjectPath = typeof deps.normalizePath === 'function'
|
|
18
18
|
? deps.normalizePath
|
|
19
19
|
: fallbackNormalizeSafeProjectPath;
|
|
20
|
-
const readActiveEditorText = typeof deps.readActiveEditorText === 'function'
|
|
21
|
-
? deps.readActiveEditorText
|
|
22
|
-
: () => '';
|
|
23
20
|
let invalidatedAt = 0;
|
|
24
21
|
let preferredZipEndpoint = '';
|
|
25
22
|
const writebackReads = new Set();
|
|
23
|
+
const editorCaptures = new WeakMap();
|
|
24
|
+
|
|
25
|
+
function editorFile(path, ready, source) {
|
|
26
|
+
if (ready.documentIdentity && ready.capture) editorCaptures.set(ready.documentIdentity, ready.capture);
|
|
27
|
+
return { path, content: ready.text, source, kind: 'text',
|
|
28
|
+
...(ready.documentIdentity !== undefined ? { documentIdentity: ready.documentIdentity } : {}) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getSnapshotEditorCaptures(snapshot) {
|
|
32
|
+
return (snapshot?.files || []).filter(file => typeof file.content === 'string'
|
|
33
|
+
&& !/^overleaf-(?:zip|doc-fetch)/.test(file.source || ''))
|
|
34
|
+
.map(file => editorCaptures.get(file.documentIdentity));
|
|
35
|
+
}
|
|
26
36
|
|
|
27
37
|
function getProjectId() {
|
|
28
38
|
return treeOperations.getProjectId?.() || null;
|
|
@@ -214,6 +224,18 @@
|
|
|
214
224
|
}
|
|
215
225
|
|
|
216
226
|
async function buildProjectSnapshot(params = {}) {
|
|
227
|
+
const projectId = params.projectId || getProjectId();
|
|
228
|
+
if (!projectId || projectId !== getProjectId()) return projectChangedSnapshot(projectId);
|
|
229
|
+
const snapshot = await buildProjectSnapshotForProject({ ...params, projectId });
|
|
230
|
+
return getProjectId() === projectId ? snapshot : projectChangedSnapshot(projectId);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function projectChangedSnapshot(projectId) {
|
|
234
|
+
return { ok: false, id: projectId, files: [], capabilities: { fullProjectSnapshot: false,
|
|
235
|
+
skipped: [{ path: '', reason: 'document_identity_mismatch' }] } };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function buildProjectSnapshotForProject(params = {}) {
|
|
217
239
|
if (params.zipOnly || params.serverOnly === true) {
|
|
218
240
|
return buildZipOnlyProjectSnapshot(params);
|
|
219
241
|
}
|
|
@@ -242,9 +264,18 @@
|
|
|
242
264
|
|
|
243
265
|
const zipSnapshot = await fetchProjectZipSnapshot(params);
|
|
244
266
|
if (zipSnapshot.ok && zipSnapshot.files.length) {
|
|
267
|
+
const skipped = [...(zipSnapshot.skipped || [])];
|
|
268
|
+
const overlays = [...(lightweightSnapshot?.files || [])];
|
|
269
|
+
for (let index = 0; index < overlays.length; index++) {
|
|
270
|
+
if (overlays[index].source !== 'active-editor') continue;
|
|
271
|
+
const captured = await readActiveEditorOverlay(overlays[index].path, zipSnapshot.files, { projectId: params.projectId });
|
|
272
|
+
skipped.push(...captured.skipped);
|
|
273
|
+
overlays.splice(index, 1, ...captured.files);
|
|
274
|
+
if (!captured.files.length) index--;
|
|
275
|
+
}
|
|
245
276
|
const mergedFiles = mergeSnapshotFiles(
|
|
246
277
|
zipSnapshot.files,
|
|
247
|
-
filterZipSnapshotOverlayFiles(zipSnapshot.files,
|
|
278
|
+
filterZipSnapshotOverlayFiles(zipSnapshot.files, overlays, skipped)
|
|
248
279
|
);
|
|
249
280
|
const files = filterRequestedSnapshotFiles(mergedFiles, params, activePath);
|
|
250
281
|
const requestedPathsComplete = params.restrictToRequestedPathsOnly === true
|
|
@@ -261,7 +292,7 @@
|
|
|
261
292
|
contentComplete: !(zipSnapshot.skipped || []).length,
|
|
262
293
|
requestedPathsComplete,
|
|
263
294
|
method: 'overleaf-zip',
|
|
264
|
-
skipped
|
|
295
|
+
skipped,
|
|
265
296
|
diagnostics: {
|
|
266
297
|
zipEndpoint: zipSnapshot.endpoint,
|
|
267
298
|
zipTiming: zipSnapshot.diagnostics,
|
|
@@ -309,15 +340,16 @@
|
|
|
309
340
|
snapshotActivePath,
|
|
310
341
|
...(Array.isArray(params.focusFiles) ? params.focusFiles : [])
|
|
311
342
|
];
|
|
343
|
+
const enumeratedPaths = params.restrictToRequestedPathsOnly === true
|
|
344
|
+
? []
|
|
345
|
+
: collectProjectTextPaths(snapshotActivePath);
|
|
312
346
|
const projectPaths = window.CodexOverleafProjectFiles.collectUniqueTextPaths(
|
|
313
|
-
|
|
314
|
-
? requestedPaths
|
|
315
|
-
: [
|
|
316
|
-
...requestedPaths,
|
|
317
|
-
...collectProjectTextPaths(snapshotActivePath)
|
|
318
|
-
],
|
|
347
|
+
[...requestedPaths, ...enumeratedPaths],
|
|
319
348
|
80
|
|
320
349
|
);
|
|
350
|
+
const enumerationTruncated = projectPaths.truncated === true
|
|
351
|
+
|| enumeratedPaths.truncated === true
|
|
352
|
+
|| internalDocRecords.truncated === true;
|
|
321
353
|
const files = [];
|
|
322
354
|
const skipped = [...(zipSnapshot?.skipped || [])];
|
|
323
355
|
const ambiguousPaths = new Set(skipped.filter(file => file.reason === 'zip_path_duplicate').map(file => file.path));
|
|
@@ -332,7 +364,7 @@
|
|
|
332
364
|
for (const filePath of projectPaths) {
|
|
333
365
|
if (ambiguousPaths.has(filePath)) continue;
|
|
334
366
|
if (filePath === snapshotActivePath) {
|
|
335
|
-
const ready = await waitForActiveEditorText(filePath, 5000);
|
|
367
|
+
const ready = await waitForActiveEditorText(filePath, 5000, { allowEmpty: true, projectId: params.projectId });
|
|
336
368
|
if (!ready.ok) {
|
|
337
369
|
skipped.push({
|
|
338
370
|
path: filePath,
|
|
@@ -340,18 +372,14 @@
|
|
|
340
372
|
});
|
|
341
373
|
continue;
|
|
342
374
|
}
|
|
343
|
-
files.push(
|
|
344
|
-
path: filePath,
|
|
345
|
-
content: ready.text,
|
|
346
|
-
source: 'active-editor'
|
|
347
|
-
});
|
|
375
|
+
files.push(editorFile(filePath, ready, 'active-editor'));
|
|
348
376
|
previousSignature = contentSignature(ready.text);
|
|
349
377
|
continue;
|
|
350
378
|
}
|
|
351
379
|
|
|
352
380
|
const docRecord = docRecordByPath.get(filePath);
|
|
353
381
|
if (docRecord) {
|
|
354
|
-
const fetched = await fetchOverleafDocContent(docRecord);
|
|
382
|
+
const fetched = await fetchOverleafDocContent(docRecord, params.projectId);
|
|
355
383
|
if (fetched.ok) {
|
|
356
384
|
files.push({
|
|
357
385
|
path: filePath,
|
|
@@ -365,6 +393,7 @@
|
|
|
365
393
|
path: filePath,
|
|
366
394
|
reason: fetched.reason
|
|
367
395
|
});
|
|
396
|
+
if (fetched.reason === 'document_identity_mismatch') continue;
|
|
368
397
|
}
|
|
369
398
|
|
|
370
399
|
if (!canOpenInactiveFileForSnapshot(filePath, params, lightweightOnly, docRecord)) {
|
|
@@ -386,7 +415,9 @@
|
|
|
386
415
|
|
|
387
416
|
await waitForActiveFile(filePath, 5000);
|
|
388
417
|
const ready = await waitForActiveEditorText(filePath, 7000, {
|
|
389
|
-
notSignature: previousSignature
|
|
418
|
+
notSignature: previousSignature,
|
|
419
|
+
projectId: params.projectId,
|
|
420
|
+
allowEmpty: true
|
|
390
421
|
});
|
|
391
422
|
if (!ready.ok) {
|
|
392
423
|
skipped.push({
|
|
@@ -395,11 +426,7 @@
|
|
|
395
426
|
});
|
|
396
427
|
continue;
|
|
397
428
|
}
|
|
398
|
-
files.push(
|
|
399
|
-
path: filePath,
|
|
400
|
-
content: ready.text,
|
|
401
|
-
source: opened.method || 'file-tree-open'
|
|
402
|
-
});
|
|
429
|
+
files.push(editorFile(filePath, ready, opened.method || 'file-tree-open'));
|
|
403
430
|
previousSignature = contentSignature(ready.text);
|
|
404
431
|
}
|
|
405
432
|
|
|
@@ -409,14 +436,6 @@
|
|
|
409
436
|
await waitForActiveEditorText(activePath, 5000);
|
|
410
437
|
}
|
|
411
438
|
|
|
412
|
-
if (snapshotActivePath && !ambiguousPaths.has(snapshotActivePath) && !files.length && (!lightweightOnly || params.allowZipFallback === false)) {
|
|
413
|
-
files.push({
|
|
414
|
-
path: snapshotActivePath,
|
|
415
|
-
content: readActiveEditorText(),
|
|
416
|
-
source: 'active-editor'
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
|
|
420
439
|
const requestedPathsComplete = isCompleteProjectSnapshot(files, projectPaths);
|
|
421
440
|
return {
|
|
422
441
|
id: getProjectId(),
|
|
@@ -424,7 +443,7 @@
|
|
|
424
443
|
activePath: snapshotActivePath,
|
|
425
444
|
files,
|
|
426
445
|
capabilities: {
|
|
427
|
-
fullProjectSnapshot: params.restrictToRequestedPathsOnly !== true && requestedPathsComplete && zipSnapshot?.pathIdentityComplete !== false,
|
|
446
|
+
fullProjectSnapshot: params.restrictToRequestedPathsOnly !== true && !enumerationTruncated && requestedPathsComplete && zipSnapshot?.pathIdentityComplete !== false,
|
|
428
447
|
pathIdentityComplete: zipSnapshot?.pathIdentityComplete,
|
|
429
448
|
requestedPathsComplete,
|
|
430
449
|
method: resolvePageSnapshotMethod(files, lightweightOnly),
|
|
@@ -444,11 +463,11 @@
|
|
|
444
463
|
|
|
445
464
|
async function buildZipOnlyProjectSnapshot(params = {}) {
|
|
446
465
|
const activePath = getPassiveActiveFilePath();
|
|
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);
|
|
450
|
-
|
|
451
466
|
const zipSnapshot = await fetchProjectZipSnapshot(params);
|
|
467
|
+
const captured = params.serverOnly === true ? { files: [], skipped: [] }
|
|
468
|
+
: await readActiveEditorOverlay(activePath, zipSnapshot.ok ? zipSnapshot.files : null, { passive: true, projectId: params.projectId });
|
|
469
|
+
const activeOverlay = captured.files;
|
|
470
|
+
const skipped = [...(zipSnapshot.skipped || []), ...captured.skipped];
|
|
452
471
|
if (zipSnapshot.ok && zipSnapshot.files.length) {
|
|
453
472
|
const files = filterRequestedSnapshotFiles(
|
|
454
473
|
mergeSnapshotFiles(zipSnapshot.files, filterZipSnapshotOverlayFiles(zipSnapshot.files, activeOverlay, zipSnapshot.skipped)),
|
|
@@ -469,7 +488,7 @@
|
|
|
469
488
|
contentComplete: !(zipSnapshot.skipped || []).length,
|
|
470
489
|
requestedPathsComplete,
|
|
471
490
|
method: 'overleaf-zip',
|
|
472
|
-
skipped
|
|
491
|
+
skipped,
|
|
473
492
|
diagnostics: {
|
|
474
493
|
zipEndpoint: zipSnapshot.endpoint,
|
|
475
494
|
zipTiming: zipSnapshot.diagnostics,
|
|
@@ -494,7 +513,7 @@
|
|
|
494
513
|
? 'active-editor-zip-only-fallback'
|
|
495
514
|
: 'zip-only-fallback-empty',
|
|
496
515
|
contentComplete: false,
|
|
497
|
-
skipped: [...
|
|
516
|
+
skipped: [...skipped, {
|
|
498
517
|
path: 'project.zip',
|
|
499
518
|
reason: zipSnapshot.reason || 'ZIP file identities were incomplete'
|
|
500
519
|
}],
|
|
@@ -531,32 +550,15 @@
|
|
|
531
550
|
return (files || []).filter(file => requestedSet.has(file?.path));
|
|
532
551
|
}
|
|
533
552
|
|
|
534
|
-
function readActiveEditorOverlay(activePath) {
|
|
535
|
-
if (!activePath) return [];
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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) {
|
|
558
|
-
return [];
|
|
559
|
-
}
|
|
553
|
+
async function readActiveEditorOverlay(activePath, referenceFiles, options = {}) {
|
|
554
|
+
if (!activePath) return { files: [], skipped: [] };
|
|
555
|
+
const references = referenceFiles ? new Map(referenceFiles
|
|
556
|
+
.filter(file => file.kind !== 'binary' && typeof file.content === 'string')
|
|
557
|
+
.map(file => [file.path, file.content])) : null;
|
|
558
|
+
const ready = await waitForActiveEditorText(activePath, 5000, { ...options, allowEmpty: true, references });
|
|
559
|
+
return ready.ok
|
|
560
|
+
? { files: [editorFile(activePath, ready, 'active-editor')], skipped: [] }
|
|
561
|
+
: { files: [], skipped: [{ path: activePath, reason: ready.code || ready.reason }] };
|
|
560
562
|
}
|
|
561
563
|
|
|
562
564
|
function mergeSnapshotFiles(primaryFiles = [], overrideFiles = []) {
|
|
@@ -609,7 +611,7 @@
|
|
|
609
611
|
return projectPaths.every(filePath => capturedPaths.has(filePath));
|
|
610
612
|
}
|
|
611
613
|
|
|
612
|
-
async function fetchOverleafDocContent(record) {
|
|
614
|
+
async function fetchOverleafDocContent(record, expectedProjectId = getProjectId()) {
|
|
613
615
|
if (!record?.id || !record.path) {
|
|
614
616
|
return {
|
|
615
617
|
ok: false,
|
|
@@ -625,6 +627,9 @@
|
|
|
625
627
|
};
|
|
626
628
|
}
|
|
627
629
|
|
|
630
|
+
const target = treeOperations.resolveTargetDocument?.(record.path, { projectId });
|
|
631
|
+
if (projectId !== expectedProjectId || (target && (target.status !== 'known' || target.documentId !== record.id.toLowerCase())))
|
|
632
|
+
return { ok: false, reason: 'document_identity_mismatch' };
|
|
628
633
|
try {
|
|
629
634
|
const response = await fetch(`${window.location.origin}/project/${encodeURIComponent(projectId)}/doc/${encodeURIComponent(record.id)}`, {
|
|
630
635
|
credentials: 'include',
|
|
@@ -639,6 +644,9 @@
|
|
|
639
644
|
};
|
|
640
645
|
}
|
|
641
646
|
const payload = await response.json();
|
|
647
|
+
const currentTarget = treeOperations.resolveTargetDocument?.(record.path, { projectId });
|
|
648
|
+
if (getProjectId() !== projectId || (target && (currentTarget?.status !== 'known' || currentTarget.documentId !== target.documentId)))
|
|
649
|
+
return { ok: false, reason: 'document_identity_mismatch' };
|
|
642
650
|
const content = readDocPayloadContent(payload);
|
|
643
651
|
if (!window.CodexOverleafProjectFiles.isUsableProjectFileContent(content)) {
|
|
644
652
|
return {
|
|
@@ -1071,6 +1079,7 @@
|
|
|
1071
1079
|
assembleSnapshot,
|
|
1072
1080
|
buildProjectFileList,
|
|
1073
1081
|
buildProjectSnapshot,
|
|
1082
|
+
getSnapshotEditorCaptures,
|
|
1074
1083
|
fetchProjectZipSnapshot,
|
|
1075
1084
|
getInvalidatedAt,
|
|
1076
1085
|
getRequestedSnapshotPaths,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
function resolveJumpToPositionRange(params, text, filePath) {
|
|
9
|
-
if (params.line === undefined && params.column === undefined) {
|
|
9
|
+
if (params.line === undefined && params.column === undefined && params.endLine === undefined) {
|
|
10
10
|
return {
|
|
11
11
|
ok: true,
|
|
12
12
|
from: params.from,
|
|
@@ -33,6 +33,22 @@
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
if (params.endLine !== undefined) {
|
|
37
|
+
const endLineNumber = Number(params.endLine);
|
|
38
|
+
if (!Number.isInteger(endLineNumber) || endLineNumber < lineNumber) {
|
|
39
|
+
return jumpPositionOutOfRange('line_out_of_range', filePath, {
|
|
40
|
+
line: lineNumber,
|
|
41
|
+
endLine: Number.isInteger(endLineNumber) ? endLineNumber : params.endLine,
|
|
42
|
+
lineCount
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
ok: true,
|
|
47
|
+
from: line.start,
|
|
48
|
+
to: lines[Math.min(endLineNumber, lineCount) - 1].end
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
36
52
|
const lineLength = line.end - line.start;
|
|
37
53
|
if (params.selectLine === true) {
|
|
38
54
|
return {
|
|
@@ -108,9 +108,14 @@
|
|
|
108
108
|
}
|
|
109
109
|
function verificationFailure(error, changedDocument, phase) {
|
|
110
110
|
const interrupted = ['codex_cancelled', 'aborted_project_changed'].includes(error?.code);
|
|
111
|
-
|
|
111
|
+
// Only a failed server read means the project could not be checked; any
|
|
112
|
+
// other error before the upload came from the Overleaf file-tree controls.
|
|
113
|
+
const treeControls = !interrupted && !changedDocument && error?.code !== 'source_zip_unavailable';
|
|
114
|
+
const code = interrupted ? error.code : changedDocument ? 'file_upload_unconfirmed'
|
|
115
|
+
: treeControls ? 'file_tree_controls_unavailable' : 'source_zip_unavailable';
|
|
112
116
|
const reason = interrupted ? error.message : changedDocument
|
|
113
117
|
? 'The file was submitted, but its saved content could not be confirmed.'
|
|
118
|
+
: treeControls ? 'Codex could not safely perform this file operation with the available Overleaf controls.'
|
|
114
119
|
: 'The project could not be checked before uploading this file.';
|
|
115
120
|
return { ok: false, code, reason, changedDocument, diagnostics: {
|
|
116
121
|
verificationPhase: phase, ...(error?.diagnostics || {}), reason: error?.technicalMessage || error?.message || ''
|
|
@@ -610,6 +615,9 @@
|
|
|
610
615
|
async function ensureWriteMode(reviewing, params = {}) {
|
|
611
616
|
const ensureMode = reviewing ? deps.ensureReviewing : deps.ensureEditing;
|
|
612
617
|
if (typeof ensureMode !== 'function') return { ok: false, reason: 'Editor mode controls are unavailable.' };
|
|
618
|
+
// An open paywall can make the underlying editor inert. Detect it before
|
|
619
|
+
// preparation attempts any file navigation behind that modal.
|
|
620
|
+
if (reviewing && deps.findUpgradeModal?.()) return ensureMode(params);
|
|
613
621
|
const prepared = await prepareEditor(params);
|
|
614
622
|
return prepared.ok ? ensureMode(params) : prepared;
|
|
615
623
|
}
|