codex-overleaf-link 2.0.0 → 2.1.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 +24 -23
- package/extension/bootstrap/manifest.template.json +2 -1
- package/extension/runtime-manifest.json +12 -6
- package/extension/src/background.js +7 -0
- package/extension/src/content/composerPanel.js +13 -3
- package/extension/src/content/contentRuntime.js +129 -64
- package/extension/src/content/contextTray.js +173 -9
- package/extension/src/content/markdownText.js +16 -1
- package/extension/src/content/mathText.js +233 -0
- package/extension/src/content/modelPicker.js +117 -106
- package/extension/src/content/modelPickerSupport.js +115 -0
- package/extension/src/content/nativeChannel.js +20 -1
- package/extension/src/content/panelMaintenance.js +2 -0
- package/extension/src/content/projectSettingsCoordinator.js +2 -0
- package/extension/src/content/providerSettingsCoordinator.js +413 -0
- package/extension/src/content/providerSettingsDialog.js +981 -0
- package/extension/src/content/runTimelineView.js +24 -5
- package/extension/src/content/settingsPanel.js +33 -2
- package/extension/src/content/writebackOrchestrator.js +3 -1
- package/extension/src/page/overleafProjectSnapshot.js +29 -33
- package/extension/src/page/snapshotRouter.js +75 -8
- package/extension/src/page/writebackRouter.js +20 -33
- package/extension/src/pageBridge.js +1 -1
- package/extension/src/shared/agentTranscript.js +130 -0
- package/extension/src/shared/compatibility.js +3 -2
- package/extension/src/shared/failureReasons.js +75 -0
- package/extension/src/shared/i18n.js +22 -0
- package/extension/src/shared/models.js +1 -0
- package/extension/src/shared/providerProfiles.js +283 -0
- package/extension/src/shared/sessionState.js +12 -2
- package/extension/src/shared/storageDb.js +1 -1
- package/extension/styles/panel.css +587 -1
- package/extension/vendor/katex/LICENSE +21 -0
- package/extension/vendor/katex/VERSION +2 -0
- package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/extension/vendor/katex/katex.min.css +1 -0
- package/extension/vendor/katex/katex.min.js +1 -0
- package/native-host/src/anthropicBridgeRequest.js +435 -0
- package/native-host/src/anthropicBridgeResponse.js +431 -0
- package/native-host/src/anthropicMessagesBridge.js +281 -0
- package/native-host/src/chatBridgeRequest.js +274 -0
- package/native-host/src/chatBridgeResponse.js +594 -0
- package/native-host/src/chatCompletionsBridge.js +485 -0
- package/native-host/src/codexCommand.js +25 -0
- package/native-host/src/codexPromptAssembly.js +9 -0
- package/native-host/src/codexProviderLaunch.js +213 -0
- package/native-host/src/codexProviderTest.js +196 -0
- package/native-host/src/codexSessionRunner.js +29 -25
- package/native-host/src/managedInstall.js +1 -0
- package/native-host/src/providerBridgeRoutes.js +11 -0
- package/native-host/src/providerModelCatalog.js +107 -0
- package/native-host/src/providerProfile.js +454 -0
- package/native-host/src/providerReasoning.js +235 -0
- package/native-host/src/providerRedaction.js +18 -0
- package/native-host/src/providerRuntime.js +365 -0
- package/native-host/src/providerStore.js +691 -0
- package/native-host/src/readProgressGuard.js +360 -0
- package/native-host/src/taskRunnerRuntime.js +39 -3
- package/native-host/src/updateArchive.js +1 -0
- package/package.json +2 -1
|
@@ -480,7 +480,11 @@
|
|
|
480
480
|
existing.dataset.streamRole = event.streamRole || '';
|
|
481
481
|
const text = existing.querySelector('[data-stream-text]');
|
|
482
482
|
if (text) {
|
|
483
|
-
|
|
483
|
+
if (event.streamRole === 'assistant') {
|
|
484
|
+
renderMarkdownBlockText(text, event.title || '');
|
|
485
|
+
} else {
|
|
486
|
+
renderMarkdownInlineText(text, event.title || '');
|
|
487
|
+
}
|
|
484
488
|
}
|
|
485
489
|
return;
|
|
486
490
|
}
|
|
@@ -498,7 +502,11 @@
|
|
|
498
502
|
const text = document.createElement('div');
|
|
499
503
|
text.className = 'run-stream-text';
|
|
500
504
|
text.dataset.streamText = '';
|
|
501
|
-
|
|
505
|
+
if (event.streamRole === 'assistant') {
|
|
506
|
+
renderMarkdownBlockText(text, event.title || '');
|
|
507
|
+
} else {
|
|
508
|
+
renderMarkdownInlineText(text, event.title || '');
|
|
509
|
+
}
|
|
502
510
|
|
|
503
511
|
row.append(text);
|
|
504
512
|
return row;
|
|
@@ -727,9 +735,7 @@
|
|
|
727
735
|
main.className = 'run-final-answer';
|
|
728
736
|
const mainSections = [];
|
|
729
737
|
if (structured.conclusion) {
|
|
730
|
-
mainSections.push(
|
|
731
|
-
? `结论:${structured.conclusion}`
|
|
732
|
-
: `Conclusion: ${structured.conclusion}`);
|
|
738
|
+
mainSections.push(formatConclusionMarkdown(structured.conclusion));
|
|
733
739
|
}
|
|
734
740
|
if (structured.body) {
|
|
735
741
|
mainSections.push(structured.body);
|
|
@@ -764,6 +770,19 @@
|
|
|
764
770
|
return report;
|
|
765
771
|
}
|
|
766
772
|
|
|
773
|
+
function formatConclusionMarkdown(value) {
|
|
774
|
+
const conclusion = String(value || '');
|
|
775
|
+
const label = getLocale() === 'zh' ? '结论:' : 'Conclusion:';
|
|
776
|
+
return hasLeadingMarkdownBlock(conclusion)
|
|
777
|
+
? `${label}\n${conclusion}`
|
|
778
|
+
: `${label} ${conclusion}`;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function hasLeadingMarkdownBlock(value) {
|
|
782
|
+
const firstLine = String(value || '').trimStart().split(/\r?\n/, 1)[0];
|
|
783
|
+
return /^(?:#{1,6}\s+\S|```|~~~|(?:[-+*]|\d+[.)])\s+\S|\*\*[^*]+\*\*:?\s*$)/.test(firstLine);
|
|
784
|
+
}
|
|
785
|
+
|
|
767
786
|
// For failure codes that have an actionable recovery path the user can
|
|
768
787
|
// invoke directly from the run card, append a button inside the completion
|
|
769
788
|
// report. Today's only consumer is codex_project_locked → "Force-release
|
|
@@ -147,12 +147,29 @@
|
|
|
147
147
|
<p class="codex-set-row-help" data-i18n="languageHelp">Panel display language.</p>
|
|
148
148
|
</div>
|
|
149
149
|
</details>
|
|
150
|
+
<details class="codex-set-group" data-set-group="providers" open>
|
|
151
|
+
<summary class="codex-set-group-head">
|
|
152
|
+
<span class="codex-set-group-title">${codexSetIcon('bolt')}<span data-i18n="providerSettingsTitle">Model providers</span><span class="codex-provider-experimental-badge" data-i18n="providerSettingsExperimental">Third-party experimental</span></span>
|
|
153
|
+
</summary>
|
|
154
|
+
<div class="codex-set-card codex-provider-settings-entry">
|
|
155
|
+
<div class="codex-provider-settings-entry-copy">
|
|
156
|
+
<p class="codex-set-row-help" data-i18n="providerSettingsHelp">Configure experimental third-party model APIs. Compatibility varies by provider and gateway.</p>
|
|
157
|
+
<p class="codex-provider-settings-summary" data-provider-settings-summary data-tone="ok" data-i18n="providerSettingsBuiltin">Built-in Codex is active</p>
|
|
158
|
+
</div>
|
|
159
|
+
<button type="button" class="codex-set-btn" data-provider-settings-open data-i18n="providerSettingsConfigure">Configure</button>
|
|
160
|
+
</div>
|
|
161
|
+
</details>
|
|
150
162
|
<details class="codex-set-group" data-set-group="updates" open>
|
|
151
163
|
<summary class="codex-set-group-head">
|
|
152
164
|
<span class="codex-set-group-title">${codexSetIcon('software')}<span data-i18n="softwareUpdatesTitle">Software updates</span></span>
|
|
153
165
|
</summary>
|
|
154
166
|
<div class="codex-set-card" data-update-settings>
|
|
155
167
|
<div class="codex-set-row">
|
|
168
|
+
<label class="codex-project-settings-row codex-project-settings-row--switch">
|
|
169
|
+
<span class="codex-project-settings-row-label" data-i18n="preloadProjectContextTitle">Preload project context</span>
|
|
170
|
+
<input type="checkbox" class="codex-switch" data-preload-project-context>
|
|
171
|
+
</label>
|
|
172
|
+
<p class="codex-set-row-help" data-i18n="preloadProjectContextDescription">Fetch the exact project file list in the background after an Overleaf project becomes idle.</p>
|
|
156
173
|
<p class="codex-set-row-help" data-update-settings-summary data-i18n="softwareUpdatesHelp">Automatically checks signed stable releases for the Extension and Native Host.</p>
|
|
157
174
|
<p class="codex-set-note" data-update-settings-state aria-live="polite" hidden></p>
|
|
158
175
|
<button type="button" class="codex-set-btn" data-check-updates data-i18n="checkForUpdates">Check for updates</button>
|
|
@@ -211,6 +228,7 @@
|
|
|
211
228
|
container.querySelector('[data-settings-back]')?.addEventListener('click', () => instance.callbacks.onBack?.());
|
|
212
229
|
container.querySelector('[data-skills-back]')?.addEventListener('click', () => instance.callbacks.onSkillsBack?.());
|
|
213
230
|
container.querySelector('[data-skills-entry]')?.addEventListener('click', () => instance.callbacks.onSkillsOpen?.());
|
|
231
|
+
container.querySelector('[data-provider-settings-open]')?.addEventListener('click', () => instance.callbacks.onProvidersOpen?.());
|
|
214
232
|
container.querySelector('[data-clear-all-history]')?.addEventListener('click', () => instance.callbacks.onClearAllHistory?.());
|
|
215
233
|
container.querySelector('[data-history-card]')?.addEventListener('toggle', event => {
|
|
216
234
|
if (event.target.open) {
|
|
@@ -230,7 +248,7 @@
|
|
|
230
248
|
flashSaved(instance, event);
|
|
231
249
|
});
|
|
232
250
|
// Governance and skill fields: auto-save on change and input for immediate response.
|
|
233
|
-
for (const selector of ['[data-governance-readonly-patterns]', '[data-governance-writable-patterns]', '[data-sensitive-check-enabled]', '[data-sensitive-confirm-allowed]', '[data-load-codex-local-skills]', '[data-load-codex-overleaf-skills]', '[data-theme-select]', '[data-language-select]']) {
|
|
251
|
+
for (const selector of ['[data-governance-readonly-patterns]', '[data-governance-writable-patterns]', '[data-sensitive-check-enabled]', '[data-sensitive-confirm-allowed]', '[data-preload-project-context]', '[data-load-codex-local-skills]', '[data-load-codex-overleaf-skills]', '[data-theme-select]', '[data-language-select]']) {
|
|
234
252
|
const element = container.querySelector(selector);
|
|
235
253
|
element?.addEventListener?.('change', event => {
|
|
236
254
|
instance.callbacks.onInputChange?.(event);
|
|
@@ -254,6 +272,7 @@
|
|
|
254
272
|
setStatus: (text, status) => setStatus(instance, text, status),
|
|
255
273
|
clearStatus: () => clearStatus(instance),
|
|
256
274
|
setSkillsSummary: text => setSkillsSummary(instance, text),
|
|
275
|
+
setProviderSummary: summary => setProviderSummary(instance, summary),
|
|
257
276
|
refreshNotes: () => updateGovernanceNotes(instance),
|
|
258
277
|
destroy: () => destroy(instance),
|
|
259
278
|
_instance: instance
|
|
@@ -328,6 +347,7 @@
|
|
|
328
347
|
// shared container, so query that instead of a single screen root.
|
|
329
348
|
const scope = instance?.container || root;
|
|
330
349
|
const toggles = state.skillToggles || {};
|
|
350
|
+
setChecked(scope, '[data-preload-project-context]', toggles.preloadProjectContext !== false);
|
|
331
351
|
setChecked(scope, '[data-load-codex-local-skills]', toggles.loadCodexLocalSkills !== false);
|
|
332
352
|
setChecked(scope, '[data-load-codex-overleaf-skills]', toggles.loadCodexOverleafSkills !== false);
|
|
333
353
|
setValue(scope, '[data-theme-select]', state.theme || 'dark');
|
|
@@ -349,6 +369,7 @@
|
|
|
349
369
|
sensitiveConfirmAllowed: root?.querySelector('[data-sensitive-confirm-allowed]')?.checked === true
|
|
350
370
|
},
|
|
351
371
|
skillToggles: {
|
|
372
|
+
preloadProjectContext: scope?.querySelector('[data-preload-project-context]')?.checked !== false,
|
|
352
373
|
loadCodexLocalSkills: scope?.querySelector('[data-load-codex-local-skills]')?.checked !== false,
|
|
353
374
|
loadCodexOverleafSkills: scope?.querySelector('[data-load-codex-overleaf-skills]')?.checked !== false
|
|
354
375
|
},
|
|
@@ -467,6 +488,15 @@
|
|
|
467
488
|
}
|
|
468
489
|
}
|
|
469
490
|
|
|
491
|
+
function setProviderSummary(instance, value = {}) {
|
|
492
|
+
const element = instance?.container?.querySelector('[data-provider-settings-summary]');
|
|
493
|
+
if (!element) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
element.textContent = typeof value === 'string' ? value : value.summary || '';
|
|
497
|
+
element.dataset.tone = typeof value === 'object' && value.tone ? value.tone : 'ok';
|
|
498
|
+
}
|
|
499
|
+
|
|
470
500
|
// Collapse memory: card open/closed state persists per browser profile.
|
|
471
501
|
const GROUP_STORE_KEY = 'codexOverleafSettingsGroups';
|
|
472
502
|
|
|
@@ -535,6 +565,7 @@
|
|
|
535
565
|
isVisible,
|
|
536
566
|
setStatus,
|
|
537
567
|
clearStatus,
|
|
538
|
-
setSkillsSummary
|
|
568
|
+
setSkillsSummary,
|
|
569
|
+
setProviderSummary
|
|
539
570
|
};
|
|
540
571
|
})();
|
|
@@ -544,7 +544,9 @@
|
|
|
544
544
|
status: 'running'
|
|
545
545
|
});
|
|
546
546
|
|
|
547
|
-
await callPageBridge('invalidateProjectSnapshot', {
|
|
547
|
+
await callPageBridge('invalidateProjectSnapshot', {
|
|
548
|
+
invalidateFileList: true
|
|
549
|
+
});
|
|
548
550
|
const freshProject = sanitizeRunProjectSnapshot(await callPageBridge('getProjectSnapshot', {
|
|
549
551
|
force: true,
|
|
550
552
|
maxAgeMs: 0,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
function create(deps = {}) {
|
|
15
15
|
const pageWindow = deps.window || window;
|
|
16
16
|
const snapshotCache = createKeyedCache();
|
|
17
|
-
const fileListCache =
|
|
17
|
+
const fileListCache = createKeyedCache();
|
|
18
18
|
|
|
19
19
|
async function getProjectSnapshot(params = {}) {
|
|
20
20
|
const cacheKey = getSnapshotCacheKey(params);
|
|
@@ -62,41 +62,53 @@
|
|
|
62
62
|
const cacheKey = getProjectFileListCacheKey(params);
|
|
63
63
|
const maxAgeMs = normalizeFileListMaxAge(params.maxAgeMs);
|
|
64
64
|
const now = Date.now();
|
|
65
|
+
const cacheEntry = fileListCache.entries.get(cacheKey);
|
|
65
66
|
|
|
66
|
-
if (
|
|
67
|
-
return
|
|
67
|
+
if (cacheEntry?.pending) {
|
|
68
|
+
return cacheEntry.pending;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
if (!params.force &&
|
|
71
|
-
return withFileListCacheMetadata(
|
|
71
|
+
if (!params.force && cacheEntry?.value && now - cacheEntry.capturedAt <= maxAgeMs) {
|
|
72
|
+
return withFileListCacheMetadata(cacheEntry.value, 'memory', cacheEntry.capturedAt);
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
const pending = deps.buildProjectFileList(params)
|
|
75
76
|
.then(fileList => {
|
|
76
77
|
const capturedAt = Date.now();
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
const currentEntry = fileListCache.entries.get(cacheKey);
|
|
79
|
+
if (currentEntry?.pending === pending) {
|
|
80
|
+
if (shouldCacheProjectFileList(fileList, params)) {
|
|
81
|
+
fileListCache.entries.set(cacheKey, {
|
|
82
|
+
value: fileList,
|
|
83
|
+
capturedAt,
|
|
84
|
+
pending: null
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
fileListCache.entries.delete(cacheKey);
|
|
88
|
+
}
|
|
84
89
|
}
|
|
85
90
|
return withFileListCacheMetadata(fileList, 'fresh', capturedAt);
|
|
86
91
|
})
|
|
87
92
|
.finally(() => {
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
const currentEntry = fileListCache.entries.get(cacheKey);
|
|
94
|
+
if (currentEntry?.pending === pending) {
|
|
95
|
+
currentEntry.pending = null;
|
|
90
96
|
}
|
|
91
97
|
});
|
|
92
98
|
|
|
93
|
-
fileListCache.
|
|
94
|
-
|
|
99
|
+
fileListCache.entries.set(cacheKey, {
|
|
100
|
+
value: cacheEntry?.value || null,
|
|
101
|
+
capturedAt: cacheEntry?.capturedAt || 0,
|
|
102
|
+
pending
|
|
103
|
+
});
|
|
95
104
|
return pending;
|
|
96
105
|
}
|
|
97
106
|
|
|
98
|
-
function invalidateProjectSnapshot() {
|
|
107
|
+
function invalidateProjectSnapshot(options = {}) {
|
|
99
108
|
resetKeyedCache(snapshotCache);
|
|
109
|
+
if (options.invalidateFileList === true) {
|
|
110
|
+
resetKeyedCache(fileListCache);
|
|
111
|
+
}
|
|
100
112
|
}
|
|
101
113
|
|
|
102
114
|
function getSnapshotCacheKey(params = {}) {
|
|
@@ -143,15 +155,6 @@
|
|
|
143
155
|
return fileList?.capabilities?.method === 'overleaf-zip-file-list';
|
|
144
156
|
}
|
|
145
157
|
|
|
146
|
-
function createCache() {
|
|
147
|
-
return {
|
|
148
|
-
key: '',
|
|
149
|
-
capturedAt: 0,
|
|
150
|
-
value: null,
|
|
151
|
-
pending: null
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
158
|
function createKeyedCache() {
|
|
156
159
|
return {
|
|
157
160
|
entries: new Map()
|
|
@@ -162,13 +165,6 @@
|
|
|
162
165
|
cache.entries.clear();
|
|
163
166
|
}
|
|
164
167
|
|
|
165
|
-
function resetCache(cache) {
|
|
166
|
-
cache.key = '';
|
|
167
|
-
cache.capturedAt = 0;
|
|
168
|
-
cache.value = null;
|
|
169
|
-
cache.pending = null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
168
|
function normalizeSnapshotMaxAge(value) {
|
|
173
169
|
if (value === 0) {
|
|
174
170
|
return 0;
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
? deps.readActiveEditorText
|
|
22
22
|
: () => '';
|
|
23
23
|
let invalidatedAt = 0;
|
|
24
|
+
let preferredZipEndpoint = '';
|
|
24
25
|
|
|
25
26
|
function getProjectId() {
|
|
26
27
|
return treeOperations.getProjectId?.() || null;
|
|
@@ -100,6 +101,7 @@
|
|
|
100
101
|
skipped: [],
|
|
101
102
|
diagnostics: {
|
|
102
103
|
zipEndpoint: zipSnapshot.endpoint,
|
|
104
|
+
zipTiming: zipSnapshot.diagnostics,
|
|
103
105
|
docRecordCount: internalDocRecords.length,
|
|
104
106
|
docRecords: internalDocRecords.slice(0, 8)
|
|
105
107
|
},
|
|
@@ -123,6 +125,7 @@
|
|
|
123
125
|
reason: zipSnapshot.reason || 'Overleaf source ZIP was unavailable'
|
|
124
126
|
}],
|
|
125
127
|
diagnostics: {
|
|
128
|
+
zipTiming: zipSnapshot.diagnostics,
|
|
126
129
|
docRecordCount: internalDocRecords.length,
|
|
127
130
|
docRecords: internalDocRecords.slice(0, 8)
|
|
128
131
|
},
|
|
@@ -257,6 +260,7 @@
|
|
|
257
260
|
skipped: zipSnapshot.skipped || [],
|
|
258
261
|
diagnostics: {
|
|
259
262
|
zipEndpoint: zipSnapshot.endpoint,
|
|
263
|
+
zipTiming: zipSnapshot.diagnostics,
|
|
260
264
|
docRecordCount: internalDocRecords.length,
|
|
261
265
|
docRecords: internalDocRecords.slice(0, 8)
|
|
262
266
|
},
|
|
@@ -460,6 +464,7 @@
|
|
|
460
464
|
skipped: zipSnapshot.skipped || [],
|
|
461
465
|
diagnostics: {
|
|
462
466
|
zipEndpoint: zipSnapshot.endpoint,
|
|
467
|
+
zipTiming: zipSnapshot.diagnostics,
|
|
463
468
|
docRecordCount: 0,
|
|
464
469
|
docRecords: []
|
|
465
470
|
},
|
|
@@ -640,24 +645,60 @@
|
|
|
640
645
|
`${window.location.origin}/project/${encodeURIComponent(projectId)}/download/zip`,
|
|
641
646
|
`${window.location.origin}/download/project/${encodeURIComponent(projectId)}`
|
|
642
647
|
];
|
|
648
|
+
const orderedEndpoints = preferredZipEndpoint && endpoints.includes(preferredZipEndpoint)
|
|
649
|
+
? [preferredZipEndpoint, ...endpoints.filter(endpoint => endpoint !== preferredZipEndpoint)]
|
|
650
|
+
: endpoints;
|
|
643
651
|
|
|
652
|
+
const startedAt = Date.now();
|
|
653
|
+
const deadline = startedAt + timeoutMs;
|
|
644
654
|
const errors = [];
|
|
645
|
-
|
|
655
|
+
const attempts = [];
|
|
656
|
+
for (const endpoint of orderedEndpoints) {
|
|
657
|
+
const remainingMs = deadline - Date.now();
|
|
658
|
+
if (remainingMs <= 0) {
|
|
659
|
+
errors.push(`Overleaf ZIP download exhausted its ${timeoutMs}ms total timeout budget`);
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
const attemptStartedAt = Date.now();
|
|
646
663
|
try {
|
|
647
|
-
const { response, contentType, buffer } = await fetchZipEndpoint(endpoint,
|
|
664
|
+
const { response, contentType, buffer, timing } = await fetchZipEndpoint(endpoint, remainingMs);
|
|
648
665
|
if (!response.ok) {
|
|
666
|
+
attempts.push({
|
|
667
|
+
endpoint,
|
|
668
|
+
status: response.status,
|
|
669
|
+
elapsedMs: Date.now() - attemptStartedAt
|
|
670
|
+
});
|
|
649
671
|
errors.push(`${endpoint} returned ${response.status}`);
|
|
650
672
|
continue;
|
|
651
673
|
}
|
|
674
|
+
const extractStartedAt = Date.now();
|
|
652
675
|
const extracted = await extractFilesFromZip(buffer, {
|
|
653
676
|
includeBinaryFiles: Boolean(params.includeBinaryFiles),
|
|
654
677
|
includeContent: params.includeContent !== false
|
|
655
678
|
});
|
|
679
|
+
const extractMs = Date.now() - extractStartedAt;
|
|
656
680
|
const files = extracted.files || [];
|
|
657
681
|
if (!files.length) {
|
|
682
|
+
attempts.push({
|
|
683
|
+
endpoint,
|
|
684
|
+
status: response.status,
|
|
685
|
+
elapsedMs: Date.now() - attemptStartedAt,
|
|
686
|
+
downloadMs: timing.totalMs,
|
|
687
|
+
extractMs
|
|
688
|
+
});
|
|
658
689
|
errors.push(`${endpoint} returned no text files (${contentType || 'unknown content type'})`);
|
|
659
690
|
continue;
|
|
660
691
|
}
|
|
692
|
+
preferredZipEndpoint = endpoint;
|
|
693
|
+
attempts.push({
|
|
694
|
+
endpoint,
|
|
695
|
+
status: response.status,
|
|
696
|
+
elapsedMs: Date.now() - attemptStartedAt,
|
|
697
|
+
timeToHeadersMs: timing.timeToHeadersMs,
|
|
698
|
+
downloadMs: timing.totalMs,
|
|
699
|
+
extractMs,
|
|
700
|
+
bytes: buffer?.byteLength || 0
|
|
701
|
+
});
|
|
661
702
|
return {
|
|
662
703
|
ok: true,
|
|
663
704
|
endpoint,
|
|
@@ -665,22 +706,42 @@
|
|
|
665
706
|
files: files.map(file => ({
|
|
666
707
|
...file,
|
|
667
708
|
source: 'overleaf-zip'
|
|
668
|
-
}))
|
|
709
|
+
})),
|
|
710
|
+
diagnostics: {
|
|
711
|
+
totalMs: Date.now() - startedAt,
|
|
712
|
+
timeoutBudgetMs: timeoutMs,
|
|
713
|
+
attempts
|
|
714
|
+
}
|
|
669
715
|
};
|
|
670
716
|
} catch (error) {
|
|
717
|
+
attempts.push({
|
|
718
|
+
endpoint,
|
|
719
|
+
error: error.message,
|
|
720
|
+
elapsedMs: Date.now() - attemptStartedAt
|
|
721
|
+
});
|
|
671
722
|
errors.push(`${endpoint} failed: ${error.message}`);
|
|
723
|
+
if (error?.code === 'zip_timeout') {
|
|
724
|
+
errors.push(`Overleaf ZIP download exhausted its ${timeoutMs}ms total timeout budget`);
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
672
727
|
}
|
|
673
728
|
}
|
|
674
729
|
|
|
675
730
|
return {
|
|
676
731
|
ok: false,
|
|
677
|
-
reason: errors.join('; ') || 'Overleaf source ZIP was unavailable'
|
|
732
|
+
reason: errors.join('; ') || 'Overleaf source ZIP was unavailable',
|
|
733
|
+
diagnostics: {
|
|
734
|
+
totalMs: Date.now() - startedAt,
|
|
735
|
+
timeoutBudgetMs: timeoutMs,
|
|
736
|
+
attempts
|
|
737
|
+
}
|
|
678
738
|
};
|
|
679
739
|
}
|
|
680
740
|
|
|
681
741
|
function fetchZipEndpoint(endpoint, timeoutMs) {
|
|
682
742
|
const controller = typeof AbortController === 'function' ? new AbortController() : null;
|
|
683
743
|
const request = (async () => {
|
|
744
|
+
const startedAt = Date.now();
|
|
684
745
|
const response = await fetch(endpoint, {
|
|
685
746
|
credentials: 'include',
|
|
686
747
|
headers: {
|
|
@@ -688,12 +749,17 @@
|
|
|
688
749
|
},
|
|
689
750
|
...(controller ? { signal: controller.signal } : {})
|
|
690
751
|
});
|
|
752
|
+
const headersAt = Date.now();
|
|
691
753
|
const contentType = response.headers.get('content-type') || '';
|
|
692
754
|
const buffer = response.ok ? await response.arrayBuffer() : null;
|
|
693
755
|
return {
|
|
694
756
|
response,
|
|
695
757
|
contentType,
|
|
696
|
-
buffer
|
|
758
|
+
buffer,
|
|
759
|
+
timing: {
|
|
760
|
+
timeToHeadersMs: headersAt - startedAt,
|
|
761
|
+
totalMs: Date.now() - startedAt
|
|
762
|
+
}
|
|
697
763
|
};
|
|
698
764
|
})();
|
|
699
765
|
|
|
@@ -706,11 +772,12 @@
|
|
|
706
772
|
let timeoutId = null;
|
|
707
773
|
const timeout = new Promise((_resolve, reject) => {
|
|
708
774
|
timeoutId = window.setTimeout(() => {
|
|
775
|
+
const timeoutError = new Error(message);
|
|
776
|
+
timeoutError.code = 'zip_timeout';
|
|
777
|
+
reject(timeoutError);
|
|
709
778
|
try {
|
|
710
779
|
onTimeout?.();
|
|
711
|
-
}
|
|
712
|
-
reject(new Error(message));
|
|
713
|
-
}
|
|
780
|
+
} catch (_error) {}
|
|
714
781
|
}, timeoutMs);
|
|
715
782
|
});
|
|
716
783
|
|
|
@@ -879,55 +879,42 @@
|
|
|
879
879
|
const previousEditorIdentity = getActiveEditorIdentity();
|
|
880
880
|
const previousSignature = contentSignature(currentText);
|
|
881
881
|
|
|
882
|
-
// §9.1 target_file_not_found: if the target path is not visible in the
|
|
883
|
-
// Overleaf project file tree, refuse the write before attempting to open.
|
|
884
|
-
// openFileByPath itself would fail anyway, but emitting the canonical
|
|
885
|
-
// path-not-found code (rather than a generic open failure) gives the run
|
|
886
|
-
// card / final report a more actionable presentation per §17.3.1.
|
|
887
|
-
// Only fires when the tree-operations layer actually exposes a
|
|
888
|
-
// projectPathExists hook; older harnesses without the hook fall through
|
|
889
|
-
// to the openFileByPath path and surface a generic open failure.
|
|
890
|
-
if (operation?.type === 'edit' && hasProjectPathExistsHook() && !projectPathExists(filePath)) {
|
|
891
|
-
return {
|
|
892
|
-
ok: false,
|
|
893
|
-
code: 'path_not_found',
|
|
894
|
-
reason: `Cannot edit ${filePath}; the path is not in the Overleaf project file tree.`,
|
|
895
|
-
failure: buildPageFailure('target_file_not_found', {
|
|
896
|
-
file: filePath,
|
|
897
|
-
activeFile: initialActivePath || '',
|
|
898
|
-
operationType: operation?.type || 'edit',
|
|
899
|
-
changedDocument: false,
|
|
900
|
-
userMessage: `Codex could not find ${filePath} in this Overleaf project.`,
|
|
901
|
-
evidence: {
|
|
902
|
-
originalCode: 'path_not_found',
|
|
903
|
-
initialActivePath: initialActivePath || '',
|
|
904
|
-
writeStarted: false
|
|
905
|
-
}
|
|
906
|
-
})
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
|
|
910
882
|
const opened = await openFileByPath(filePath, { force: initialActivePath === filePath });
|
|
911
883
|
if (!opened.ok) {
|
|
912
|
-
|
|
884
|
+
// A collapsed folder can make a real nested file temporarily invisible
|
|
885
|
+
// to projectPathExists(). openFileByPath() owns the side-effecting
|
|
886
|
+
// expand-and-open attempt, so only classify the path as missing after
|
|
887
|
+
// that attempt has failed and the path still cannot be observed.
|
|
888
|
+
const pathMissing = operation?.type === 'edit'
|
|
889
|
+
&& hasProjectPathExistsHook()
|
|
890
|
+
&& !projectPathExists(filePath);
|
|
891
|
+
const resultCode = pathMissing ? 'path_not_found' : 'file_open_failed';
|
|
892
|
+
const failureCode = pathMissing ? 'target_file_not_found' : 'target_file_open_failed';
|
|
893
|
+
const reasonText = pathMissing
|
|
894
|
+
? `Cannot edit ${filePath}; the path was not found after Overleaf tried to open it.`
|
|
895
|
+
: `Cannot edit ${filePath}; active file is ${initialActivePath || 'unknown'}; ${opened.reason}`;
|
|
913
896
|
return {
|
|
914
897
|
ok: false,
|
|
915
|
-
code:
|
|
916
|
-
debug: buildWritebackDebug('open_file_failed', operation, baseFileLookup, currentText, {
|
|
898
|
+
code: resultCode,
|
|
899
|
+
debug: buildWritebackDebug(pathMissing ? 'open_file_path_not_found' : 'open_file_failed', operation, baseFileLookup, currentText, {
|
|
917
900
|
initialActivePath,
|
|
918
901
|
opened
|
|
919
902
|
}),
|
|
920
903
|
reason: reasonText,
|
|
921
|
-
failure: buildPageFailure(
|
|
904
|
+
failure: buildPageFailure(failureCode, {
|
|
922
905
|
file: filePath,
|
|
923
906
|
activeFile: initialActivePath || '',
|
|
924
907
|
operationType: operation?.type || 'edit',
|
|
925
908
|
changedDocument: false,
|
|
926
|
-
userMessage:
|
|
909
|
+
userMessage: pathMissing
|
|
910
|
+
? `Codex could not find ${filePath} in this Overleaf project.`
|
|
911
|
+
: `Codex could not open ${filePath} in Overleaf.`,
|
|
927
912
|
technicalMessage: opened.reason || '',
|
|
928
913
|
evidence: {
|
|
914
|
+
originalCode: resultCode,
|
|
929
915
|
initialActivePath: initialActivePath || '',
|
|
930
916
|
openMethod: opened.method || '',
|
|
917
|
+
openAttempted: true,
|
|
931
918
|
writeStarted: false
|
|
932
919
|
}
|
|
933
920
|
})
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
return projectSnapshotBridge.getProjectFileList(params);
|
|
203
203
|
}
|
|
204
204
|
if (method === 'invalidateProjectSnapshot') {
|
|
205
|
-
projectSnapshotBridge.invalidateProjectSnapshot();
|
|
205
|
+
projectSnapshotBridge.invalidateProjectSnapshot(params);
|
|
206
206
|
return { ok: true };
|
|
207
207
|
}
|
|
208
208
|
if (method === 'createCheckpoint') {
|