codex-overleaf-link 2.1.5 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -23
- package/extension/bootstrap/background.js +14 -1
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/runtime-manifest.json +10 -0
- package/extension/src/background.js +507 -8
- package/extension/src/backgroundUpdateCoordinator.js +284 -19
- package/extension/src/content/activeTurnControl.js +179 -0
- package/extension/src/content/composerPanel.js +14 -5
- package/extension/src/content/contentRuntime.js +330 -309
- package/extension/src/content/diagnosticsPanel.js +132 -1
- package/extension/src/content/markdownDomRenderer.js +451 -0
- package/extension/src/content/markdownText.js +86 -7
- package/extension/src/content/mathText.js +49 -4
- package/extension/src/content/modelPicker.js +14 -5
- package/extension/src/content/nativeChannel.js +7 -1
- package/extension/src/content/panelRenderer.js +112 -12
- package/extension/src/content/pendingInputView.js +106 -0
- package/extension/src/content/projectProviderSelection.js +68 -0
- package/extension/src/content/projectSessionCleanup.js +4 -0
- package/extension/src/content/providerSettingsCoordinator.js +170 -22
- package/extension/src/content/providerSettingsDialog.js +2 -2
- package/extension/src/content/recentProjects.js +7 -2
- package/extension/src/content/runGuidanceController.js +70 -0
- package/extension/src/content/runGuidanceView.js +48 -0
- package/extension/src/content/runQueueScheduler.js +260 -0
- package/extension/src/content/runTimelineView.js +33 -80
- package/extension/src/content/sessionManager.js +2 -2
- package/extension/src/content/sessionPersistence.js +62 -0
- package/extension/src/content/settingsPanel.js +1 -1
- package/extension/src/content/updateNotice.js +80 -1
- package/extension/src/page/writebackRouter.js +3 -9
- package/extension/src/shared/compatibility.js +2 -1
- package/extension/src/shared/i18n.js +38 -0
- package/extension/src/shared/providerProfiles.js +3 -2
- package/extension/src/shared/runInputQueue.js +253 -0
- package/extension/src/shared/sessionState.js +45 -22
- package/extension/src/shared/storageDb.js +39 -44
- package/extension/src/shared/storageMigration.js +123 -3
- package/extension/src/shared/storageRunActions.js +34 -1
- package/extension/src/shared/updateConsent.js +15 -4
- package/extension/styles/panel.css +319 -20
- package/extension/vendor/katex/katex.min.css +1 -1
- package/extension/vendor/markdown-it/LICENSE +22 -0
- package/extension/vendor/markdown-it/SOURCE.json +7 -0
- package/extension/vendor/markdown-it/VERSION +1 -0
- package/extension/vendor/markdown-it/markdown-it.min.js +3 -0
- package/native-host/src/anthropicBridgeRequest.js +3 -3
- package/native-host/src/anthropicBridgeResponse.js +58 -17
- package/native-host/src/anthropicMessagesBridge.js +156 -23
- package/native-host/src/chatBridgeRequest.js +1 -1
- package/native-host/src/chatBridgeResponse.js +12 -8
- package/native-host/src/chatCompletionsBridge.js +22 -40
- package/native-host/src/codexPromptAssembly.js +3 -1
- package/native-host/src/codexProviderLaunch.js +6 -3
- package/native-host/src/codexSessionRunner.js +65 -65
- package/native-host/src/codexSessionTiming.js +45 -0
- package/native-host/src/index.js +58 -3
- package/native-host/src/managedInstall.js +45 -7
- package/native-host/src/mirrorWorkspace.js +15 -8
- package/native-host/src/providerProfile.js +2 -2
- package/native-host/src/providerStreamLifecycle.js +100 -0
- package/native-host/src/responsesPassthroughBridge.js +636 -0
- package/native-host/src/taskRunnerRuntime.js +81 -2
- package/native-host/src/updateArchive.js +4 -3
- package/native-host/src/updateManager.js +306 -19
- package/native-host/src/updateTrust.js +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
(function initCodexOverleafRunQueueScheduler(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory(require('../shared/runInputQueue'));
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafRunQueueScheduler = factory(root.CodexOverleafRunInputQueue);
|
|
6
|
+
}
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function runQueueSchedulerFactory(Queue) {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
function create(options = {}) {
|
|
11
|
+
let scheduling = false;
|
|
12
|
+
|
|
13
|
+
function dequeue(queue, itemId, context = {}) {
|
|
14
|
+
const item = itemId ? queue.find(entry => entry.id === itemId) : null;
|
|
15
|
+
if (item) {
|
|
16
|
+
options.onDequeued?.(item, context);
|
|
17
|
+
}
|
|
18
|
+
return itemId ? Queue.remove(queue, itemId) : queue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function afterSettlement({ sessionId, status, queueItemId = '', continueQueue = false } = {}) {
|
|
22
|
+
let queue = options.getQueue?.(sessionId) || [];
|
|
23
|
+
if (status === 'completed') {
|
|
24
|
+
queue = dequeue(queue, queueItemId, { sessionId, reason: 'completed' });
|
|
25
|
+
} else if (continueQueue) {
|
|
26
|
+
queue = dequeue(queue, queueItemId, { sessionId, reason: 'continue_queue' });
|
|
27
|
+
} else if (queue.length) {
|
|
28
|
+
queue = Queue.pauseAll(queue, status || 'run_did_not_complete');
|
|
29
|
+
}
|
|
30
|
+
options.setQueue?.(sessionId, queue);
|
|
31
|
+
await options.persist?.();
|
|
32
|
+
options.onChange?.(sessionId);
|
|
33
|
+
if (status === 'completed' || continueQueue) {
|
|
34
|
+
scheduleOne(sessionId);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function scheduleOne(sessionId) {
|
|
39
|
+
if (scheduling || options.isRunning?.() || options.canStart?.(sessionId) === false) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const claimed = Queue.claimNext(options.getQueue?.(sessionId) || [], {
|
|
43
|
+
randomUUID: options.randomUUID
|
|
44
|
+
});
|
|
45
|
+
if (!claimed.ok) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
scheduling = true;
|
|
49
|
+
options.setQueue?.(sessionId, claimed.queue);
|
|
50
|
+
Promise.resolve(options.persist?.())
|
|
51
|
+
.then(() => {
|
|
52
|
+
options.onChange?.(sessionId);
|
|
53
|
+
scheduling = false;
|
|
54
|
+
return options.start?.(claimed.item, sessionId);
|
|
55
|
+
})
|
|
56
|
+
.catch(error => {
|
|
57
|
+
scheduling = false;
|
|
58
|
+
const paused = Queue.pauseAll(options.getQueue?.(sessionId) || claimed.queue, error?.code || 'start_failed');
|
|
59
|
+
options.setQueue?.(sessionId, paused);
|
|
60
|
+
options.onChange?.(sessionId);
|
|
61
|
+
return options.persist?.();
|
|
62
|
+
});
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function markExecuting(sessionId, itemId, _runId) {
|
|
67
|
+
const queue = options.getQueue?.(sessionId) || [];
|
|
68
|
+
options.setQueue?.(
|
|
69
|
+
sessionId,
|
|
70
|
+
dequeue(queue, itemId, { sessionId, runId: _runId, reason: 'executing' })
|
|
71
|
+
);
|
|
72
|
+
await options.persist?.();
|
|
73
|
+
options.onChange?.(sessionId);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function resume(sessionId) {
|
|
77
|
+
options.setQueue?.(sessionId, Queue.resumeAll(options.getQueue?.(sessionId) || []));
|
|
78
|
+
await options.persist?.();
|
|
79
|
+
options.onChange?.(sessionId);
|
|
80
|
+
scheduleOne(sessionId);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { afterSettlement, markExecuting, resume, scheduleOne };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function createCoordinator(options = {}) {
|
|
87
|
+
let view = null;
|
|
88
|
+
const scheduler = create({
|
|
89
|
+
getQueue: sessionId => options.getSession?.(sessionId)?.pendingInputs || [],
|
|
90
|
+
setQueue: (sessionId, pendingInputs) => options.setQueue?.(sessionId, pendingInputs),
|
|
91
|
+
persist: () => options.save?.(),
|
|
92
|
+
isRunning: () => Boolean(options.getCurrentRun?.()),
|
|
93
|
+
canStart: sessionId => options.canStart?.(sessionId) !== false,
|
|
94
|
+
start: (item, sessionId) => startQueuedInput(item, sessionId),
|
|
95
|
+
onChange: () => render(),
|
|
96
|
+
onDequeued: (item, context) => options.removeGuidance?.(item.id, {
|
|
97
|
+
sessionId: context.sessionId,
|
|
98
|
+
recordId: item.sourceRunId || ''
|
|
99
|
+
}),
|
|
100
|
+
randomUUID: options.randomUUID
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
function queueComposerInput() {
|
|
104
|
+
options.readInputs?.();
|
|
105
|
+
const text = String(options.getTask?.() || '').trim();
|
|
106
|
+
if (!text) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (options.hasAttachments?.()) {
|
|
110
|
+
options.toast?.(options.tr?.('queuedInputAttachmentsUnsupported'), 'warning');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const session = options.getActiveSession?.();
|
|
114
|
+
const result = Queue.enqueue(session?.pendingInputs || [], {
|
|
115
|
+
text,
|
|
116
|
+
sourceRunId: options.getCurrentRun?.()?.recordId || '',
|
|
117
|
+
payload: options.capturePayload?.() || {}
|
|
118
|
+
}, {
|
|
119
|
+
randomUUID: options.randomUUID
|
|
120
|
+
});
|
|
121
|
+
if (!result.ok) {
|
|
122
|
+
options.toast?.(options.tr?.('queuedInputError_' + (result.error?.code || 'queue_full')), 'warning');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
options.setQueue?.(session.id, result.queue);
|
|
126
|
+
// A queued follow-up has not reached the active model turn yet. Keep it
|
|
127
|
+
// exclusively in the queue UI until the user explicitly guides the
|
|
128
|
+
// active turn, or until the scheduler promotes it to the next run.
|
|
129
|
+
options.clearTask?.();
|
|
130
|
+
render();
|
|
131
|
+
options.saveSoon?.();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function guide(itemId) {
|
|
135
|
+
const session = options.getActiveSession?.();
|
|
136
|
+
const item = (session?.pendingInputs || []).find(entry => entry.id === itemId);
|
|
137
|
+
const run = options.getCurrentRun?.();
|
|
138
|
+
const binding = run?.activeTurn;
|
|
139
|
+
if (!session || !item || !run || run.sessionId !== session.id || !binding?.threadId || !binding?.turnId) {
|
|
140
|
+
options.toast?.(options.tr?.('queuedInputGuideUnavailable'), 'warning');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
options.setQueue?.(session.id, Queue.markSteering(session.pendingInputs, itemId));
|
|
144
|
+
render();
|
|
145
|
+
await options.save?.();
|
|
146
|
+
try {
|
|
147
|
+
const response = await options.sendSteer?.({
|
|
148
|
+
requestId: run.nativeRequestId,
|
|
149
|
+
projectKey: run.runProjectId,
|
|
150
|
+
threadId: binding.threadId,
|
|
151
|
+
expectedTurnId: binding.turnId,
|
|
152
|
+
clientUserMessageId: item.clientUserMessageId,
|
|
153
|
+
input: [{ type: 'text', text: item.text }]
|
|
154
|
+
});
|
|
155
|
+
const latest = options.getSession?.(session.id);
|
|
156
|
+
options.setQueue?.(
|
|
157
|
+
session.id,
|
|
158
|
+
response?.ok
|
|
159
|
+
? Queue.remove(latest?.pendingInputs || [], itemId)
|
|
160
|
+
: Queue.returnToQueue(latest?.pendingInputs || [], itemId, response?.error?.code || 'steer_failed')
|
|
161
|
+
);
|
|
162
|
+
if (response?.ok) {
|
|
163
|
+
if (typeof options.appendGuidance === 'function') {
|
|
164
|
+
options.appendGuidance(item.text, {
|
|
165
|
+
guidanceId: item.id,
|
|
166
|
+
sessionId: session.id,
|
|
167
|
+
recordId: run.recordId,
|
|
168
|
+
status: 'completed'
|
|
169
|
+
});
|
|
170
|
+
} else {
|
|
171
|
+
options.appendEvent?.(options.tr?.('queuedInputGuided'), 'completed');
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
options.toast?.(response?.error?.message || options.tr?.('queuedInputGuideFailed'), 'warning');
|
|
175
|
+
}
|
|
176
|
+
render();
|
|
177
|
+
await options.save?.();
|
|
178
|
+
} catch (error) {
|
|
179
|
+
const latest = options.getSession?.(session.id);
|
|
180
|
+
const paused = (latest?.pendingInputs || []).map(entry => entry.id === itemId
|
|
181
|
+
? { ...entry, status: 'paused', pauseReason: 'steer_delivery_unknown' }
|
|
182
|
+
: entry);
|
|
183
|
+
options.setQueue?.(session.id, Queue.normalizeQueue(paused));
|
|
184
|
+
options.toast?.(error?.message || options.tr?.('queuedInputGuideFailed'), 'warning');
|
|
185
|
+
render();
|
|
186
|
+
await options.save?.();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function remove(itemId) {
|
|
191
|
+
const session = options.getActiveSession?.();
|
|
192
|
+
if (!session) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const item = (session.pendingInputs || []).find(entry => entry.id === itemId);
|
|
196
|
+
if (item) {
|
|
197
|
+
options.removeGuidance?.(item.id, {
|
|
198
|
+
sessionId: session.id,
|
|
199
|
+
recordId: item.sourceRunId || ''
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
options.setQueue?.(session.id, Queue.remove(session.pendingInputs || [], itemId));
|
|
203
|
+
render();
|
|
204
|
+
options.saveSoon?.();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function resume() {
|
|
208
|
+
const session = options.getActiveSession?.();
|
|
209
|
+
if (session) {
|
|
210
|
+
scheduler.resume(session.id).catch(error => options.toast?.(error.message, 'warning'));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function render() {
|
|
215
|
+
const container = options.getContainer?.();
|
|
216
|
+
if (!container) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (!view || view.container !== container) {
|
|
220
|
+
view = options.createView?.({
|
|
221
|
+
container,
|
|
222
|
+
tr: options.tr,
|
|
223
|
+
onGuide: itemId => guide(itemId).catch(error => options.toast?.(error.message, 'warning')),
|
|
224
|
+
onRemove: remove,
|
|
225
|
+
onResume: resume
|
|
226
|
+
});
|
|
227
|
+
if (view) {
|
|
228
|
+
view.container = container;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const session = options.getActiveSession?.();
|
|
232
|
+
const run = options.getCurrentRun?.();
|
|
233
|
+
view?.render?.(session?.pendingInputs || [], {
|
|
234
|
+
running: Boolean(run),
|
|
235
|
+
canGuide: Boolean(run?.activeTurn?.turnId)
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function startQueuedInput(item, sessionId) {
|
|
240
|
+
if (options.canStart?.(sessionId) === false || options.getCurrentRun?.()) {
|
|
241
|
+
const error = new Error('Queued input cannot start in the current session.');
|
|
242
|
+
error.code = 'queue_session_not_active';
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
options.applyQueuedInput?.(item);
|
|
246
|
+
await options.run?.(item);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
guide,
|
|
251
|
+
queueComposerInput,
|
|
252
|
+
remove,
|
|
253
|
+
render,
|
|
254
|
+
resume,
|
|
255
|
+
scheduler
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return { create, createCoordinator };
|
|
260
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
(function initCodexOverleafRunTimelineView() {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
const RunGuidanceView = window.CodexOverleafRunGuidanceView;
|
|
4
5
|
// Run-timeline view — the timeline render pipeline carved out of
|
|
5
6
|
// contentRuntime.js (v1.4.6 structural-debt phase 2): the scroll engine +
|
|
6
7
|
// jump-to-latest button, the live-elapsed tick and collapsed-header summary,
|
|
@@ -43,7 +44,6 @@
|
|
|
43
44
|
} = deps;
|
|
44
45
|
|
|
45
46
|
let logAutoFollow = true;
|
|
46
|
-
let runSearchQuery = '';
|
|
47
47
|
let userScrollIntentUntil = 0;
|
|
48
48
|
// Scroll engine: a single rAF coalesces a burst of scroll requests into one
|
|
49
49
|
// write per frame (streaming can fire ~25/sec); `scrollLogPendingForce`
|
|
@@ -308,86 +308,12 @@
|
|
|
308
308
|
truncated.textContent = tr('runsTruncatedNote');
|
|
309
309
|
log.append(truncated);
|
|
310
310
|
}
|
|
311
|
-
// In-session search (v1.8.0): filter run cards by task or report text.
|
|
312
|
-
// Rendered with the same >= 3 threshold as the turn navigator; filtering
|
|
313
|
-
// is pure display (hidden attribute), state is kept across re-renders.
|
|
314
|
-
if (runs.length < 3 && runSearchQuery) {
|
|
315
|
-
// No search box is rendered below this threshold — a stale query from a
|
|
316
|
-
// bigger session would silently blank this log with no way to clear it.
|
|
317
|
-
runSearchQuery = '';
|
|
318
|
-
}
|
|
319
|
-
if (runs.length >= 3) {
|
|
320
|
-
const search = document.createElement('input');
|
|
321
|
-
search.type = 'search';
|
|
322
|
-
search.className = 'run-search';
|
|
323
|
-
search.setAttribute('data-run-search', '');
|
|
324
|
-
search.placeholder = tr('runSearchPlaceholder');
|
|
325
|
-
search.setAttribute('aria-label', tr('runSearchPlaceholder'));
|
|
326
|
-
search.value = runSearchQuery;
|
|
327
|
-
search.addEventListener('input', () => {
|
|
328
|
-
runSearchQuery = search.value;
|
|
329
|
-
applyRunSearchFilter(log);
|
|
330
|
-
});
|
|
331
|
-
log.append(search);
|
|
332
|
-
}
|
|
333
|
-
// Turn navigation (v1.7.5): once the log holds enough turns that
|
|
334
|
-
// scroll-hunting hurts, offer a jump-to-turn dropdown pinned above them.
|
|
335
|
-
if (runs.length >= 3) {
|
|
336
|
-
const nav = document.createElement('select');
|
|
337
|
-
nav.className = 'run-turn-nav';
|
|
338
|
-
nav.setAttribute('data-run-turn-nav', '');
|
|
339
|
-
nav.setAttribute('aria-label', tr('runTurnNavLabel'));
|
|
340
|
-
const placeholder = document.createElement('option');
|
|
341
|
-
placeholder.value = '';
|
|
342
|
-
placeholder.textContent = tr('runTurnNavLabel');
|
|
343
|
-
nav.append(placeholder);
|
|
344
|
-
runs.forEach((run, index) => {
|
|
345
|
-
const option = document.createElement('option');
|
|
346
|
-
option.value = run.id;
|
|
347
|
-
option.textContent = `#${index + 1} ${String(run.task || '').replace(/\s+/g, ' ').slice(0, 48)}`;
|
|
348
|
-
nav.append(option);
|
|
349
|
-
});
|
|
350
|
-
nav.addEventListener('change', () => {
|
|
351
|
-
if (!nav.value) {
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
const card = log.querySelector(`[data-run-id="${cssEscape(nav.value)}"]`);
|
|
355
|
-
card?.scrollIntoView({ block: 'start' });
|
|
356
|
-
nav.value = '';
|
|
357
|
-
});
|
|
358
|
-
log.append(nav);
|
|
359
|
-
}
|
|
360
311
|
for (const run of runs) {
|
|
361
312
|
log.append(renderRunCard(run));
|
|
362
313
|
}
|
|
363
|
-
if (runSearchQuery) {
|
|
364
|
-
applyRunSearchFilter(log);
|
|
365
|
-
}
|
|
366
314
|
scrollLogToBottom({ force: true });
|
|
367
315
|
}
|
|
368
316
|
|
|
369
|
-
// Case-insensitive substring match over the run's task text and its
|
|
370
|
-
// report/event narration. Empty query shows everything again.
|
|
371
|
-
function applyRunSearchFilter(log) {
|
|
372
|
-
const query = String(runSearchQuery || '').trim().toLowerCase();
|
|
373
|
-
const state = getState();
|
|
374
|
-
const matchingIds = new Set();
|
|
375
|
-
if (query) {
|
|
376
|
-
for (const run of state.runs || []) {
|
|
377
|
-
const haystack = [
|
|
378
|
-
run.task || '',
|
|
379
|
-
...(run.events || []).map(event => `${event.title || ''} ${typeof event.detail === 'string' ? event.detail : ''}`)
|
|
380
|
-
].join(' ').toLowerCase();
|
|
381
|
-
if (haystack.includes(query)) {
|
|
382
|
-
matchingIds.add(run.id);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
for (const card of log.querySelectorAll('[data-run-id]')) {
|
|
387
|
-
card.hidden = Boolean(query) && !matchingIds.has(card.dataset.runId);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
317
|
function renderRunCard(run) {
|
|
392
318
|
const root = document.createElement('section');
|
|
393
319
|
root.className = 'transcript-turn run-card';
|
|
@@ -404,6 +330,7 @@
|
|
|
404
330
|
<div class="transcript-turn-main">
|
|
405
331
|
<div class="run-attachments codex-attachment-preview-list" data-run-attachments hidden></div>
|
|
406
332
|
<div class="run-prompt" data-run-task></div>
|
|
333
|
+
<div class="run-guidance-list" data-run-guidance></div>
|
|
407
334
|
<div class="run-turn-meta">
|
|
408
335
|
<button type="button" data-run-accept hidden title="Accept this run's tracked changes in Overleaf">Accept changes</button>
|
|
409
336
|
<button type="button" data-run-undo hidden title="Undo this run's writes to Overleaf">Undo</button>
|
|
@@ -413,19 +340,25 @@
|
|
|
413
340
|
<span class="run-status" data-run-status></span>
|
|
414
341
|
<span class="run-scan" aria-hidden="true"></span>
|
|
415
342
|
</summary>
|
|
416
|
-
<div class="run-activity-list" data-run-events></div>
|
|
417
343
|
</details>
|
|
344
|
+
<div class="run-activity-list" data-run-events></div>
|
|
418
345
|
<div class="run-report" data-run-report aria-live="polite" hidden></div>
|
|
419
346
|
</div>
|
|
420
347
|
`;
|
|
421
348
|
|
|
422
349
|
renderAttachmentPreviewList(run.attachments, root.querySelector('[data-run-attachments]'), { readonly: true });
|
|
423
|
-
root.querySelector('[data-run-task]')
|
|
350
|
+
const runTask = root.querySelector('[data-run-task]');
|
|
351
|
+
runTask.classList.add('run-user-message');
|
|
352
|
+
runTask.textContent = run.task || '';
|
|
424
353
|
root.querySelector('[data-run-status]').textContent = getRunStatusText(run);
|
|
425
354
|
const process = root.querySelector('[data-run-process]');
|
|
426
355
|
process.open = run.status === 'running';
|
|
427
356
|
|
|
357
|
+
const guidance = root.querySelector('[data-run-guidance]');
|
|
428
358
|
const events = root.querySelector('[data-run-events]');
|
|
359
|
+
const eventListId = `codex-run-events-${String(run.id || 'unknown').replace(/[^a-zA-Z0-9_-]/g, '-')}`;
|
|
360
|
+
events.id = eventListId;
|
|
361
|
+
root.querySelector('[data-run-process-summary]')?.setAttribute('aria-controls', eventListId);
|
|
429
362
|
const report = root.querySelector('[data-run-report]');
|
|
430
363
|
if ((run.events || []).length >= 300) {
|
|
431
364
|
const truncated = document.createElement('div');
|
|
@@ -433,12 +366,28 @@
|
|
|
433
366
|
truncated.textContent = tr('eventsTruncatedNote');
|
|
434
367
|
events.append(truncated);
|
|
435
368
|
}
|
|
369
|
+
const guidanceTarget = run.status === 'running' ? events : guidance;
|
|
370
|
+
const renderedGuidanceIds = new Set();
|
|
371
|
+
const completedLegacyGuidance = new Set((run.events || [])
|
|
372
|
+
.filter(event => event.kind === 'guidance' && !event.guidanceId && event.status === 'completed')
|
|
373
|
+
.map(event => event.title));
|
|
436
374
|
for (const event of run.events || []) {
|
|
437
375
|
if (event.kind === 'report') {
|
|
438
376
|
report.hidden = false;
|
|
439
377
|
report.replaceChildren(renderCompletionReport(event, run));
|
|
440
378
|
} else if (event.kind === 'technical') {
|
|
441
379
|
continue;
|
|
380
|
+
} else if (RunGuidanceView.getGuidanceText(event)) {
|
|
381
|
+
if (event.guidanceId && renderedGuidanceIds.has(event.guidanceId)) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (!event.guidanceId && event.status === 'queued' && completedLegacyGuidance.has(event.title)) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (event.guidanceId) {
|
|
388
|
+
renderedGuidanceIds.add(event.guidanceId);
|
|
389
|
+
}
|
|
390
|
+
guidanceTarget.append(renderRunEvent(event));
|
|
442
391
|
} else if (event.kind === 'stream') {
|
|
443
392
|
upsertStreamEvent({ events }, event);
|
|
444
393
|
} else {
|
|
@@ -465,7 +414,11 @@
|
|
|
465
414
|
}
|
|
466
415
|
|
|
467
416
|
function renderRunEvent(event) {
|
|
468
|
-
|
|
417
|
+
const normalized = sanitizeRunEventForRender(event);
|
|
418
|
+
const guidanceText = RunGuidanceView.getGuidanceText(normalized);
|
|
419
|
+
return guidanceText
|
|
420
|
+
? RunGuidanceView.renderGuidanceMessage(normalized, guidanceText, tx)
|
|
421
|
+
: renderActivityLine(normalized);
|
|
469
422
|
}
|
|
470
423
|
|
|
471
424
|
function upsertStreamEvent(view, event) {
|
|
@@ -481,7 +434,7 @@
|
|
|
481
434
|
const text = existing.querySelector('[data-stream-text]');
|
|
482
435
|
if (text) {
|
|
483
436
|
if (event.streamRole === 'assistant') {
|
|
484
|
-
renderMarkdownBlockText(text, event.title || '');
|
|
437
|
+
renderMarkdownBlockText(text, event.title || '', { streaming: event.status === 'running' });
|
|
485
438
|
} else {
|
|
486
439
|
renderMarkdownInlineText(text, event.title || '');
|
|
487
440
|
}
|
|
@@ -503,7 +456,7 @@
|
|
|
503
456
|
text.className = 'run-stream-text';
|
|
504
457
|
text.dataset.streamText = '';
|
|
505
458
|
if (event.streamRole === 'assistant') {
|
|
506
|
-
renderMarkdownBlockText(text, event.title || '');
|
|
459
|
+
renderMarkdownBlockText(text, event.title || '', { streaming: event.status === 'running' });
|
|
507
460
|
} else {
|
|
508
461
|
renderMarkdownInlineText(text, event.title || '');
|
|
509
462
|
}
|
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
161
|
setState(deleteSession(getState(), sessionId));
|
|
162
|
-
await saveState();
|
|
162
|
+
await saveState({ deletedSessionIds: [sessionId] });
|
|
163
163
|
applyStateToPanel();
|
|
164
164
|
showPluginToast(tr('deleteSessionDeletedToast'), { status: 'info' });
|
|
165
165
|
return;
|
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
setState(deleteSession(getState(), sessionId));
|
|
189
|
-
await saveState();
|
|
189
|
+
await saveState({ deletedSessionIds: [sessionId] });
|
|
190
190
|
applyStateToPanel();
|
|
191
191
|
|
|
192
192
|
try {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
(function initCodexOverleafSessionPersistence() {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
function migration() {
|
|
5
|
+
return window.CodexOverleafStorageMigration;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function loadTombstones() {
|
|
9
|
+
return migration()?.loadSessionTombstones?.() || {};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function getDeletedSessionIds(projectId) {
|
|
13
|
+
const Migration = migration();
|
|
14
|
+
return Migration?.getDeletedSessionIds?.(await loadTombstones(), projectId) || [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function addTombstones(projectId, sessionIds) {
|
|
18
|
+
return migration()?.addSessionTombstones?.(projectId, sessionIds) || loadTombstones();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isVisibleRecord(record, deletedIds, accountScopeId) {
|
|
22
|
+
return Boolean(record)
|
|
23
|
+
&& !(deletedIds || []).includes(record.id)
|
|
24
|
+
&& (!accountScopeId || record.accountScopeId === accountScopeId);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function writeSessions(options = {}) {
|
|
28
|
+
const { StorageDb, projectId } = options;
|
|
29
|
+
const Migration = options.Migration || migration();
|
|
30
|
+
const requested = (Array.isArray(options.deletedSessionIds) ? options.deletedSessionIds : [])
|
|
31
|
+
.filter(id => typeof id === 'string' && id);
|
|
32
|
+
const tombstones = requested.length && Migration?.addSessionTombstones
|
|
33
|
+
? await Migration.addSessionTombstones(projectId, requested)
|
|
34
|
+
: await Migration?.loadSessionTombstones?.() || {};
|
|
35
|
+
const deleted = new Set(requested);
|
|
36
|
+
for (const id of Migration?.getDeletedSessionIds?.(tombstones, projectId) || []) deleted.add(id);
|
|
37
|
+
const sessionRecords = (options.sessionRecords || []).filter(record => !deleted.has(record.id));
|
|
38
|
+
const existingSessions = await StorageDb.getAllByIndex('sessions', 'projectId', projectId);
|
|
39
|
+
const deletedExisting = existingSessions.filter(record => deleted.has(record.id));
|
|
40
|
+
if (deletedExisting.length) {
|
|
41
|
+
await Promise.all(deletedExisting.map(record => StorageDb.deleteRecord('sessions', record.id)));
|
|
42
|
+
}
|
|
43
|
+
const existingById = new Map(existingSessions
|
|
44
|
+
.filter(record => !deleted.has(record.id))
|
|
45
|
+
.map(record => [record.id, record]));
|
|
46
|
+
const writable = sessionRecords.filter(record => {
|
|
47
|
+
const existing = existingById.get(record.id);
|
|
48
|
+
if (!existing) return true;
|
|
49
|
+
return (Date.parse(record.updatedAt || '') || 0) >= (Date.parse(existing.updatedAt || '') || 0);
|
|
50
|
+
});
|
|
51
|
+
if (writable.length) await StorageDb.putRecords('sessions', writable);
|
|
52
|
+
return writable;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
window.CodexOverleafSessionPersistence = {
|
|
56
|
+
loadTombstones,
|
|
57
|
+
getDeletedSessionIds,
|
|
58
|
+
addTombstones,
|
|
59
|
+
isVisibleRecord,
|
|
60
|
+
writeSessions
|
|
61
|
+
};
|
|
62
|
+
})();
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
<div class="codex-set-card codex-provider-settings-entry">
|
|
155
155
|
<div class="codex-provider-settings-entry-copy">
|
|
156
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="
|
|
157
|
+
<p class="codex-provider-settings-summary" data-provider-settings-summary data-tone="loading">${t(instance, 'providerSettingsLoading')}</p>
|
|
158
158
|
</div>
|
|
159
159
|
<button type="button" class="codex-set-btn" data-provider-settings-open data-i18n="providerSettingsConfigure">Configure</button>
|
|
160
160
|
</div>
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
let getLocale = () => '';
|
|
10
10
|
let settingsRoot = null;
|
|
11
11
|
let settingsActionInFlight = false;
|
|
12
|
+
let restartWatchdog = null;
|
|
13
|
+
let manualCommandCopied = false;
|
|
12
14
|
const ACTIVE_UPDATE_STATES = new Set(['downloading', 'staged', 'waiting_for_idle', 'applying', 'awaiting_health']);
|
|
15
|
+
const RESTART_WATCHDOG_MS = 30 * 1000;
|
|
13
16
|
|
|
14
17
|
function mount(panelLike, options = {}) {
|
|
15
18
|
if (typeof options.getLocale === 'function') {
|
|
@@ -60,6 +63,20 @@
|
|
|
60
63
|
async function handleClick(event) {
|
|
61
64
|
const action = event.target?.closest?.('[data-update-notice-action]')?.dataset?.updateNoticeAction;
|
|
62
65
|
if (!action || actionInFlight) return;
|
|
66
|
+
if (action === 'copy-manual') {
|
|
67
|
+
try {
|
|
68
|
+
await navigator.clipboard.writeText(manualUpdateCommand(currentView?.state));
|
|
69
|
+
manualCommandCopied = true;
|
|
70
|
+
render();
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
manualCommandCopied = false;
|
|
73
|
+
render();
|
|
74
|
+
}, 1800);
|
|
75
|
+
} catch (_error) {
|
|
76
|
+
manualCommandCopied = false;
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
63
80
|
actionInFlight = true;
|
|
64
81
|
render();
|
|
65
82
|
try {
|
|
@@ -121,6 +138,7 @@
|
|
|
121
138
|
}
|
|
122
139
|
|
|
123
140
|
function buildRestartingView() {
|
|
141
|
+
scheduleRestartWatchdog();
|
|
124
142
|
return {
|
|
125
143
|
...(currentView || {}),
|
|
126
144
|
state: {
|
|
@@ -134,6 +152,43 @@
|
|
|
134
152
|
};
|
|
135
153
|
}
|
|
136
154
|
|
|
155
|
+
function scheduleRestartWatchdog() {
|
|
156
|
+
clearTimeout(restartWatchdog);
|
|
157
|
+
restartWatchdog = setTimeout(async () => {
|
|
158
|
+
try {
|
|
159
|
+
const view = await request('codex-overleaf/consent-update-get-state');
|
|
160
|
+
if (view?.state && !ACTIVE_UPDATE_STATES.has(view.state.state)) {
|
|
161
|
+
currentView = view;
|
|
162
|
+
} else {
|
|
163
|
+
currentView = buildFailedView('update_recovery_timeout', tx(
|
|
164
|
+
'The update did not finish restarting. Retry or use the manual recovery command.',
|
|
165
|
+
'更新重启未能完成。请重试或使用手动恢复命令。'
|
|
166
|
+
));
|
|
167
|
+
}
|
|
168
|
+
} catch (_error) {
|
|
169
|
+
currentView = buildFailedView('update_recovery_timeout', tx(
|
|
170
|
+
'The update did not finish restarting. Retry or use the manual recovery command.',
|
|
171
|
+
'更新重启未能完成。请重试或使用手动恢复命令。'
|
|
172
|
+
));
|
|
173
|
+
}
|
|
174
|
+
render();
|
|
175
|
+
}, RESTART_WATCHDOG_MS);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function buildFailedView(code, message) {
|
|
179
|
+
return {
|
|
180
|
+
...(currentView || {}),
|
|
181
|
+
state: {
|
|
182
|
+
...(currentView?.state || {}),
|
|
183
|
+
state: 'failed',
|
|
184
|
+
code,
|
|
185
|
+
message
|
|
186
|
+
},
|
|
187
|
+
progress: { value: 0, determinate: true, phase: 'failed' },
|
|
188
|
+
showPanel: true
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
137
192
|
function bindSettingsControls(panel) {
|
|
138
193
|
settingsRoot = panel;
|
|
139
194
|
const button = panel.querySelector?.('[data-check-updates]');
|
|
@@ -184,6 +239,10 @@
|
|
|
184
239
|
|
|
185
240
|
const focusedAction = document.activeElement?.dataset?.updateNoticeAction || '';
|
|
186
241
|
const state = currentView.state || {};
|
|
242
|
+
if (state.state !== 'awaiting_health') {
|
|
243
|
+
clearTimeout(restartWatchdog);
|
|
244
|
+
restartWatchdog = null;
|
|
245
|
+
}
|
|
187
246
|
const progress = currentView.progress || {};
|
|
188
247
|
const copy = getCopy(state);
|
|
189
248
|
notice.hidden = false;
|
|
@@ -201,6 +260,16 @@
|
|
|
201
260
|
detail.textContent = copy.detail;
|
|
202
261
|
body.append(eyebrow, title, detail);
|
|
203
262
|
|
|
263
|
+
if (state.state === 'failed') {
|
|
264
|
+
const recovery = document.createElement('span');
|
|
265
|
+
recovery.className = 'codex-update-notice-recovery';
|
|
266
|
+
recovery.textContent = tx('Manual recovery:', '手动恢复:');
|
|
267
|
+
const command = document.createElement('code');
|
|
268
|
+
command.className = 'codex-update-notice-command';
|
|
269
|
+
command.textContent = manualUpdateCommand(state);
|
|
270
|
+
body.append(recovery, command);
|
|
271
|
+
}
|
|
272
|
+
|
|
204
273
|
if (!['update_available', 'failed', 'rolled_back'].includes(state.state)) {
|
|
205
274
|
const bar = document.createElement('div');
|
|
206
275
|
bar.className = 'codex-update-notice-progress' + (progress.determinate ? '' : ' is-indeterminate');
|
|
@@ -290,7 +359,10 @@
|
|
|
290
359
|
return [{ id: 'later', label: tx('Later', '稍后'), primary: false }];
|
|
291
360
|
}
|
|
292
361
|
if (['failed', 'rolled_back'].includes(state)) {
|
|
293
|
-
return [
|
|
362
|
+
return [
|
|
363
|
+
{ id: 'copy-manual', label: manualCommandCopied ? tx('Copied', '已复制') : tx('Copy command', '复制命令'), primary: false },
|
|
364
|
+
{ id: 'retry', label: tx('Retry', '重试'), primary: true }
|
|
365
|
+
];
|
|
294
366
|
}
|
|
295
367
|
if (state === 'committed') {
|
|
296
368
|
return [{ id: 'dismiss', label: tx('Done', '完成'), primary: true }];
|
|
@@ -350,6 +422,13 @@
|
|
|
350
422
|
}[state.state] || { eyebrow: '', title: '', detail: '' };
|
|
351
423
|
}
|
|
352
424
|
|
|
425
|
+
function manualUpdateCommand(state = {}) {
|
|
426
|
+
const version = /^\d+\.\d+\.\d+$/.test(String(state.latestVersion || ''))
|
|
427
|
+
? state.latestVersion
|
|
428
|
+
: 'latest';
|
|
429
|
+
return `npm exec --yes codex-overleaf-link@${version} -- install-managed`;
|
|
430
|
+
}
|
|
431
|
+
|
|
353
432
|
function blockerCopy(value) {
|
|
354
433
|
return {
|
|
355
434
|
unsaved: tx('Overleaf has not confirmed that this document is saved.', 'Overleaf 尚未确认当前文档已保存。'),
|