codex-overleaf-link 2.3.5 → 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 +31 -24
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +4 -0
- package/extension/src/backgroundUpdateCoordinator.js +88 -4
- package/extension/src/content/contentRuntime.js +24 -7
- package/extension/src/content/diagnosticsController.js +13 -3
- package/extension/src/content/diagnosticsPanel.js +1 -1
- package/extension/src/content/generated/content.bundle.js +848 -245
- package/extension/src/content/generated/content.bundle.meta.json +6 -3
- package/extension/src/content/legacyGlobalRegistry.js +2 -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/providerSettingsCoordinator.js +1 -0
- 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 +28 -11
- 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/i18n.js +77 -37
- 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 +73 -14
- 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
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
if (typeof module === 'object' && module.exports) {
|
|
3
3
|
module.exports = factory(root);
|
|
4
4
|
} else {
|
|
5
|
+
if (typeof root.CodexOverleafRealtimeObserver?.dispose === 'function') {
|
|
6
|
+
root.CodexOverleafRealtimeObserver.dispose();
|
|
7
|
+
}
|
|
5
8
|
root.CodexOverleafRealtimeObserver = factory(root);
|
|
6
9
|
}
|
|
7
10
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function overleafRealtimeObserverFactory(root) {
|
|
@@ -12,8 +15,34 @@
|
|
|
12
15
|
const BASELINE_REFRESH_EVENT_TYPES = ['click', 'focusin', 'change'];
|
|
13
16
|
const CHANNEL_KEY_PATTERN = /socket|websocket|channel|realtime|collab|share|ot|doc|editor|connection|event|broadcast|presence/i;
|
|
14
17
|
const SENSITIVE_KEY_PATTERN = /^(content|previousContent|nextContent|text|body|raw|rawContent|source|sourceText)$/i;
|
|
18
|
+
const LIMITS = Object.freeze({ maxFileBytes: 1024 * 1024, maxEvents: 64, maxQueuedBytes: 4 * 1024 * 1024 });
|
|
19
|
+
const encoder = typeof TextEncoder === 'function' ? new TextEncoder() : null;
|
|
20
|
+
const ownedObservers = new WeakMap();
|
|
15
21
|
|
|
16
22
|
function create(deps = {}) {
|
|
23
|
+
const documentRef = Object.prototype.hasOwnProperty.call(deps, 'document')
|
|
24
|
+
? deps.document
|
|
25
|
+
: getDefaultDocument();
|
|
26
|
+
dispose(documentRef);
|
|
27
|
+
const observer = createObserver(deps);
|
|
28
|
+
if (documentRef && ['object', 'function'].includes(typeof documentRef)) {
|
|
29
|
+
ownedObservers.set(documentRef, observer);
|
|
30
|
+
}
|
|
31
|
+
return observer;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function dispose(documentRef = getDefaultDocument()) {
|
|
35
|
+
if (!documentRef || !['object', 'function'].includes(typeof documentRef)) return;
|
|
36
|
+
const previous = ownedObservers.get(documentRef);
|
|
37
|
+
if (!previous) return;
|
|
38
|
+
try {
|
|
39
|
+
previous.stop();
|
|
40
|
+
} finally {
|
|
41
|
+
ownedObservers.delete(documentRef);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createObserver(deps = {}) {
|
|
17
46
|
const pageWindow = deps.window || getDefaultWindow();
|
|
18
47
|
const pageDocument = Object.prototype.hasOwnProperty.call(deps, 'document')
|
|
19
48
|
? deps.document
|
|
@@ -21,6 +50,9 @@
|
|
|
21
50
|
const events = [];
|
|
22
51
|
let activePath = '';
|
|
23
52
|
let lastContent = '';
|
|
53
|
+
let baselineByteCount = 0;
|
|
54
|
+
let queuedTextBytes = 0;
|
|
55
|
+
let coalescedEventCount = 0;
|
|
24
56
|
let running = false;
|
|
25
57
|
let listenersAttached = false;
|
|
26
58
|
let statusName = 'off';
|
|
@@ -31,6 +63,7 @@
|
|
|
31
63
|
|
|
32
64
|
function start(_params = {}) {
|
|
33
65
|
clearQueuedEvents();
|
|
66
|
+
coalescedEventCount = 0;
|
|
34
67
|
channelCandidates = collectChannelCandidates(pageWindow);
|
|
35
68
|
if (!canAttachDocumentListeners()) {
|
|
36
69
|
running = false;
|
|
@@ -40,13 +73,15 @@
|
|
|
40
73
|
|
|
41
74
|
running = true;
|
|
42
75
|
refreshActiveBaseline();
|
|
43
|
-
attachDocumentListeners();
|
|
76
|
+
if (running) attachDocumentListeners();
|
|
44
77
|
return getStatus();
|
|
45
78
|
}
|
|
46
79
|
|
|
47
80
|
function stop() {
|
|
48
81
|
detachDocumentListeners();
|
|
49
82
|
clearQueuedEvents();
|
|
83
|
+
lastContent = '';
|
|
84
|
+
baselineByteCount = 0;
|
|
50
85
|
running = false;
|
|
51
86
|
statusName = 'off';
|
|
52
87
|
statusReason = '';
|
|
@@ -61,6 +96,9 @@
|
|
|
61
96
|
strategy: STRATEGY_ACTIVE_EDITOR,
|
|
62
97
|
activePath,
|
|
63
98
|
queuedEventCount: events.length,
|
|
99
|
+
queuedTextBytes,
|
|
100
|
+
baselineByteCount,
|
|
101
|
+
coalescedEventCount,
|
|
64
102
|
lastEventAt,
|
|
65
103
|
lastErrorCode,
|
|
66
104
|
channelCandidates: cloneChannelCandidates(channelCandidates)
|
|
@@ -72,13 +110,60 @@
|
|
|
72
110
|
}
|
|
73
111
|
|
|
74
112
|
function drainEvents() {
|
|
113
|
+
// Capture-phase input can precede the editor transaction; paste, undo
|
|
114
|
+
// and collaboration updates may dispatch no DOM input at all. The
|
|
115
|
+
// existing bounded poll samples committed text before draining.
|
|
116
|
+
if (running) handleEditorInput();
|
|
75
117
|
const drained = events.slice();
|
|
76
118
|
clearQueuedEvents();
|
|
77
|
-
return
|
|
119
|
+
if (!drained.length) return [];
|
|
120
|
+
const otText = resolveOtText(deps, pageWindow, root);
|
|
121
|
+
if (typeof otText?.normalizeObservedTextEvent !== 'function') {
|
|
122
|
+
stopObservation('missing_ot_text');
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
const normalized = [];
|
|
126
|
+
for (const pending of drained) {
|
|
127
|
+
let event;
|
|
128
|
+
const { previousBytes, nextBytes, ...input } = pending;
|
|
129
|
+
try { event = otText.normalizeObservedTextEvent(input); } catch (_error) { /* fail closed below */ }
|
|
130
|
+
if (!event || event.ok !== true) {
|
|
131
|
+
stopObservation(event?.reason || 'normalize_observed_text_event_failed');
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
normalized.push(sanitizeObservedEvent(event));
|
|
135
|
+
}
|
|
136
|
+
return normalized;
|
|
78
137
|
}
|
|
79
138
|
|
|
80
139
|
function clearQueuedEvents() {
|
|
81
140
|
events.length = 0;
|
|
141
|
+
queuedTextBytes = 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function stopObservation(reason) {
|
|
145
|
+
stop();
|
|
146
|
+
markUnavailable(reason);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function queueObservedChange(event, previousBytes, nextBytes) {
|
|
150
|
+
const tail = events[events.length - 1];
|
|
151
|
+
const merge = tail?.path === event.path && tail.nextContent === event.previousContent;
|
|
152
|
+
const pending = { ...event, previousContent: merge ? tail.previousContent : event.previousContent,
|
|
153
|
+
previousBytes: merge ? tail.previousBytes : previousBytes, nextBytes };
|
|
154
|
+
const remainingBytes = queuedTextBytes - (merge ? tail.previousBytes + tail.nextBytes : 0);
|
|
155
|
+
const noChange = pending.previousContent === pending.nextContent;
|
|
156
|
+
const nextLength = events.length - (merge ? 1 : 0) + (noChange ? 0 : 1);
|
|
157
|
+
const nextTotal = remainingBytes + (noChange ? 0 : pending.previousBytes + pending.nextBytes);
|
|
158
|
+
if (nextLength > LIMITS.maxEvents || nextTotal > LIMITS.maxQueuedBytes) {
|
|
159
|
+
stopObservation('ot_queue_limit');
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
if (merge) { events.pop(); coalescedEventCount += 1; }
|
|
163
|
+
if (!noChange) events.push(pending);
|
|
164
|
+
queuedTextBytes = nextTotal;
|
|
165
|
+
lastEventAt = event.observedAt;
|
|
166
|
+
return true;
|
|
82
167
|
}
|
|
83
168
|
|
|
84
169
|
function canAttachDocumentListeners() {
|
|
@@ -118,12 +203,14 @@
|
|
|
118
203
|
|
|
119
204
|
const textResult = readEditorText();
|
|
120
205
|
if (!textResult.ok) {
|
|
121
|
-
|
|
206
|
+
if (textResult.reason === 'ot_file_limit') stopObservation(textResult.reason);
|
|
207
|
+
else markUnavailable(textResult.reason);
|
|
122
208
|
return false;
|
|
123
209
|
}
|
|
124
210
|
|
|
125
211
|
activePath = pathResult.path;
|
|
126
212
|
lastContent = textResult.text;
|
|
213
|
+
baselineByteCount = textResult.bytes;
|
|
127
214
|
markObserving();
|
|
128
215
|
return true;
|
|
129
216
|
}
|
|
@@ -144,16 +231,19 @@
|
|
|
144
231
|
|
|
145
232
|
const textResult = readEditorText();
|
|
146
233
|
if (!textResult.ok) {
|
|
147
|
-
|
|
234
|
+
if (textResult.reason === 'ot_file_limit') stopObservation(textResult.reason);
|
|
235
|
+
else markUnavailable(textResult.reason);
|
|
148
236
|
return;
|
|
149
237
|
}
|
|
150
238
|
|
|
151
239
|
activePath = pathResult.path;
|
|
152
240
|
lastContent = textResult.text;
|
|
241
|
+
baselineByteCount = textResult.bytes;
|
|
153
242
|
markObserving();
|
|
154
243
|
}
|
|
155
244
|
|
|
156
|
-
function handleEditorInput() {
|
|
245
|
+
function handleEditorInput(inputEvent) {
|
|
246
|
+
if (inputEvent?.target?.closest?.('#codex-overleaf-panel')) return;
|
|
157
247
|
if (!running) {
|
|
158
248
|
return;
|
|
159
249
|
}
|
|
@@ -166,7 +256,8 @@
|
|
|
166
256
|
|
|
167
257
|
const textResult = readEditorText();
|
|
168
258
|
if (!textResult.ok) {
|
|
169
|
-
|
|
259
|
+
if (textResult.reason === 'ot_file_limit') stopObservation(textResult.reason);
|
|
260
|
+
else markUnavailable(textResult.reason);
|
|
170
261
|
return;
|
|
171
262
|
}
|
|
172
263
|
|
|
@@ -175,6 +266,7 @@
|
|
|
175
266
|
// Fallback for file switches that were not preceded by a selection/focus event.
|
|
176
267
|
activePath = pathResult.path;
|
|
177
268
|
lastContent = nextContent;
|
|
269
|
+
baselineByteCount = textResult.bytes;
|
|
178
270
|
markObserving();
|
|
179
271
|
return;
|
|
180
272
|
}
|
|
@@ -185,34 +277,25 @@
|
|
|
185
277
|
}
|
|
186
278
|
|
|
187
279
|
const observedAt = resolveObservedAt();
|
|
188
|
-
const
|
|
189
|
-
if (!otText || typeof otText.normalizeObservedTextEvent !== 'function') {
|
|
190
|
-
markUnavailable('missing_ot_text');
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const event = otText.normalizeObservedTextEvent({
|
|
280
|
+
const queued = queueObservedChange({
|
|
195
281
|
path: activePath,
|
|
196
282
|
previousContent: lastContent,
|
|
197
283
|
nextContent,
|
|
198
284
|
observedAt,
|
|
199
285
|
source: STRATEGY_ACTIVE_EDITOR
|
|
200
|
-
});
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
lastEventAt = queuedEvent.observedAt || observedAt;
|
|
286
|
+
}, baselineByteCount, textResult.bytes);
|
|
287
|
+
if (queued) {
|
|
288
|
+
lastContent = nextContent;
|
|
289
|
+
baselineByteCount = textResult.bytes;
|
|
205
290
|
markObserving();
|
|
206
|
-
} else {
|
|
207
|
-
lastErrorCode = event?.reason || 'normalize_observed_text_event_failed';
|
|
208
291
|
}
|
|
209
|
-
lastContent = nextContent;
|
|
210
292
|
}
|
|
211
293
|
|
|
212
294
|
function handleActivePathUnavailable(reason) {
|
|
213
295
|
if (reason === 'missing_active_path') {
|
|
214
296
|
activePath = '';
|
|
215
297
|
lastContent = '';
|
|
298
|
+
baselineByteCount = 0;
|
|
216
299
|
}
|
|
217
300
|
markUnavailable(reason);
|
|
218
301
|
}
|
|
@@ -252,9 +335,17 @@
|
|
|
252
335
|
};
|
|
253
336
|
}
|
|
254
337
|
try {
|
|
338
|
+
const text = deps.readActiveEditorText();
|
|
339
|
+
if (typeof text !== 'string') {
|
|
340
|
+
return { ok: false, reason: 'missing_editor_content' };
|
|
341
|
+
}
|
|
342
|
+
const bytes = text.length > LIMITS.maxFileBytes ? LIMITS.maxFileBytes + 1
|
|
343
|
+
: encoder ? encoder.encode(text).byteLength : text.length * 3;
|
|
344
|
+
if (bytes > LIMITS.maxFileBytes) return { ok: false, reason: 'ot_file_limit' };
|
|
255
345
|
return {
|
|
256
346
|
ok: true,
|
|
257
|
-
text
|
|
347
|
+
text,
|
|
348
|
+
bytes
|
|
258
349
|
};
|
|
259
350
|
} catch (_error) {
|
|
260
351
|
return {
|
|
@@ -444,7 +535,6 @@
|
|
|
444
535
|
|
|
445
536
|
function normalizePath(value) {
|
|
446
537
|
return String(value || '')
|
|
447
|
-
.replace(/\s+/g, ' ')
|
|
448
538
|
.replace(/\\/g, '/')
|
|
449
539
|
.trim()
|
|
450
540
|
.replace(/^\/+/, '');
|
|
@@ -485,7 +575,9 @@
|
|
|
485
575
|
}
|
|
486
576
|
|
|
487
577
|
return {
|
|
578
|
+
LIMITS,
|
|
488
579
|
collectChannelCandidates,
|
|
489
|
-
create
|
|
580
|
+
create,
|
|
581
|
+
dispose
|
|
490
582
|
};
|
|
491
583
|
});
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
const MIN_NATIVE_VERSION = '1.0.0';
|
|
13
13
|
const MIN_COMPATIBLE_NATIVE_VERSION = '1.0.0';
|
|
14
14
|
const MIN_COMPATIBLE_EXTENSION_VERSION = '1.0.0';
|
|
15
|
-
const BUILD_TARGET_VERSION = '2.3.
|
|
15
|
+
const BUILD_TARGET_VERSION = '2.3.6';
|
|
16
16
|
const DEFAULT_CHROME_EXTENSION_ID = 'illdpneeeopfffmiepaejglgmhpmdhdc';
|
|
17
17
|
const REQUIRED_CAPABILITIES = Object.freeze([
|
|
18
18
|
'bridgePing',
|
|
@@ -40,20 +40,31 @@
|
|
|
40
40
|
diagnosticsPageSubtitle: 'Current file, write access, track changes',
|
|
41
41
|
diagnosticsSnapshotTitle: 'Check Project Read',
|
|
42
42
|
diagnosticsSnapshotSubtitle: 'Full project, assets, read source',
|
|
43
|
-
diagnosticsOtTitle: 'Check
|
|
43
|
+
diagnosticsOtTitle: 'Check OT Mirror',
|
|
44
44
|
diagnosticsOtSubtitle: 'Status, fresh files, fallback',
|
|
45
45
|
diagnosticsExportTitle: 'Export Diagnostics',
|
|
46
46
|
diagnosticsExportSubtitle: 'Redacted audit and environment bundle',
|
|
47
47
|
diagnosticsExportDone: 'Diagnostics bundle exported.',
|
|
48
|
-
diagnosticsOtSummaryEnabled: '
|
|
49
|
-
diagnosticsOtSummaryDisabled: '
|
|
50
|
-
diagnosticsOtNextStep: '
|
|
51
|
-
experimentalOtMenuTitle: '
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
diagnosticsOtSummaryEnabled: 'OT Mirror is enabled. Codex will use the OT-warmed mirror only when matching files are fresh; otherwise it falls back to the normal project read path.',
|
|
49
|
+
diagnosticsOtSummaryDisabled: 'OT Mirror is disabled, so Codex is using the normal project read path.',
|
|
50
|
+
diagnosticsOtNextStep: 'Observer status describes warming availability. File scope and freshness are checked again before each run.',
|
|
51
|
+
experimentalOtMenuTitle: 'OT Mirror',
|
|
52
|
+
otSettingsTitle: 'Project cache',
|
|
53
|
+
contextLoadingTitle: 'Context loading',
|
|
54
|
+
runFeedbackFailedTitle: 'Needs attention',
|
|
55
|
+
runFeedbackPartialTitle: 'Partially completed',
|
|
56
|
+
runFeedbackUnconfirmedTitle: 'Write status needs review',
|
|
57
|
+
runFeedbackGeneric: 'Open the run details for the reported cause and the next step.',
|
|
58
|
+
runFeedbackUnconfirmed: 'Some changes may already be in Overleaf. Check the document before retrying.',
|
|
59
|
+
runFeedbackUnconfirmedResult: 'Not fully confirmed. Review Overleaf before retrying.',
|
|
60
|
+
runFeedbackReviewFile: 'Review {file} in Overleaf',
|
|
61
|
+
runFeedbackReviewDetails: 'Review run details',
|
|
62
|
+
runFeedbackDetailsUnavailable: 'Detailed steps are unavailable for this older run.',
|
|
63
|
+
experimentalOtMenuSubtitle: 'Warms recent edits to the active text file locally. File scope and freshness are checked before reuse.',
|
|
64
|
+
experimentalOtConfirmTitle: 'Enable OT Mirror?',
|
|
65
|
+
experimentalOtConfirmMessage: 'Observes the active text editor and warms its local mirror. This is read-only with respect to Overleaf and does not upload assets. Each run checks file scope and freshness before reuse; normal project reading remains the fallback.',
|
|
55
66
|
experimentalOtConfirmEnable: 'Turn on',
|
|
56
|
-
experimentalOtEnabledToast: '
|
|
67
|
+
experimentalOtEnabledToast: 'OT Mirror requested for this project. Its status shows whether warming is available.',
|
|
57
68
|
diagnosticsSnapshotLoading: 'Checking whether the extension can read the full Overleaf project.',
|
|
58
69
|
diagnosticsSnapshotErrorSummary: 'Overleaf may still be loading, or the page API did not return project files yet.',
|
|
59
70
|
diagnosticsNativeLoading: 'Checking whether local Codex, Native Host, and LaTeX tools are available.',
|
|
@@ -326,18 +337,27 @@
|
|
|
326
337
|
requireReviewingTitle: 'When enabled, Codex checks or switches Overleaf Reviewing/Track Changes before writing. Deletes still require confirmation.',
|
|
327
338
|
autoCompile: 'Compile',
|
|
328
339
|
autoCompileTitle: 'After Codex writes, click Overleaf Recompile and record the compile result for this task. Ask mode will not trigger it.',
|
|
329
|
-
experimentalOtWarmMirror: '
|
|
340
|
+
experimentalOtWarmMirror: 'OT warm mirror',
|
|
330
341
|
experimentalOtWarmMirrorShort: 'OT',
|
|
331
|
-
experimentalOtWarmMirrorTitle: '
|
|
342
|
+
experimentalOtWarmMirrorTitle: 'Read-only local text warming. File scope and freshness are checked before reuse; normal project reading remains the fallback.',
|
|
332
343
|
otStatus: 'OT status',
|
|
333
|
-
otFreshFiles: 'OT
|
|
334
|
-
otFallback: '
|
|
344
|
+
otFreshFiles: 'Files with recent OT evidence',
|
|
345
|
+
otFallback: 'Read fallback',
|
|
335
346
|
otStatusOff: 'Off',
|
|
336
|
-
otStatusStarting: '
|
|
337
|
-
otStatusObserving: '
|
|
338
|
-
otStatusUnavailable: '
|
|
339
|
-
otStatusStale: '
|
|
340
|
-
otStatusInconsistent: '
|
|
347
|
+
otStatusStarting: 'Connecting',
|
|
348
|
+
otStatusObserving: 'Observing current editor',
|
|
349
|
+
otStatusUnavailable: 'Unavailable',
|
|
350
|
+
otStatusStale: 'Warming paused; awaiting verification',
|
|
351
|
+
otStatusInconsistent: 'Needs verification',
|
|
352
|
+
otFailureBridge: 'The page connection needs to be restored. Reload the extension and Overleaf before retrying.',
|
|
353
|
+
otFailureObserver: 'The editor observer is unavailable. The next run will try normal project reading.',
|
|
354
|
+
otFailureEditor: 'The active editor could not be read. Warming is paused; the next run will try normal project reading.',
|
|
355
|
+
otFailureFileLimit: 'This file exceeds the 1 MiB warming limit. The next run will try normal project reading.',
|
|
356
|
+
otFailureQueueLimit: 'The mirror queue reached its limit. Warming is paused; the next run will try normal project reading.',
|
|
357
|
+
otFailureExpired: 'The observation is too old to reuse. The next run will try normal project reading.',
|
|
358
|
+
otFailureMirror: 'The mirror update was not fully confirmed. The next run will try normal project reading.',
|
|
359
|
+
otFailureGeneric: 'Mirror warming could not continue. The next run will try normal project reading.',
|
|
360
|
+
otFailureNextStep: 'Resolve the reported cause, then enable OT again. If the page connection is unavailable, reload the extension and Overleaf first.',
|
|
341
361
|
modelSourceFallback: 'fallback',
|
|
342
362
|
modelSourceDiscovered: 'discovered',
|
|
343
363
|
modelSourceFailed: 'failed',
|
|
@@ -560,20 +580,31 @@
|
|
|
560
580
|
diagnosticsPageSubtitle: '当前文件、写入能力、留痕状态',
|
|
561
581
|
diagnosticsSnapshotTitle: '检查项目读取',
|
|
562
582
|
diagnosticsSnapshotSubtitle: '完整项目、资源文件、读取来源',
|
|
563
|
-
diagnosticsOtTitle: '
|
|
583
|
+
diagnosticsOtTitle: '检查 OT Mirror',
|
|
564
584
|
diagnosticsOtSubtitle: '状态、新鲜文件、回退路径',
|
|
565
585
|
diagnosticsExportTitle: '导出诊断信息',
|
|
566
586
|
diagnosticsExportSubtitle: '已脱敏的审计和环境信息包',
|
|
567
587
|
diagnosticsExportDone: '诊断信息已导出。',
|
|
568
|
-
diagnosticsOtSummaryEnabled: '
|
|
569
|
-
diagnosticsOtSummaryDisabled: '
|
|
570
|
-
diagnosticsOtNextStep: '
|
|
571
|
-
experimentalOtMenuTitle: '
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
588
|
+
diagnosticsOtSummaryEnabled: 'OT Mirror 已开启。只有匹配文件是新鲜状态时,Codex 才会使用 OT 预热镜像;否则会回退到常规项目读取路径。',
|
|
589
|
+
diagnosticsOtSummaryDisabled: 'OT Mirror 已关闭,Codex 会使用常规项目读取路径。',
|
|
590
|
+
diagnosticsOtNextStep: '观测状态说明预热是否可用;每次任务开始前仍会重新校验文件范围和新鲜度。',
|
|
591
|
+
experimentalOtMenuTitle: 'OT Mirror',
|
|
592
|
+
otSettingsTitle: '项目缓存',
|
|
593
|
+
contextLoadingTitle: '上下文加载',
|
|
594
|
+
runFeedbackFailedTitle: '需要处理',
|
|
595
|
+
runFeedbackPartialTitle: '部分完成',
|
|
596
|
+
runFeedbackUnconfirmedTitle: '写入状态待确认',
|
|
597
|
+
runFeedbackGeneric: '可在本轮详情中查看具体原因和后续处理方式。',
|
|
598
|
+
runFeedbackUnconfirmed: '部分改动可能已经写入 Overleaf,重试前请先检查文档。',
|
|
599
|
+
runFeedbackUnconfirmedResult: '结果尚未完全确认,请检查 Overleaf 后再决定是否重试。',
|
|
600
|
+
runFeedbackReviewFile: '在 Overleaf 检查 {file}',
|
|
601
|
+
runFeedbackReviewDetails: '查看运行步骤',
|
|
602
|
+
runFeedbackDetailsUnavailable: '这条历史记录没有可展开的详细步骤。',
|
|
603
|
+
experimentalOtMenuSubtitle: '在本地预热当前文本文件的最近改动,复用前重新校验文件范围和新鲜度。',
|
|
604
|
+
experimentalOtConfirmTitle: '开启 OT Mirror?',
|
|
605
|
+
experimentalOtConfirmMessage: '观测当前文本编辑器并预热本地镜像。此功能不会写回 Overleaf,也不负责上传资产。每次任务复用前都会校验文件范围和新鲜度,不满足条件时尝试常规项目读取。',
|
|
575
606
|
experimentalOtConfirmEnable: '开启',
|
|
576
|
-
experimentalOtEnabledToast: '
|
|
607
|
+
experimentalOtEnabledToast: '已为当前项目请求开启 OT Mirror,可用情况见状态提示。',
|
|
577
608
|
diagnosticsSnapshotLoading: '确认插件能否读取完整 Overleaf 项目。',
|
|
578
609
|
diagnosticsSnapshotErrorSummary: '这通常是 Overleaf 页面还没加载完成,或者当前页面接口暂时没有返回项目文件。',
|
|
579
610
|
diagnosticsNativeLoading: '确认本机 Codex、Native Host 和 LaTeX 工具是否可用。',
|
|
@@ -846,18 +877,27 @@
|
|
|
846
877
|
requireReviewingTitle: '开启后,写入前会确认并尝试切到 Overleaf Reviewing/Track Changes;删除仍需确认。',
|
|
847
878
|
autoCompile: '编译',
|
|
848
879
|
autoCompileTitle: 'Codex 写入后自动点击 Overleaf Recompile,并把编译结果记录到本轮任务。只问不改时不会触发。',
|
|
849
|
-
experimentalOtWarmMirror: '
|
|
880
|
+
experimentalOtWarmMirror: 'OT 预热镜像',
|
|
850
881
|
experimentalOtWarmMirrorShort: 'OT',
|
|
851
|
-
experimentalOtWarmMirrorTitle: '
|
|
882
|
+
experimentalOtWarmMirrorTitle: '只读本地文本预热;复用前校验文件范围和新鲜度,常规项目读取作为回退路径。',
|
|
852
883
|
otStatus: 'OT 状态',
|
|
853
|
-
otFreshFiles: 'OT
|
|
854
|
-
otFallback: '
|
|
855
|
-
otStatusOff: '
|
|
856
|
-
otStatusStarting: '
|
|
857
|
-
otStatusObserving: '
|
|
858
|
-
otStatusUnavailable: '
|
|
859
|
-
otStatusStale: '
|
|
860
|
-
otStatusInconsistent: '
|
|
884
|
+
otFreshFiles: '近期有 OT 校验证据的文件',
|
|
885
|
+
otFallback: '读取回退',
|
|
886
|
+
otStatusOff: '未开启',
|
|
887
|
+
otStatusStarting: '正在连接',
|
|
888
|
+
otStatusObserving: '正在观测当前编辑器',
|
|
889
|
+
otStatusUnavailable: '暂不可用',
|
|
890
|
+
otStatusStale: '预热暂缓,等待重新校验',
|
|
891
|
+
otStatusInconsistent: '需重新校验',
|
|
892
|
+
otFailureBridge: '页面连接需要恢复,请先重新加载扩展并刷新 Overleaf,再重试。',
|
|
893
|
+
otFailureObserver: '编辑器观察器暂不可用,下次任务将尝试常规项目读取。',
|
|
894
|
+
otFailureEditor: '当前编辑器内容尚不可读,预热已暂停;下次任务将尝试常规项目读取。',
|
|
895
|
+
otFailureFileLimit: '文件超过 1 MiB 预热上限,下次任务将尝试常规项目读取。',
|
|
896
|
+
otFailureQueueLimit: '镜像队列达到容量上限,预热已暂停;下次任务将尝试常规项目读取。',
|
|
897
|
+
otFailureExpired: '观测记录已过期,下次任务将尝试常规项目读取。',
|
|
898
|
+
otFailureMirror: '镜像更新尚未完整确认,下次任务将尝试常规项目读取。',
|
|
899
|
+
otFailureGeneric: '镜像预热未能继续,下次任务将尝试常规项目读取。',
|
|
900
|
+
otFailureNextStep: '处理所显示的原因后重新开启 OT;若页面连接不可用,请先重新加载扩展并刷新 Overleaf。',
|
|
861
901
|
modelSourceFallback: '备用列表',
|
|
862
902
|
modelSourceDiscovered: '已发现',
|
|
863
903
|
modelSourceFailed: '获取失败',
|
|
@@ -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) {
|