codex-overleaf-link 2.3.4 → 2.3.6
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 +325 -211
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +9 -0
- package/extension/src/backgroundUpdateCoordinator.js +88 -4
- package/extension/src/content/composerPanel.js +1 -1
- package/extension/src/content/contentRuntime.js +51 -35
- package/extension/src/content/diagnosticsController.js +13 -3
- package/extension/src/content/diagnosticsPanel.js +1 -1
- package/extension/src/content/generated/content.bundle.js +1233 -298
- package/extension/src/content/generated/content.bundle.meta.json +8 -3
- package/extension/src/content/globalPreferencesController.js +125 -0
- package/extension/src/content/legacyGlobalRegistry.js +4 -0
- package/extension/src/content/otWarmMirror.js +95 -65
- package/extension/src/content/otWarmMirrorController.js +102 -1
- package/extension/src/content/panelMaintenance.js +26 -15
- package/extension/src/content/panelRenderer.js +6 -1
- package/extension/src/content/projectSettingsCoordinator.js +1 -0
- package/extension/src/content/providerSettingsCoordinator.js +16 -0
- package/extension/src/content/providerSettingsDialog.js +24 -15
- package/extension/src/content/runFailureNotice.js +104 -0
- package/extension/src/content/runScrollLayout.js +124 -0
- package/extension/src/content/runTimelineView.js +36 -10
- package/extension/src/content/settingsPanel.js +32 -13
- package/extension/src/content/themeController.js +3 -0
- package/extension/src/content/updateNotice.js +24 -4
- package/extension/src/page/overleafRealtimeObserver.js +116 -24
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/globalPreferences.js +131 -0
- package/extension/src/shared/i18n.js +79 -39
- package/extension/src/shared/managedUpdateProjection.js +1 -1
- package/extension/src/shared/otText.js +0 -1
- package/extension/src/shared/sessionState.js +17 -21
- package/extension/src/shared/sessionTitle.js +120 -0
- package/extension/src/shared/updateRuntimeIdentity.js +32 -0
- package/extension/styles/panel.css +90 -16
- package/native-host/src/mirrorWorkspace.js +20 -1
- package/native-host/src/releaseProxy.js +177 -0
- package/native-host/src/releaseTransport.js +154 -0
- package/native-host/src/updateManager.js +40 -39
- package/package.json +1 -1
- package/scripts/install-managed.mjs +1 -1
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
require('./settlementFacts'),
|
|
6
6
|
require('./runInputQueue'),
|
|
7
7
|
require('./lineReferences'),
|
|
8
|
-
require('./pathRedaction')
|
|
8
|
+
require('./pathRedaction'),
|
|
9
|
+
require('./sessionTitle')
|
|
9
10
|
);
|
|
10
11
|
} else {
|
|
11
12
|
root.CodexOverleafModuleRegistry.define(
|
|
12
13
|
'SessionState',
|
|
13
|
-
['I18n', 'SettlementFacts', 'RunInputQueue', 'LineReferences', 'PathRedaction'],
|
|
14
|
+
['I18n', 'SettlementFacts', 'RunInputQueue', 'LineReferences', 'PathRedaction', 'SessionTitle'],
|
|
14
15
|
factory
|
|
15
16
|
);
|
|
16
17
|
}
|
|
@@ -19,7 +20,8 @@
|
|
|
19
20
|
SettlementFacts,
|
|
20
21
|
RunInputQueue,
|
|
21
22
|
LineReferences,
|
|
22
|
-
PathRedaction
|
|
23
|
+
PathRedaction,
|
|
24
|
+
SessionTitle
|
|
23
25
|
) {
|
|
24
26
|
'use strict';
|
|
25
27
|
|
|
@@ -402,6 +404,12 @@
|
|
|
402
404
|
) {
|
|
403
405
|
next.title = session.title;
|
|
404
406
|
next.titleSource = 'manual';
|
|
407
|
+
} else if (session.titleSource === 'auto' && patch.titleSource !== 'manual'
|
|
408
|
+
&& typeof patch.title === 'string' && patch.title.trim() && session.runs?.length
|
|
409
|
+
&& SessionTitle.isGeneric(sanitizeAutoTitle(patch.title))
|
|
410
|
+
&& !SessionTitle.isGeneric(sanitizeAutoTitle(session.title))) {
|
|
411
|
+
next.title = session.title;
|
|
412
|
+
next.titleSource = 'auto';
|
|
405
413
|
}
|
|
406
414
|
return normalizeSession({
|
|
407
415
|
...next
|
|
@@ -472,12 +480,7 @@
|
|
|
472
480
|
}
|
|
473
481
|
|
|
474
482
|
function deriveSessionTitle(runs, task) {
|
|
475
|
-
|
|
476
|
-
if (taskTitle) {
|
|
477
|
-
return taskTitle;
|
|
478
|
-
}
|
|
479
|
-
const firstRunTask = Array.isArray(runs) && runs.length ? runs[0]?.task : '';
|
|
480
|
-
return sanitizeAutoTitle(firstRunTask);
|
|
483
|
+
return SessionTitle.derive(runs, task, { sanitize: sanitizeAssistantVisibleText, maxChars: SESSION_AUTO_TITLE_CHARS });
|
|
481
484
|
}
|
|
482
485
|
|
|
483
486
|
// Rename a session with the placeholder/derived-title ghost guard (mirrors
|
|
@@ -519,21 +522,14 @@
|
|
|
519
522
|
if (titleSource === 'manual') {
|
|
520
523
|
return normalizeTextField(rawTitle === LEGACY_DEFAULT_SESSION_TITLE ? '' : rawTitle, STORAGE_DEFAULT_LIMITS.sessionTitleChars);
|
|
521
524
|
}
|
|
522
|
-
|
|
525
|
+
const current = sanitizeAutoTitle(rawTitle === LEGACY_DEFAULT_SESSION_TITLE ? '' : rawTitle);
|
|
526
|
+
if (!current || SessionTitle.isGeneric(current)) return derivedTitle || current || '';
|
|
527
|
+
if (current.endsWith('…') && derivedTitle.startsWith(current.slice(0, -1))) return derivedTitle;
|
|
528
|
+
return current;
|
|
523
529
|
}
|
|
524
530
|
|
|
525
531
|
function sanitizeAutoTitle(value) {
|
|
526
|
-
|
|
527
|
-
.replace(/@file:[^\s]+/g, ' ')
|
|
528
|
-
.replace(/@(context|compile-log)\b/g, ' ')
|
|
529
|
-
.replace(/\s+/g, ' ')
|
|
530
|
-
.trim();
|
|
531
|
-
if (!title) {
|
|
532
|
-
return '';
|
|
533
|
-
}
|
|
534
|
-
return title.length > SESSION_AUTO_TITLE_CHARS
|
|
535
|
-
? `${title.slice(0, SESSION_AUTO_TITLE_CHARS - 1)}…`
|
|
536
|
-
: title;
|
|
532
|
+
return SessionTitle.fromPrompt(value, { sanitize: sanitizeAssistantVisibleText, maxChars: SESSION_AUTO_TITLE_CHARS });
|
|
537
533
|
}
|
|
538
534
|
|
|
539
535
|
function isDisplayableSession(session) {
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
(function initSessionTitle(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafModuleRegistry.define('SessionTitle', [], factory);
|
|
6
|
+
}
|
|
7
|
+
})(typeof globalThis !== 'undefined' ? globalThis : window, function sessionTitleFactory() {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const INPUT_LIMIT = 12000;
|
|
11
|
+
const STORAGE_LIMIT = 80;
|
|
12
|
+
const segmenter = typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function'
|
|
13
|
+
? new Intl.Segmenter(undefined, { granularity: 'grapheme' }) : null;
|
|
14
|
+
const FILE_TOKEN = /@file:(?:"([^"]+)"|'([^']+)'|([^\s]+))/g;
|
|
15
|
+
|
|
16
|
+
function derive(runs, task, options = {}) {
|
|
17
|
+
let fallback = '';
|
|
18
|
+
for (const run of Array.isArray(runs) ? runs : []) {
|
|
19
|
+
const candidate = fromPrompt(run?.task, options);
|
|
20
|
+
fallback ||= candidate;
|
|
21
|
+
if (candidate && !isGeneric(candidate)) return candidate;
|
|
22
|
+
}
|
|
23
|
+
const draft = fromPrompt(task, options);
|
|
24
|
+
return draft && !isGeneric(draft) ? draft : fallback || draft;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function fromPrompt(value, { sanitize = text => text, maxChars = 24 } = {}) {
|
|
28
|
+
if (typeof value !== 'string' || !value.trim()) return '';
|
|
29
|
+
const source = String(sanitize(value.slice(0, INPUT_LIMIT)) || '');
|
|
30
|
+
let fence = null;
|
|
31
|
+
let fallback = '';
|
|
32
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
33
|
+
const line = rawLine.trim();
|
|
34
|
+
const marker = /^(`{3,}|~{3,})(.*)$/.exec(line);
|
|
35
|
+
if (marker) {
|
|
36
|
+
if (!fence) {
|
|
37
|
+
fence = marker[1];
|
|
38
|
+
const language = marker[2].trim().match(/^[A-Za-z0-9_+-]{1,24}\b/)?.[0] || 'code';
|
|
39
|
+
fallback ||= `${language} snippet`;
|
|
40
|
+
} else if (marker[1][0] === fence[0] && marker[1].length >= fence.length && !marker[2].trim()) {
|
|
41
|
+
fence = null;
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (fence || !line || /^>/.test(line)) continue;
|
|
46
|
+
const withoutContext = line.replace(FILE_TOKEN, ' ').replace(/@(context|compile-log)\b/g, ' ').trim();
|
|
47
|
+
const cleaned = cleanLine(line);
|
|
48
|
+
if (!withoutContext || /^https?:\/\/\S+$/i.test(line)) {
|
|
49
|
+
fallback ||= cleaned;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
for (const sentence of cleaned.split(/[\u3002\uff01\uff1f]+/u)) {
|
|
53
|
+
const candidate = sentence.trim().replace(/[,;:\uff0c\uff1b\uff1a]+$/u, '').trim();
|
|
54
|
+
if (!/[\p{L}\p{N}]/u.test(candidate) || isScaffolding(candidate)) continue;
|
|
55
|
+
fallback ||= candidate;
|
|
56
|
+
if (!isGeneric(candidate)) return shorten(candidate, maxChars);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return shorten(fallback, maxChars);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cleanLine(value) {
|
|
63
|
+
return value
|
|
64
|
+
.replace(/^#{1,6}\s+/, '')
|
|
65
|
+
.replace(/^(?:[-*+]\s+|\d+\.\s+|\d+[)\u3001]\s*)/, '')
|
|
66
|
+
.replace(FILE_TOKEN, (_match, quoted, singleQuoted, bare) =>
|
|
67
|
+
String(quoted || singleQuoted || bare || '').replace(/\\/g, '/').split('/').pop())
|
|
68
|
+
.replace(/@(context|compile-log)\b/g, ' ')
|
|
69
|
+
.replace(/!?\[([^\]]*)\]\([^\n)]*\)/g, '$1')
|
|
70
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
71
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
72
|
+
.replace(/https?:\/\/\S+/gi, address => {
|
|
73
|
+
try { return new URL(address).hostname; } catch (_error) { return ''; }
|
|
74
|
+
})
|
|
75
|
+
.replace(/\[(?:REDACTED_SECRET|local path(?::\d+)?)\]/gi, ' ')
|
|
76
|
+
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '')
|
|
77
|
+
.replace(/\s+/g, ' ').trim()
|
|
78
|
+
.replace(/^(?:\u8bf7(?:\u4f60)?\u5e2e\u6211|\u8bf7\u4f60|\u5e2e\u6211|\u9ebb\u70e6\u4f60(?:\u5e2e\u6211)?|\u9ebb\u70e6\u5e2e\u6211|please\s+)\s*/i, '');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isScaffolding(value) {
|
|
82
|
+
return /^(?:my request(?: for [^:]+)?|request|task|context|files(?: mentioned by [^:]+)?|attachments|\u6211\u7684\u8bf7\u6c42|\u9700\u6c42|\u4efb\u52a1|\u4e0a\u4e0b\u6587|\u9644\u4ef6|\u6587\u4ef6)$/i.test(value);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isGeneric(value) {
|
|
86
|
+
const text = String(value || '').trim().replace(/[\s.!?\u3002\uff1f\uff01\u2026]+$/u, '');
|
|
87
|
+
if (!text || Array.from(text).length < 2) return true;
|
|
88
|
+
return /^(?:ok(?:ay)?|yes|no|go|done|continue|proceed|thanks?|thank you|hello|hi|test|new task|untitled task|\u597d(?:\u7684)?|\u53ef\u4ee5|\u7ee7\u7eed|\u786e\u8ba4|\u786e\u5b9a|\u6536\u5230|\u8c22\u8c22|\u6d4b\u8bd5|\u6267\u884c|\u662f\u7684|\u4f60\u597d)$/i.test(text)
|
|
89
|
+
|| /^[A-Za-z0-9_+-]{1,24} snippet$/i.test(text)
|
|
90
|
+
|| /^[^\s]+\.(?:tex|bib|md|txt|pdf|svg|png|jpe?g|webp)(?::\d+)?$/i.test(text)
|
|
91
|
+
|| /^(?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,}$/i.test(text);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function shorten(value, maxChars) {
|
|
95
|
+
const title = String(value || '').trim();
|
|
96
|
+
if (!title) return '';
|
|
97
|
+
const chars = Number.isFinite(maxChars) && maxChars > 0 ? Math.min(40, Math.floor(maxChars)) : 24;
|
|
98
|
+
const maxUnits = chars * 2;
|
|
99
|
+
const glyphs = segmenter
|
|
100
|
+
? Array.from(segmenter.segment(title), item => item.segment) : Array.from(title);
|
|
101
|
+
const width = glyph => /[^\u0000-\u00ff]/u.test(glyph) ? 2 : 1;
|
|
102
|
+
if (title.length <= STORAGE_LIMIT && glyphs.reduce((total, glyph) => total + width(glyph), 0) <= maxUnits) return title;
|
|
103
|
+
let prefix = '';
|
|
104
|
+
let units = 0;
|
|
105
|
+
let index = 0;
|
|
106
|
+
for (; index < glyphs.length; index++) {
|
|
107
|
+
const glyph = glyphs[index];
|
|
108
|
+
if (units + width(glyph) > maxUnits - 2 || prefix.length + glyph.length > STORAGE_LIMIT - 1) break;
|
|
109
|
+
prefix += glyph;
|
|
110
|
+
units += width(glyph);
|
|
111
|
+
}
|
|
112
|
+
const boundary = prefix.lastIndexOf(' ');
|
|
113
|
+
if (/[A-Za-z0-9]$/.test(prefix) && /^[A-Za-z0-9]/.test(glyphs[index] || '') && boundary >= prefix.length * 0.6) {
|
|
114
|
+
prefix = prefix.slice(0, boundary);
|
|
115
|
+
}
|
|
116
|
+
return prefix.trimEnd().replace(/[,;:\uff0c\uff1b\uff1a]+$/u, '') + '\u2026';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return Object.freeze({ derive, fromPrompt, isGeneric });
|
|
120
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
const api = factory();
|
|
3
|
+
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
4
|
+
if (root) root.CodexOverleafUpdateRuntimeIdentity = api;
|
|
5
|
+
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
function version(value) { return /^\d+\.\d+\.\d+$/.test(String(value || '')) ? String(value) : ''; }
|
|
9
|
+
function compare(left, right) {
|
|
10
|
+
const a = left.split('.').map(Number);
|
|
11
|
+
const b = right.split('.').map(Number);
|
|
12
|
+
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function inspectInstalledRuntime(status = {}, options = {}) {
|
|
16
|
+
const result = { installedVersion: version(status.activeVersion), nativeVersion: version(status.runtimeVersion),
|
|
17
|
+
extensionVersion: version(options.extensionVersion), runtimeVersion: version(options.runtimeVersion), state: 'unknown' };
|
|
18
|
+
if (!status.managed || !result.installedVersion || !result.nativeVersion ||
|
|
19
|
+
!result.extensionVersion || !result.runtimeVersion) return result;
|
|
20
|
+
if (['authorized', 'bound'].includes(status.authorization?.state) ||
|
|
21
|
+
['staged', 'activation_pending', 'applying', 'awaiting_health'].includes(status.transaction?.state)) {
|
|
22
|
+
return { ...result, state: 'transaction_active' };
|
|
23
|
+
}
|
|
24
|
+
if (status.installedAligned !== true) return { ...result, state: 'repair_required' };
|
|
25
|
+
const loaded = [result.nativeVersion, result.extensionVersion, result.runtimeVersion];
|
|
26
|
+
if (loaded.every(value => value === result.installedVersion)) return { ...result, state: 'aligned' };
|
|
27
|
+
return { ...result, state: loaded.every(value => compare(result.installedVersion, value) >= 0)
|
|
28
|
+
? 'reload_required' : 'repair_required' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return Object.freeze({ inspectInstalledRuntime });
|
|
32
|
+
});
|
|
@@ -170,14 +170,14 @@
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
.codex-provider-dialog-root {
|
|
173
|
-
--provider-bg:
|
|
173
|
+
--provider-bg: #151716;
|
|
174
174
|
--provider-surface: #1c1f1d;
|
|
175
175
|
--provider-surface-raised: #222622;
|
|
176
176
|
--provider-line: rgba(255, 255, 255, 0.12);
|
|
177
177
|
--provider-muted: #a0a59f;
|
|
178
178
|
--provider-accent: #79d58a;
|
|
179
179
|
--provider-danger: #ef8176;
|
|
180
|
-
--provider-text:
|
|
180
|
+
--provider-text: #eef0ec;
|
|
181
181
|
--provider-field-label: #dfe3dd;
|
|
182
182
|
--provider-control-bg: rgba(255, 255, 255, 0.035);
|
|
183
183
|
--provider-hover: rgba(255, 255, 255, 0.07);
|
|
@@ -191,6 +191,14 @@
|
|
|
191
191
|
position: fixed;
|
|
192
192
|
inset: 0;
|
|
193
193
|
z-index: 2147483600;
|
|
194
|
+
margin: 0;
|
|
195
|
+
padding: 0;
|
|
196
|
+
border: 0;
|
|
197
|
+
width: 100vw;
|
|
198
|
+
max-width: none;
|
|
199
|
+
height: 100vh;
|
|
200
|
+
max-height: none;
|
|
201
|
+
background: transparent;
|
|
194
202
|
color: var(--provider-text);
|
|
195
203
|
font-family: "Aptos", "SF Pro Text", sans-serif;
|
|
196
204
|
}
|
|
@@ -217,6 +225,7 @@
|
|
|
217
225
|
}
|
|
218
226
|
|
|
219
227
|
.codex-provider-dialog-root[hidden] { display: none !important; }
|
|
228
|
+
.codex-provider-dialog-root::backdrop { background: transparent; }
|
|
220
229
|
|
|
221
230
|
.codex-provider-dialog-backdrop {
|
|
222
231
|
position: absolute;
|
|
@@ -1572,7 +1581,7 @@
|
|
|
1572
1581
|
|
|
1573
1582
|
#codex-overleaf-panel .codex-set-row-help {
|
|
1574
1583
|
margin: 0;
|
|
1575
|
-
color: var(--tl-fg-
|
|
1584
|
+
color: var(--tl-fg-3);
|
|
1576
1585
|
font-size: 11px;
|
|
1577
1586
|
line-height: 1.45;
|
|
1578
1587
|
}
|
|
@@ -1801,9 +1810,15 @@
|
|
|
1801
1810
|
box-sizing: border-box;
|
|
1802
1811
|
width: var(--codex-overleaf-panel-width);
|
|
1803
1812
|
height: 100vh;
|
|
1813
|
+
max-width: none;
|
|
1814
|
+
max-height: none;
|
|
1815
|
+
margin: 0;
|
|
1816
|
+
border: 0;
|
|
1817
|
+
color: inherit;
|
|
1804
1818
|
padding: 14px;
|
|
1805
1819
|
background: rgba(12, 12, 12, 0.68);
|
|
1806
1820
|
}
|
|
1821
|
+
#codex-overleaf-panel .codex-plugin-confirm::backdrop { background: transparent; }
|
|
1807
1822
|
|
|
1808
1823
|
#codex-overleaf-panel .codex-plugin-confirm-card {
|
|
1809
1824
|
width: 100%;
|
|
@@ -2155,7 +2170,7 @@
|
|
|
2155
2170
|
padding: 4px 10px;
|
|
2156
2171
|
font-size: 11px;
|
|
2157
2172
|
cursor: pointer;
|
|
2158
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.
|
|
2173
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
|
2159
2174
|
animation: tl-fade-in 120ms ease-out;
|
|
2160
2175
|
}
|
|
2161
2176
|
|
|
@@ -2170,6 +2185,32 @@
|
|
|
2170
2185
|
border-color: var(--tl-border-strong);
|
|
2171
2186
|
}
|
|
2172
2187
|
|
|
2188
|
+
/* Let the browser anchor backscroll while the reader is detached. */
|
|
2189
|
+
#codex-overleaf-panel [data-log][data-log-auto-follow="false"],
|
|
2190
|
+
#codex-overleaf-panel [data-main][data-log-auto-follow="false"] {
|
|
2191
|
+
overflow-anchor: auto;
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
#codex-overleaf-panel :is(.run-copy-action, .run-fork-action, .tl-jump-latest, .run-final-answer__recovery-action):focus-visible {
|
|
2195
|
+
outline: 2px solid var(--tl-accent);
|
|
2196
|
+
outline-offset: 2px;
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
#codex-overleaf-panel :is(input, textarea)::placeholder {
|
|
2200
|
+
color: var(--tl-fg-3);
|
|
2201
|
+
opacity: 1;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
.codex-provider-dialog-root .codex-provider-field :is(input, textarea)::placeholder {
|
|
2205
|
+
color: var(--provider-muted);
|
|
2206
|
+
opacity: 1;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
.codex-provider-dialog-root :is(button, input, textarea, select):focus-visible {
|
|
2210
|
+
outline: 2px solid var(--provider-accent);
|
|
2211
|
+
outline-offset: 2px;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2173
2214
|
/* Active-session bar: the title on the left, rename + delete on the right, so
|
|
2174
2215
|
the session you are working in can be renamed/deleted in place. */
|
|
2175
2216
|
#codex-overleaf-panel .codex-thread-title {
|
|
@@ -3069,13 +3110,13 @@
|
|
|
3069
3110
|
}
|
|
3070
3111
|
|
|
3071
3112
|
#codex-overleaf-panel .run-final-answer .run-system-label {
|
|
3072
|
-
color: var(--tl-
|
|
3113
|
+
color: var(--tl-fg-3);
|
|
3073
3114
|
font-weight: 650;
|
|
3074
3115
|
letter-spacing: 0.01em;
|
|
3075
3116
|
}
|
|
3076
3117
|
|
|
3077
3118
|
#codex-overleaf-panel .run-completion-report[data-status="failed"] .run-system-label[data-system-label="conclusion"] {
|
|
3078
|
-
color: var(--tl-
|
|
3119
|
+
color: var(--tl-fg-3);
|
|
3079
3120
|
}
|
|
3080
3121
|
|
|
3081
3122
|
#codex-overleaf-panel .run-final-answer .run-change-summary {
|
|
@@ -3164,9 +3205,8 @@
|
|
|
3164
3205
|
}
|
|
3165
3206
|
|
|
3166
3207
|
#codex-overleaf-panel .run-final-answer__meta {
|
|
3167
|
-
margin:
|
|
3168
|
-
padding:
|
|
3169
|
-
border-top: 1px solid var(--tl-border);
|
|
3208
|
+
margin: 14px 0 0;
|
|
3209
|
+
padding: 0;
|
|
3170
3210
|
display: grid;
|
|
3171
3211
|
grid-template-columns: max-content 1fr;
|
|
3172
3212
|
column-gap: 12px;
|
|
@@ -3190,18 +3230,15 @@
|
|
|
3190
3230
|
}
|
|
3191
3231
|
|
|
3192
3232
|
#codex-overleaf-panel .run-completion-report[data-status="failed"] {
|
|
3193
|
-
color: var(--tl-
|
|
3233
|
+
color: var(--tl-fg-2);
|
|
3194
3234
|
}
|
|
3195
3235
|
|
|
3196
|
-
/*
|
|
3197
|
-
conclusion must keep the alert color or failure reads like success. */
|
|
3236
|
+
/* Failure severity belongs to the status header and compact notice. */
|
|
3198
3237
|
#codex-overleaf-panel .run-completion-report[data-status="failed"] .run-final-answer {
|
|
3199
|
-
color: var(--tl-
|
|
3238
|
+
color: var(--tl-fg-2);
|
|
3200
3239
|
}
|
|
3201
3240
|
|
|
3202
|
-
/*
|
|
3203
|
-
meta block stays muted so it still reads as system info rather than amplifying
|
|
3204
|
-
the alert. */
|
|
3241
|
+
/* Keep system metadata subordinate to the answer in both themes. */
|
|
3205
3242
|
#codex-overleaf-panel .run-completion-report[data-status="failed"] .run-final-answer__meta,
|
|
3206
3243
|
#codex-overleaf-panel .run-completion-report[data-status="failed"] .run-final-answer__meta-value {
|
|
3207
3244
|
color: var(--tl-fg-3);
|
|
@@ -3237,6 +3274,43 @@
|
|
|
3237
3274
|
opacity: 0.6;
|
|
3238
3275
|
}
|
|
3239
3276
|
|
|
3277
|
+
#codex-overleaf-panel .run-feedback {
|
|
3278
|
+
display: grid;
|
|
3279
|
+
gap: 5px;
|
|
3280
|
+
margin: 12px 0 0;
|
|
3281
|
+
padding: 10px 12px;
|
|
3282
|
+
border-radius: 8px;
|
|
3283
|
+
background: var(--tl-surface-2);
|
|
3284
|
+
color: var(--tl-fg-2);
|
|
3285
|
+
font-size: 12px;
|
|
3286
|
+
line-height: 1.5;
|
|
3287
|
+
overflow-wrap: anywhere;
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
#codex-overleaf-panel .run-feedback p { margin: 0; }
|
|
3291
|
+
#codex-overleaf-panel .run-feedback__title { color: var(--tl-fail); font-weight: 650; }
|
|
3292
|
+
#codex-overleaf-panel .run-feedback[data-tone="warning"] .run-feedback__title { color: var(--tl-review); }
|
|
3293
|
+
#codex-overleaf-panel .run-feedback .run-final-answer__recovery-action {
|
|
3294
|
+
justify-self: start;
|
|
3295
|
+
margin: 3px 0 0;
|
|
3296
|
+
padding: 3px 0;
|
|
3297
|
+
border: 0;
|
|
3298
|
+
background: transparent;
|
|
3299
|
+
color: var(--tl-accent);
|
|
3300
|
+
font-weight: 600;
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
#codex-overleaf-panel :is(.run-copy-action, .run-fork-action) {
|
|
3304
|
+
color: var(--tl-fg-3);
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3307
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3308
|
+
#codex-overleaf-panel :is(.tl-jump-latest, .run-copy-action, .run-fork-action, .run-final-answer__recovery-action) {
|
|
3309
|
+
animation: none;
|
|
3310
|
+
transition: none;
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3240
3314
|
#codex-overleaf-panel .codex-probe-line {
|
|
3241
3315
|
flex: 0 0 auto;
|
|
3242
3316
|
min-height: 18px;
|
|
@@ -712,6 +712,22 @@ function getMirrorStatus(projectId, options = {}) {
|
|
|
712
712
|
};
|
|
713
713
|
}
|
|
714
714
|
|
|
715
|
+
function normalizeObservedPatchTime(value, fallback) {
|
|
716
|
+
const candidate = value == null ? fallback : value;
|
|
717
|
+
const timestamp = typeof candidate === 'number'
|
|
718
|
+
? candidate
|
|
719
|
+
: typeof candidate === 'string' ? Date.parse(candidate) : NaN;
|
|
720
|
+
return Number.isFinite(timestamp) && Math.abs(timestamp) <= 8640000000000000
|
|
721
|
+
? new Date(timestamp).toISOString()
|
|
722
|
+
: '';
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function isFreshOtObservation(freshness) {
|
|
726
|
+
const timestamp = Date.parse(freshness?.observedAt ?? freshness?.lastPatchAt ?? '');
|
|
727
|
+
const ageMs = Date.now() - timestamp;
|
|
728
|
+
return Number.isFinite(timestamp) && ageMs >= 0 && ageMs <= 30000;
|
|
729
|
+
}
|
|
730
|
+
|
|
715
731
|
function buildOtStatusFields(baseline = {}, trusted) {
|
|
716
732
|
const metadata = {
|
|
717
733
|
lastOtPatchAt: baseline.lastOtPatchAt || '',
|
|
@@ -727,13 +743,15 @@ function buildOtStatusFields(baseline = {}, trusted) {
|
|
|
727
743
|
}
|
|
728
744
|
const textFiles = (baseline.files || []).filter(file => file?.kind === 'text');
|
|
729
745
|
const freshFiles = textFiles
|
|
730
|
-
.filter(file => file.freshness?.source === 'ot' && file.freshness?.state === 'fresh'
|
|
746
|
+
.filter(file => file.freshness?.source === 'ot' && file.freshness?.state === 'fresh'
|
|
747
|
+
&& isFreshOtObservation(file.freshness))
|
|
731
748
|
.map(file => ({
|
|
732
749
|
path: file.path,
|
|
733
750
|
source: file.freshness.source,
|
|
734
751
|
state: file.freshness.state,
|
|
735
752
|
lastFullSyncAt: file.freshness.lastFullSyncAt || '',
|
|
736
753
|
lastPatchAt: file.freshness.lastPatchAt || '',
|
|
754
|
+
observedAt: file.freshness.observedAt ?? file.freshness.lastPatchAt ?? '',
|
|
737
755
|
observedVersion: file.freshness.observedVersion ?? null
|
|
738
756
|
}))
|
|
739
757
|
.sort((left, right) => left.path.localeCompare(right.path));
|
|
@@ -935,6 +953,7 @@ async function patchMirrorFiles({ projectId, files, rootDir, source = 'ot' }) {
|
|
|
935
953
|
state: 'fresh',
|
|
936
954
|
lastFullSyncAt: baseline.lastFullSyncAt || '',
|
|
937
955
|
lastPatchAt: patchBatchAt,
|
|
956
|
+
observedAt: normalizeObservedPatchTime(patch.observedAt, patchBatchAt),
|
|
938
957
|
observedVersion: patch.observedVersion ?? null
|
|
939
958
|
}
|
|
940
959
|
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const http = require('node:http');
|
|
4
|
+
const https = require('node:https');
|
|
5
|
+
const tls = require('node:tls');
|
|
6
|
+
const net = require('node:net');
|
|
7
|
+
const { Readable } = require('node:stream');
|
|
8
|
+
const { execFileSync } = require('node:child_process');
|
|
9
|
+
const { updateError } = require('./updateTrust');
|
|
10
|
+
let systemCache = { platform: '', at: 0, text: '' };
|
|
11
|
+
|
|
12
|
+
function proxyError(code, message, retryable = false) {
|
|
13
|
+
return Object.assign(updateError(code, message), { retryable });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function bypassProxy(url, values = '') {
|
|
17
|
+
const host = url.hostname.toLowerCase();
|
|
18
|
+
const port = url.port || (url.protocol === 'https:' ? '443' : '80');
|
|
19
|
+
return String(values).split(/[,;\s]+/).some(raw => {
|
|
20
|
+
if (!raw) return false;
|
|
21
|
+
if (raw === '*') return true;
|
|
22
|
+
if (raw === '<local>') return !host.includes('.');
|
|
23
|
+
const match = raw.toLowerCase().match(/^(.*?)(?::(\d+))?$/);
|
|
24
|
+
if (match[2] && match[2] !== port) return false;
|
|
25
|
+
const domain = match[1].replace(/^\*?\./, '');
|
|
26
|
+
return host === domain || host.endsWith('.' + domain);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readSystemProxy(platform) {
|
|
31
|
+
if (systemCache.platform === platform && Date.now() - systemCache.at < 30000) return systemCache.text;
|
|
32
|
+
let text = '';
|
|
33
|
+
try {
|
|
34
|
+
const command = platform === 'darwin' ? '/usr/sbin/scutil' : 'reg.exe';
|
|
35
|
+
const args = platform === 'darwin' ? ['--proxy']
|
|
36
|
+
: ['query', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings'];
|
|
37
|
+
text = execFileSync(command, args, { encoding: 'utf8', timeout: 1000, maxBuffer: 32768,
|
|
38
|
+
windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
39
|
+
} catch (_error) { /* environment proxies and direct access remain available */ }
|
|
40
|
+
systemCache = { platform, at: Date.now(), text };
|
|
41
|
+
return text;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function resolveReleaseProxy(value, options = {}) {
|
|
45
|
+
const url = new URL(value);
|
|
46
|
+
const env = options.env || process.env;
|
|
47
|
+
if (bypassProxy(url, env.NO_PROXY || env.no_proxy)) return null;
|
|
48
|
+
const protocol = url.protocol === 'https:' ? 'HTTPS' : 'HTTP';
|
|
49
|
+
let configured = env[protocol + '_PROXY'] || env[protocol.toLowerCase() + '_proxy'] || env.ALL_PROXY || env.all_proxy;
|
|
50
|
+
let source = 'environment';
|
|
51
|
+
const platform = options.platform || process.platform;
|
|
52
|
+
if (!configured && ['darwin', 'win32'].includes(platform)) {
|
|
53
|
+
source = 'system';
|
|
54
|
+
const text = (options.systemProxy || readSystemProxy)(platform);
|
|
55
|
+
if (platform === 'darwin') {
|
|
56
|
+
const field = key => text.match(new RegExp('(?:^|\\n)\\s*' + key + '\\s*:\\s*([^\\n]+)'))?.[1]?.trim() || '';
|
|
57
|
+
if (field(protocol + 'Enable') === '1') {
|
|
58
|
+
const host = field(protocol + 'Proxy');
|
|
59
|
+
const port = Number(field(protocol + 'Port'));
|
|
60
|
+
if (host && Number.isInteger(port) && port > 0 && port <= 65535) {
|
|
61
|
+
configured = 'http://' + (host.includes(':') ? '[' + host + ']' : host) + ':' + port;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const exceptions = text.match(/ExceptionsList\s*:[\s\S]*?\n\s*}/)?.[0] || '';
|
|
65
|
+
const values = Array.from(exceptions.matchAll(/\d+\s*:\s*([^\n]+)/g), match => match[1].trim()).join(',');
|
|
66
|
+
if (bypassProxy(url, values)) return null;
|
|
67
|
+
} else if (/ProxyEnable\s+REG_DWORD\s+0x1\b/i.test(text)) {
|
|
68
|
+
const server = text.match(/ProxyServer\s+REG_SZ\s+([^\r\n]+)/i)?.[1]?.trim() || '';
|
|
69
|
+
const exceptions = text.match(/ProxyOverride\s+REG_SZ\s+([^\r\n]+)/i)?.[1] || '';
|
|
70
|
+
if (bypassProxy(url, exceptions)) return null;
|
|
71
|
+
configured = server.includes('=')
|
|
72
|
+
? server.split(';').find(part => part.toLowerCase().startsWith(protocol.toLowerCase() + '='))?.split('=').slice(1).join('=')
|
|
73
|
+
: server;
|
|
74
|
+
if (configured && !configured.includes('://')) configured = 'http://' + configured;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!configured) return null;
|
|
78
|
+
let proxy;
|
|
79
|
+
try { proxy = new URL(configured); } catch (_error) {
|
|
80
|
+
throw proxyError('update_proxy_invalid', 'The configured update proxy address is invalid.');
|
|
81
|
+
}
|
|
82
|
+
if (!['http:', 'https:'].includes(proxy.protocol)) {
|
|
83
|
+
throw proxyError('update_proxy_unsupported', 'The update proxy must use HTTP or HTTPS. SOCKS and PAC require an HTTP proxy endpoint.');
|
|
84
|
+
}
|
|
85
|
+
return { url: proxy.href, source };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function hostname(url) { return url.hostname.replace(/^\[|\]$/g, ''); }
|
|
89
|
+
function proxyHeaders(proxy) {
|
|
90
|
+
if (!proxy.username && !proxy.password) return {};
|
|
91
|
+
let credentials;
|
|
92
|
+
try { credentials = decodeURIComponent(proxy.username) + ':' + decodeURIComponent(proxy.password); }
|
|
93
|
+
catch (_error) { throw proxyError('update_proxy_invalid', 'The configured proxy credentials are malformed.'); }
|
|
94
|
+
return { 'Proxy-Authorization': 'Basic ' + Buffer.from(credentials).toString('base64') };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function tunnel(target, proxy, signal) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const authority = target.hostname + ':' + (target.port || '443');
|
|
100
|
+
const client = proxy.protocol === 'https:' ? https : http;
|
|
101
|
+
const request = client.request({ protocol: proxy.protocol, hostname: hostname(proxy), port: proxy.port,
|
|
102
|
+
method: 'CONNECT', path: authority, headers: { Host: authority, ...proxyHeaders(proxy) }, agent: false, signal });
|
|
103
|
+
request.once('error', reject);
|
|
104
|
+
request.once('connect', (response, socket, head) => {
|
|
105
|
+
if (response.statusCode !== 200) {
|
|
106
|
+
socket.destroy();
|
|
107
|
+
reject(proxyError(response.statusCode === 407 ? 'update_proxy_auth_required' : 'update_proxy_failed',
|
|
108
|
+
'The proxy refused the secure update connection (HTTP ' + response.statusCode + ').', response.statusCode >= 500));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (signal.aborted) { socket.destroy(); reject(signal.reason); return; }
|
|
112
|
+
if (head.length) socket.unshift(head);
|
|
113
|
+
const secure = tls.connect({ socket, host: hostname(target),
|
|
114
|
+
servername: net.isIP(hostname(target)) ? undefined : hostname(target), ALPNProtocols: ['http/1.1'] });
|
|
115
|
+
const abort = () => secure.destroy(signal.reason);
|
|
116
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
117
|
+
secure.once('close', () => signal.removeEventListener('abort', abort));
|
|
118
|
+
secure.once('error', reject);
|
|
119
|
+
secure.once('secureConnect', () => resolve(secure));
|
|
120
|
+
});
|
|
121
|
+
request.end();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function proxyRequest(target, proxy, init) {
|
|
126
|
+
let agent;
|
|
127
|
+
if (target.protocol === 'https:') {
|
|
128
|
+
const socket = await tunnel(target, proxy, init.signal);
|
|
129
|
+
if (init.signal.aborted) { socket.destroy(); throw init.signal.reason; }
|
|
130
|
+
agent = new https.Agent({ keepAlive: false });
|
|
131
|
+
agent.createConnection = () => socket;
|
|
132
|
+
}
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
const headers = { ...Object.fromEntries(new Headers(init.headers || {})), Host: target.host };
|
|
135
|
+
const secure = target.protocol === 'https:';
|
|
136
|
+
const client = secure || proxy.protocol === 'https:' ? https : http;
|
|
137
|
+
const request = secure
|
|
138
|
+
? client.request(target, { method: init.method, headers, agent, signal: init.signal })
|
|
139
|
+
: client.request({ protocol: proxy.protocol, hostname: hostname(proxy), port: proxy.port,
|
|
140
|
+
method: init.method, path: target.href, headers: { ...headers, ...proxyHeaders(proxy) }, agent: false, signal: init.signal });
|
|
141
|
+
request.once('error', error => { agent?.destroy(); reject(error); });
|
|
142
|
+
request.once('response', incoming => {
|
|
143
|
+
incoming.once('close', () => agent?.destroy());
|
|
144
|
+
const headers = new Headers();
|
|
145
|
+
for (let i = 0; i < incoming.rawHeaders.length; i += 2) headers.append(incoming.rawHeaders[i], incoming.rawHeaders[i + 1]);
|
|
146
|
+
const empty = init.method === 'HEAD' || [204, 205, 304].includes(incoming.statusCode);
|
|
147
|
+
if (empty) incoming.resume();
|
|
148
|
+
const response = new Response(empty ? null : Readable.toWeb(incoming),
|
|
149
|
+
{ status: incoming.statusCode, statusText: incoming.statusMessage, headers });
|
|
150
|
+
Object.defineProperty(response, 'url', { value: target.href });
|
|
151
|
+
resolve(response);
|
|
152
|
+
});
|
|
153
|
+
request.end();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function fetchRelease(url, init, options = {}) {
|
|
158
|
+
const proxy = resolveReleaseProxy(url, options);
|
|
159
|
+
if (!proxy) return globalThis.fetch(url, init);
|
|
160
|
+
let target = new URL(url);
|
|
161
|
+
for (let redirect = 0; redirect <= 5; redirect += 1) {
|
|
162
|
+
const response = await proxyRequest(target, new URL(proxy.url), init);
|
|
163
|
+
if (![301, 302, 303, 307, 308].includes(response.status)) return response;
|
|
164
|
+
const location = response.headers.get('location');
|
|
165
|
+
if (!location) return response;
|
|
166
|
+
if (response.body) await response.body.cancel();
|
|
167
|
+
const next = new URL(location, target);
|
|
168
|
+
if (!['http:', 'https:'].includes(next.protocol) || target.protocol === 'https:' && next.protocol !== 'https:' ||
|
|
169
|
+
options.allowedHosts && !options.allowedHosts.has(next.hostname)) {
|
|
170
|
+
throw proxyError('update_asset_redirect_forbidden', 'Release request redirected to an untrusted host.');
|
|
171
|
+
}
|
|
172
|
+
target = next;
|
|
173
|
+
}
|
|
174
|
+
throw proxyError('update_network_redirect_limit', 'The update request exceeded its redirect limit.');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = { fetchRelease, resolveReleaseProxy };
|