codex-overleaf-link 2.5.1 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -398
- package/README.zh-CN.md +153 -404
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +78 -3
- package/extension/src/backgroundSharedSessions.js +55 -2
- package/extension/src/backgroundUpdateCoordinator.js +128 -12
- package/extension/src/content/activeTurnControl.js +164 -7
- package/extension/src/content/composerAttachments.js +11 -2
- package/extension/src/content/composerPanel.js +92 -12
- package/extension/src/content/contentRuntime.js +920 -276
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsPanel.js +18 -0
- package/extension/src/content/generated/content.bundle.js +4496 -924
- package/extension/src/content/generated/content.bundle.meta.json +6 -3
- package/extension/src/content/legacyGlobalRegistry.js +4 -0
- package/extension/src/content/localSkillsPanel.js +2 -2
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +42 -5
- package/extension/src/content/modelPicker.js +1 -0
- package/extension/src/content/nativeCompatibilityController.js +29 -10
- package/extension/src/content/otWarmMirror.js +1 -1
- package/extension/src/content/pageBridgeClient.js +2 -2
- package/extension/src/content/panelMaintenance.js +6 -2
- package/extension/src/content/panelRenderer.js +51 -27
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectSettingsCoordinator.js +30 -6
- package/extension/src/content/providerSettingsDialog.js +2 -1
- package/extension/src/content/recentProjects.js +4 -3
- package/extension/src/content/runActivitySummary.js +122 -14
- package/extension/src/content/runCompletionMeta.js +194 -6
- package/extension/src/content/runController.js +2 -1
- package/extension/src/content/runFailureNotice.js +8 -1
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +9 -5
- package/extension/src/content/runRecoveryActions.js +2 -1
- package/extension/src/content/runResultActions.js +22 -20
- package/extension/src/content/runReviewController.js +94 -35
- package/extension/src/content/runTimelineView.js +224 -25
- package/extension/src/content/scopedPersistencePanelState.js +92 -23
- package/extension/src/content/sensitivePreflight.js +14 -1
- package/extension/src/content/sessionManager.js +49 -3
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +15 -2
- package/extension/src/content/settingsWorkbench.js +127 -89
- package/extension/src/content/sharedSessionViewSync.js +4 -3
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateNotice.js +60 -6
- package/extension/src/content/writebackOrchestrator.js +35 -22
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/overleafCapabilities.js +4 -1
- package/extension/src/page/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +9 -1
- package/extension/src/page/writebackRouter.js +24 -77
- package/extension/src/pageBridge.js +107 -66
- package/extension/src/popup.js +1 -1
- package/extension/src/shared/agentTranscript.js +350 -17
- package/extension/src/shared/auditRecords.js +2 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/compileAdapter.js +58 -17
- package/extension/src/shared/composerAttachmentDraft.js +6 -1
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/failureReasons.js +42 -3
- package/extension/src/shared/i18n.js +299 -29
- package/extension/src/shared/lineReferences.js +38 -31
- package/extension/src/shared/pageRpcContract.js +1 -1
- package/extension/src/shared/panelIcons.js +39 -0
- package/extension/src/shared/reviewing.js +130 -1
- package/extension/src/shared/runActivityModel.js +70 -4
- package/extension/src/shared/runExecutionSnapshot.js +13 -0
- package/extension/src/shared/sessionState.js +63 -4
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/sharedSessionBridge.js +81 -13
- package/extension/src/shared/staleGuard.js +72 -63
- package/extension/src/shared/storageDb.js +35 -3
- package/extension/src/shared/storageMigration.js +7 -1
- package/extension/src/shared/storageRunActions.js +62 -8
- package/extension/src/shared/undoOperations.js +126 -28
- package/extension/src/shared/updateRuntimeIdentity.js +16 -2
- package/extension/src/shared/writebackSettlement.js +21 -3
- package/extension/styles/panel.css +1106 -490
- package/native-host/src/bootstrapUpdate.js +52 -16
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +10 -1
- package/native-host/src/chatCompletionsBridge.js +29 -2
- package/native-host/src/codexPromptAssembly.js +86 -118
- package/native-host/src/codexSessionRunner.js +126 -17
- package/native-host/src/providerContextError.js +20 -0
- package/native-host/src/taskRunnerRuntime.js +18 -6
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/updateManager.js +120 -15
- package/package.json +5 -8
|
@@ -7,12 +7,23 @@
|
|
|
7
7
|
})(typeof window !== 'undefined' ? window : globalThis, function activeTurnControlFactory() {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
|
+
// Startup has an owner before a Native request exists. This identity is only
|
|
11
|
+
// for ownership probes; nativeRequestId remains reserved for the actual RPC.
|
|
12
|
+
function getStartupRequestId(sessionId, runId) {
|
|
13
|
+
return `startup:${encodeURIComponent(sessionId)}:${encodeURIComponent(runId)}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
function create(options = {}) {
|
|
11
17
|
const runtime = options.chrome?.runtime;
|
|
12
18
|
let port = null;
|
|
13
19
|
let binding = null;
|
|
14
20
|
let reconnectTimer = null;
|
|
15
21
|
let destroyed = false;
|
|
22
|
+
let bindingWaiter = null;
|
|
23
|
+
let bindingSequence = 0;
|
|
24
|
+
let recoveryTimer = null;
|
|
25
|
+
let recoveryGeneration = 0;
|
|
26
|
+
const inventoryRetries = new Map();
|
|
16
27
|
|
|
17
28
|
function connect() {
|
|
18
29
|
if (destroyed || port || typeof runtime?.connect !== 'function') {
|
|
@@ -21,6 +32,11 @@
|
|
|
21
32
|
try {
|
|
22
33
|
const nextPort = runtime.connect({ name: 'codex-overleaf/run-owner' });
|
|
23
34
|
port = nextPort;
|
|
35
|
+
nextPort.onMessage?.addListener?.(message => {
|
|
36
|
+
if (port !== nextPort || message?.type !== 'bound' || !bindingWaiter || !binding) return;
|
|
37
|
+
if (['requestId', 'projectKey', 'clientRunId', 'sessionId'].every(key => message[key] === binding[key])
|
|
38
|
+
&& message.browserOnly === true && message.ownerToken === binding.ownerToken) bindingWaiter(message.ok !== false);
|
|
39
|
+
});
|
|
24
40
|
nextPort.onDisconnect?.addListener?.(() => {
|
|
25
41
|
if (port === nextPort) {
|
|
26
42
|
port = null;
|
|
@@ -46,20 +62,39 @@
|
|
|
46
62
|
}
|
|
47
63
|
|
|
48
64
|
function bind(value = {}) {
|
|
65
|
+
bindingWaiter?.(false);
|
|
49
66
|
binding = {
|
|
50
67
|
requestId: String(value.requestId || ''),
|
|
51
68
|
projectKey: String(value.projectKey || ''),
|
|
52
69
|
clientRunId: String(value.clientRunId || ''),
|
|
53
|
-
sessionId: String(value.sessionId || '')
|
|
70
|
+
sessionId: String(value.sessionId || ''),
|
|
71
|
+
...(value.browserOnly === true ? { browserOnly: true, ownerToken: String(value.ownerToken || `browser-${++bindingSequence}`) } : {})
|
|
54
72
|
};
|
|
55
73
|
connect();
|
|
56
74
|
postBinding('bind', binding);
|
|
57
75
|
}
|
|
58
76
|
|
|
59
|
-
function
|
|
60
|
-
|
|
77
|
+
async function bindBrowserRun(value) {
|
|
78
|
+
bind({ ...value, browserOnly: true });
|
|
79
|
+
if (destroyed || !port) return false;
|
|
80
|
+
return new Promise(resolve => {
|
|
81
|
+
const timeout = setTimeout(() => complete(false), 5000);
|
|
82
|
+
const complete = ok => {
|
|
83
|
+
clearTimeout(timeout);
|
|
84
|
+
if (bindingWaiter === complete) bindingWaiter = null;
|
|
85
|
+
resolve(ok);
|
|
86
|
+
};
|
|
87
|
+
bindingWaiter = complete;
|
|
88
|
+
postBinding('bind', binding);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function release(requestId, ownerToken) {
|
|
93
|
+
if (!binding || (requestId && binding.requestId !== requestId)
|
|
94
|
+
|| (ownerToken && ownerToken !== binding.ownerToken)) {
|
|
61
95
|
return;
|
|
62
96
|
}
|
|
97
|
+
bindingWaiter?.(false);
|
|
63
98
|
postBinding('release', binding);
|
|
64
99
|
binding = null;
|
|
65
100
|
}
|
|
@@ -86,12 +121,94 @@
|
|
|
86
121
|
return response?.ok === true;
|
|
87
122
|
}
|
|
88
123
|
|
|
124
|
+
async function knownJournals(requestIds, owners = [], onRetry) {
|
|
125
|
+
if (typeof runtime?.sendMessage !== 'function') return null;
|
|
126
|
+
const known = new Set();
|
|
127
|
+
for (let offset = 0; offset < requestIds.length; offset += 64) {
|
|
128
|
+
const ids = requestIds.slice(offset, offset + 64);
|
|
129
|
+
const response = await runtime.sendMessage({ type: 'codex-overleaf/run-journal/known',
|
|
130
|
+
requestIds: ids, owners: owners.slice(offset, offset + 64) });
|
|
131
|
+
// An older background cannot rule out browser-only owners.
|
|
132
|
+
const needsStartupInventory = owners.slice(offset, offset + 64).some(owner => owner.preDispatch === true);
|
|
133
|
+
if (!response?.ok || !Array.isArray(response.known) || response.ownersChecked !== true
|
|
134
|
+
|| (needsStartupInventory && response.preDispatchChecked !== true)) {
|
|
135
|
+
if (Number.isFinite(response?.retryAfterMs) && response.retryAfterMs > 0) onRetry?.(response.retryAfterMs);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
for (const id of response.known) if (ids.includes(id)) known.add(id);
|
|
139
|
+
}
|
|
140
|
+
return known;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Native journals and live browser-only owners are separate evidence. A
|
|
144
|
+
// missing journal alone cannot establish that another page stopped retrying.
|
|
145
|
+
async function recoverOrphanedRuns(projectKey, options, result, isCurrent) {
|
|
146
|
+
const now = Date.now(), candidates = [];
|
|
147
|
+
let recheckAfterMs = Infinity;
|
|
148
|
+
const sessions = () => options.listSessions?.() || [];
|
|
149
|
+
const requestIdFor = (session, record) => record.nativeRequestId || getStartupRequestId(session.id, record.id);
|
|
150
|
+
const eligible = (session, record) => record.status === 'running' && session.id && record.id
|
|
151
|
+
&& !options.isLiveRun?.(record, session)
|
|
152
|
+
&& (!session.projectId || session.projectId === projectKey)
|
|
153
|
+
&& (!options.accountScopeId || !session.accountScopeId || session.accountScopeId === options.accountScopeId)
|
|
154
|
+
&& (!record.runProjectId || record.runProjectId === projectKey);
|
|
155
|
+
for (const session of sessions()) {
|
|
156
|
+
for (const record of session.runs || []) {
|
|
157
|
+
if (!eligible(session, record)) continue;
|
|
158
|
+
const started = Date.parse(record.startedAt);
|
|
159
|
+
if (!Number.isFinite(started)) continue;
|
|
160
|
+
const remaining = started + (options.orphanGraceMs ?? 60000) - now;
|
|
161
|
+
if (remaining > 0) { recheckAfterMs = Math.min(recheckAfterMs, remaining); continue; }
|
|
162
|
+
candidates.push({ session, record, requestId: requestIdFor(session, record) });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
result.recheckAfterMs = Number.isFinite(recheckAfterMs) ? recheckAfterMs : null;
|
|
166
|
+
if (!candidates.length) return;
|
|
167
|
+
const known = await knownJournals(candidates.map(item => item.requestId), candidates.map(({ session, record, requestId }) => ({
|
|
168
|
+
requestId, projectKey, clientRunId: record.id, sessionId: session.id,
|
|
169
|
+
...(!record.nativeRequestId ? { preDispatch: true } : {})
|
|
170
|
+
})), delay => {
|
|
171
|
+
const key = `${options.accountScopeId || ''}:${projectKey}`;
|
|
172
|
+
const attempts = inventoryRetries.get(key) || 0;
|
|
173
|
+
if (attempts >= 3) return;
|
|
174
|
+
inventoryRetries.set(key, attempts + 1);
|
|
175
|
+
result.recheckAfterMs = Math.min(result.recheckAfterMs ?? Infinity, delay);
|
|
176
|
+
}).catch(() => null);
|
|
177
|
+
if (!known || !isCurrent()) return;
|
|
178
|
+
inventoryRetries.delete(`${options.accountScopeId || ''}:${projectKey}`);
|
|
179
|
+
for (const { session, record, requestId } of candidates) {
|
|
180
|
+
const currentSession = sessions().find(value => value.id === session.id);
|
|
181
|
+
// State normalization can replace run objects while inventory is pending.
|
|
182
|
+
if (!currentSession?.runs?.includes(record) || !eligible(currentSession, record)
|
|
183
|
+
|| requestIdFor(currentSession, record) !== requestId || known.has(requestId)) continue;
|
|
184
|
+
if (!record.runProjectId) record.runProjectId = projectKey;
|
|
185
|
+
options.normalizeInterruptedRun?.(record);
|
|
186
|
+
record.status = 'interrupted';
|
|
187
|
+
record.interruptedDraft = { requestId, threadId: currentSession.codexThreadId || '',
|
|
188
|
+
turnId: record.codexTurnId || '', lastEventSeq: record.nativeEventSeq || 0, reason: 'extension_reloaded' };
|
|
189
|
+
options.pauseSessionQueue?.(currentSession, 'page_reload');
|
|
190
|
+
result.changed = true;
|
|
191
|
+
result.recoveredRuns.push({ sessionId: currentSession.id, runId: record.id,
|
|
192
|
+
projectId: projectKey, accountScopeId: options.accountScopeId });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function cancelRecovery() {
|
|
197
|
+
recoveryGeneration++;
|
|
198
|
+
clearTimeout(recoveryTimer);
|
|
199
|
+
recoveryTimer = null;
|
|
200
|
+
}
|
|
201
|
+
|
|
89
202
|
async function recoverJournals(options = {}) {
|
|
203
|
+
cancelRecovery();
|
|
204
|
+
const generation = recoveryGeneration;
|
|
205
|
+
const isCurrent = () => !destroyed && generation === recoveryGeneration && options.isCurrent?.() !== false;
|
|
90
206
|
const projectKey = String(options.projectKey || '');
|
|
91
207
|
if (!projectKey) {
|
|
92
208
|
return { changed: false, acknowledgeIds: [] };
|
|
93
209
|
}
|
|
94
210
|
const journals = await listJournals(projectKey).catch(() => []);
|
|
211
|
+
if (!isCurrent()) return { changed: false, acknowledgeIds: [], recoveredRuns: [] };
|
|
95
212
|
let changed = false;
|
|
96
213
|
const acknowledgeIds = [];
|
|
97
214
|
const recoveredRuns = [];
|
|
@@ -116,11 +233,19 @@
|
|
|
116
233
|
session.runs = [...(session.runs || []), record].slice(-20);
|
|
117
234
|
}
|
|
118
235
|
}
|
|
119
|
-
if (!record) {
|
|
236
|
+
if (!record || options.isLiveRun?.(record, session)) {
|
|
120
237
|
continue;
|
|
121
238
|
}
|
|
122
239
|
if (record.runProjectId && record.runProjectId !== projectKey) continue;
|
|
123
240
|
if (!record.runProjectId) record.runProjectId = projectKey;
|
|
241
|
+
if (journal.operation === 'compact') {
|
|
242
|
+
record.operation = 'compact';
|
|
243
|
+
if (!record.codexThreadId) record.codexThreadId = String(journal.threadId || '');
|
|
244
|
+
if (session.contextUsage?.threadId === record.codexThreadId
|
|
245
|
+
&& Date.parse(session.contextUsage.observedAt || '') < Date.parse(record.startedAt || '')) {
|
|
246
|
+
session.contextUsage = null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
124
249
|
if (record.status === 'running') {
|
|
125
250
|
options.normalizeInterruptedRun?.(record);
|
|
126
251
|
}
|
|
@@ -140,6 +265,9 @@
|
|
|
140
265
|
record.nativeEventSeq = sequence;
|
|
141
266
|
continue;
|
|
142
267
|
}
|
|
268
|
+
// Non-display state (for example context usage) must also recover
|
|
269
|
+
// from the journal, after the owning thread/turn has been bound.
|
|
270
|
+
options.onRawEvent?.(raw, { record, session, sequence, journal });
|
|
143
271
|
const recovered = options.mapEvent?.(raw, journal);
|
|
144
272
|
if (recovered) {
|
|
145
273
|
if (recovered.kind === 'stream') {
|
|
@@ -170,11 +298,21 @@
|
|
|
170
298
|
recoveredRuns.push({ sessionId: session.id, runId: record.id,
|
|
171
299
|
projectId: projectKey, accountScopeId: options.accountScopeId });
|
|
172
300
|
}
|
|
173
|
-
|
|
301
|
+
const result = { changed, acknowledgeIds, recoveredRuns };
|
|
302
|
+
await recoverOrphanedRuns(projectKey, options, result, isCurrent);
|
|
303
|
+
if (isCurrent() && result.recheckAfterMs !== null && typeof options.onRecheck === 'function') {
|
|
304
|
+
recoveryTimer = setTimeout(() => {
|
|
305
|
+
recoveryTimer = null;
|
|
306
|
+
if (isCurrent()) Promise.resolve(options.onRecheck()).catch(() => {});
|
|
307
|
+
}, Math.min(result.recheckAfterMs, 2147483647));
|
|
308
|
+
}
|
|
309
|
+
return result;
|
|
174
310
|
}
|
|
175
311
|
|
|
176
312
|
function destroy() {
|
|
177
313
|
destroyed = true;
|
|
314
|
+
cancelRecovery();
|
|
315
|
+
bindingWaiter?.(false);
|
|
178
316
|
clearTimeout(reconnectTimer);
|
|
179
317
|
reconnectTimer = null;
|
|
180
318
|
try {
|
|
@@ -187,8 +325,27 @@
|
|
|
187
325
|
}
|
|
188
326
|
|
|
189
327
|
connect();
|
|
190
|
-
return { acknowledge, bind,
|
|
328
|
+
return { acknowledge, bind, bindBrowserRun, cancelRecovery, destroy, getOwner: () => binding ? { ...binding } : null,
|
|
329
|
+
knownJournals, listJournals, recoverJournals, release };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function createInterruptedRun(journal, projectKey, statusText) {
|
|
333
|
+
return { id: journal.clientRunId, task: journal.task || 'interrupted task', mode: journal.mode || 'ask',
|
|
334
|
+
...(journal.operation === 'compact' ? { operation: 'compact', codexThreadId: String(journal.threadId || '') } : {}),
|
|
335
|
+
model: journal.model || '', status: 'interrupted', statusText, runProjectId: projectKey,
|
|
336
|
+
startedAt: journal.createdAt || new Date().toISOString(), finishedAt: journal.updatedAt || new Date().toISOString(),
|
|
337
|
+
events: [], attachments: [], undoOperations: [], undoTrackedChanges: [], undoExpectedFiles: [] };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function mapRecoveredEvent(raw, journal, options) {
|
|
341
|
+
const activity = options.mapActivity(raw);
|
|
342
|
+
if (!activity?.visible || activity.kind === 'technical') return null;
|
|
343
|
+
return { kind: activity.kind || 'activity', title: options.sanitizeText(activity.title) || 'Event',
|
|
344
|
+
status: activity.status || 'running', detail: options.sanitizeValue(activity.detail),
|
|
345
|
+
timestamp: raw.timestamp || journal.updatedAt || new Date().toISOString(),
|
|
346
|
+
streamKey: options.sanitizeText(activity.streamKey), streamRole: options.sanitizeText(activity.streamRole),
|
|
347
|
+
appendText: options.sanitizeText(activity.appendText), replaceText: activity.replaceText };
|
|
191
348
|
}
|
|
192
349
|
|
|
193
|
-
return { create };
|
|
350
|
+
return { create, createInterruptedRun, mapRecoveredEvent, getStartupRequestId };
|
|
194
351
|
});
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
const tr = typeof options.tr === 'function' ? options.tr : (key) => key;
|
|
8
8
|
const appendPlainLog = typeof options.appendPlainLog === 'function' ? options.appendPlainLog : () => {};
|
|
9
9
|
const onChange = typeof options.onChange === 'function' ? options.onChange : () => {};
|
|
10
|
+
const icons = options.icons || null;
|
|
10
11
|
const limits = options.limits || {};
|
|
11
12
|
const maxAttachments = Number(limits.maxAttachments) || 8;
|
|
12
13
|
const maxAttachmentBytes = Number(limits.maxAttachmentBytes) || 12 * 1024 * 1024;
|
|
@@ -159,6 +160,7 @@
|
|
|
159
160
|
root: getPanel() || document.body,
|
|
160
161
|
tr,
|
|
161
162
|
tx,
|
|
163
|
+
icons,
|
|
162
164
|
removable: true,
|
|
163
165
|
onRemove: removeAttachment
|
|
164
166
|
});
|
|
@@ -333,6 +335,7 @@
|
|
|
333
335
|
root: getPanel() || document.body,
|
|
334
336
|
tr,
|
|
335
337
|
tx,
|
|
338
|
+
icons,
|
|
336
339
|
...options
|
|
337
340
|
}),
|
|
338
341
|
clear,
|
|
@@ -408,6 +411,12 @@
|
|
|
408
411
|
.slice(0, 180) || '';
|
|
409
412
|
}
|
|
410
413
|
|
|
414
|
+
function setCloseGlyph(button, icons) {
|
|
415
|
+
const glyph = icons?.svg?.('close', 'codex-close-glyph');
|
|
416
|
+
if (glyph) button.innerHTML = glyph;
|
|
417
|
+
else button.textContent = '×';
|
|
418
|
+
}
|
|
419
|
+
|
|
411
420
|
function renderAttachmentPreviewList(attachments = [], container, options = {}) {
|
|
412
421
|
if (!container) return;
|
|
413
422
|
container.replaceChildren();
|
|
@@ -455,7 +464,7 @@
|
|
|
455
464
|
const remove = document.createElement('button');
|
|
456
465
|
remove.type = 'button';
|
|
457
466
|
remove.className = 'codex-attachment-preview-remove';
|
|
458
|
-
remove.
|
|
467
|
+
setCloseGlyph(remove, options.icons);
|
|
459
468
|
const removeLabel = options.tx ? options.tx('Remove ' + label, '移除 ' + label) : 'Remove ' + label;
|
|
460
469
|
remove.title = removeLabel;
|
|
461
470
|
remove.setAttribute('aria-label', removeLabel);
|
|
@@ -486,7 +495,7 @@
|
|
|
486
495
|
const close = document.createElement('button');
|
|
487
496
|
close.type = 'button';
|
|
488
497
|
close.className = 'codex-attachment-preview-dialog-close';
|
|
489
|
-
close.
|
|
498
|
+
setCloseGlyph(close, options.icons);
|
|
490
499
|
close.title = options.tr ? options.tr('close') : 'Close';
|
|
491
500
|
close.setAttribute('aria-label', options.tr ? options.tr('close') : 'Close');
|
|
492
501
|
const image = document.createElement('img');
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
attachmentController: options.attachmentController || null,
|
|
14
14
|
listeners: []
|
|
15
15
|
};
|
|
16
|
+
const icon = name => options.icons?.svg(name) || '';
|
|
16
17
|
|
|
17
18
|
container.innerHTML = `
|
|
18
19
|
<div class="codex-pending-inputs" data-pending-inputs hidden></div>
|
|
@@ -20,9 +21,9 @@
|
|
|
20
21
|
<div class="codex-attachment-strip codex-attachment-preview-list" data-attachment-strip hidden></div>
|
|
21
22
|
<div class="codex-composer-selection" data-composer-selection hidden></div>
|
|
22
23
|
<div class="codex-composer-skill-context" data-composer-skill-context hidden>
|
|
23
|
-
<span class="codex-composer-skill-icon" aria-hidden="true"
|
|
24
|
+
<span class="codex-composer-skill-icon" aria-hidden="true">${icon('sparkle')}</span>
|
|
24
25
|
<span data-composer-skill-label></span>
|
|
25
|
-
<button type="button" data-composer-skill-clear title="Clear skill" aria-label="Clear skill"
|
|
26
|
+
<button type="button" data-composer-skill-clear title="Clear skill" aria-label="Clear skill">${icon('close')}</button>
|
|
26
27
|
</div>
|
|
27
28
|
<textarea data-task rows="3" placeholder="Ask Codex anything. Type @ to add context"></textarea>
|
|
28
29
|
<div class="codex-context-summary" data-context-summary hidden></div>
|
|
@@ -60,8 +61,43 @@
|
|
|
60
61
|
</div>
|
|
61
62
|
</div>
|
|
62
63
|
<div class="codex-composer-toolbar">
|
|
63
|
-
<button type="button" data-add-context title="Add @ context" aria-label="Add @ context" aria-expanded="false"
|
|
64
|
+
<button type="button" data-add-context title="Add @ context" aria-label="Add @ context" aria-expanded="false">${icon('plus')}</button>
|
|
64
65
|
<div class="codex-model-config" data-model-config>
|
|
66
|
+
<div class="codex-context-usage" data-context-usage data-unknown="true">
|
|
67
|
+
<button type="button" class="codex-context-usage-button" data-context-usage-toggle aria-expanded="false">
|
|
68
|
+
<svg class="codex-context-usage-ring" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
|
|
69
|
+
<circle class="codex-context-usage-track" cx="8" cy="8" r="6.4" pathLength="100"></circle>
|
|
70
|
+
<circle class="codex-context-usage-fill" cx="8" cy="8" r="2.55" pathLength="100"></circle>
|
|
71
|
+
<circle class="codex-context-usage-orbit" cx="8" cy="8" r="6.4" pathLength="100"></circle>
|
|
72
|
+
</svg>
|
|
73
|
+
</button>
|
|
74
|
+
<div class="codex-context-usage-popover" data-context-usage-popover role="region" tabindex="0" hidden>
|
|
75
|
+
<div class="codex-context-usage-head">
|
|
76
|
+
<svg class="codex-context-usage-empty-ring" data-context-usage-empty-ring viewBox="0 0 26 26" aria-hidden="true" focusable="false"><circle cx="13" cy="13" r="10.5"></circle><circle class="codex-context-usage-empty-dot" cx="13" cy="13" r="2"></circle></svg>
|
|
77
|
+
<div class="codex-context-usage-head-text">
|
|
78
|
+
<div class="codex-context-usage-title" data-context-usage-title></div>
|
|
79
|
+
<div class="codex-context-usage-amount" data-context-usage-amount></div>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="codex-context-usage-figure" data-context-usage-figure>
|
|
82
|
+
<span class="codex-context-usage-spinner" data-context-usage-spinner hidden>${icon('spin')}</span>
|
|
83
|
+
<span class="codex-context-usage-chip" data-context-usage-chip hidden></span>
|
|
84
|
+
<span class="codex-context-usage-percent" data-context-usage-percent-wrap><span data-context-usage-percent></span><small data-context-usage-percent-sign>%</small></span>
|
|
85
|
+
<span class="codex-context-usage-level" data-context-usage-level hidden></span>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="codex-context-usage-note" data-context-usage-note hidden>${icon('compact')}<span data-context-usage-note-text></span></div>
|
|
89
|
+
<div class="codex-context-usage-bar" data-context-usage-bar aria-hidden="true"><i data-seg="cached"></i><i data-seg="input"></i><i data-seg="output"></i></div>
|
|
90
|
+
<div class="codex-context-usage-legend" data-context-usage-breakdown hidden>
|
|
91
|
+
<div class="codex-context-usage-row"><i class="codex-context-usage-swatch" data-seg="input"></i><span data-context-usage-input-label></span><b data-context-usage-input></b><em data-context-usage-input-share></em></div>
|
|
92
|
+
<div class="codex-context-usage-row" data-sub data-context-usage-cache><i class="codex-context-usage-swatch" data-seg="cached"></i><span data-context-usage-cached-label></span><b data-context-usage-cached></b><em data-context-usage-cache-rate></em></div>
|
|
93
|
+
<div class="codex-context-usage-row"><i class="codex-context-usage-swatch" data-seg="output"></i><span data-context-usage-output-label></span><b data-context-usage-output></b><em data-context-usage-output-share></em></div>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="codex-context-usage-foot" data-context-usage-foot>
|
|
96
|
+
<span data-context-usage-reported hidden></span>
|
|
97
|
+
<button type="button" class="codex-context-usage-compact" data-context-usage-compact>${icon('compact')}<span data-context-usage-compact-label></span></button>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
65
101
|
<button type="button" class="codex-model-config-button" data-model-config-toggle aria-haspopup="menu" aria-expanded="false">
|
|
66
102
|
<span class="codex-model-speed-indicator" data-speed-indicator hidden aria-hidden="true">
|
|
67
103
|
<svg viewBox="0 0 16 16" focusable="false">
|
|
@@ -99,7 +135,10 @@
|
|
|
99
135
|
</div>
|
|
100
136
|
</div>
|
|
101
137
|
</div>
|
|
102
|
-
<button type="submit" data-run data-action="send" title="Send" aria-label="Send"
|
|
138
|
+
<button type="submit" data-run data-action="send" title="Send" aria-label="Send">
|
|
139
|
+
<span class="codex-run-glyph" data-run-glyph="send">${icon('send')}</span>
|
|
140
|
+
<span class="codex-run-glyph" data-run-glyph="cancel">${icon('stop')}</span>
|
|
141
|
+
</button>
|
|
103
142
|
</div>
|
|
104
143
|
<div class="codex-slash-menu" data-slash-menu hidden>
|
|
105
144
|
<button type="button" data-slash-command="install-skill" data-slash-command-kind="installer">
|
|
@@ -119,9 +158,9 @@
|
|
|
119
158
|
</svg>
|
|
120
159
|
<span class="codex-context-sync-copy" data-context-sync-copy></span>
|
|
121
160
|
</span>
|
|
122
|
-
<button type="button" class="codex-attach-button" data-attach-file title="Attach files (PDF, images)" aria-label="Attach files (PDF, images)"
|
|
161
|
+
<button type="button" class="codex-attach-button" data-attach-file title="Attach files (PDF, images)" aria-label="Attach files (PDF, images)">${icon('attach')}</button>
|
|
123
162
|
<input type="file" data-attach-input multiple hidden>
|
|
124
|
-
<button type="button" data-context-refresh title="Refresh file list" aria-label="Refresh file list"
|
|
163
|
+
<button type="button" data-context-refresh title="Refresh file list" aria-label="Refresh file list">${icon('refresh')}</button>
|
|
125
164
|
</span>
|
|
126
165
|
</div>
|
|
127
166
|
<div class="codex-context-selection" data-context-selection></div>
|
|
@@ -136,6 +175,7 @@
|
|
|
136
175
|
|
|
137
176
|
return {
|
|
138
177
|
setRunning: running => setRunning(instance, running),
|
|
178
|
+
setSessionTransition: pending => setSessionTransition(instance, pending),
|
|
139
179
|
setModels: models => setModels(instance, models),
|
|
140
180
|
setMode: mode => setMode(instance, mode),
|
|
141
181
|
focus: () => focus(instance),
|
|
@@ -157,14 +197,31 @@
|
|
|
157
197
|
bind(instance, form, 'drop', event => instance.callbacks.onDrop?.(event));
|
|
158
198
|
bind(instance, instance.container.querySelector('[data-run]'), 'click', event => {
|
|
159
199
|
event.preventDefault();
|
|
160
|
-
if (instance.
|
|
200
|
+
if (instance.container.querySelector('[data-run]')?.dataset.action === 'cancel') {
|
|
161
201
|
instance.callbacks.onCancel?.();
|
|
162
202
|
return;
|
|
163
203
|
}
|
|
164
204
|
form?.requestSubmit?.();
|
|
165
205
|
});
|
|
166
206
|
bind(instance, task, 'keydown', event => instance.callbacks.onTaskKeydown?.(event));
|
|
167
|
-
bind(instance, task, 'input', event =>
|
|
207
|
+
bind(instance, task, 'input', event => {
|
|
208
|
+
if (acceptTaskInput(instance, task)) instance.callbacks.onTaskInput?.(event);
|
|
209
|
+
});
|
|
210
|
+
bind(instance, task, 'compositionstart', () => {
|
|
211
|
+
const composition = { scope: instance.callbacks.getTaskScope?.() };
|
|
212
|
+
composition.done = new Promise(resolve => { composition.resolve = resolve; });
|
|
213
|
+
instance.composition = composition;
|
|
214
|
+
});
|
|
215
|
+
bind(instance, task, 'compositionend', event => {
|
|
216
|
+
const composition = instance.composition;
|
|
217
|
+
if (acceptTaskInput(instance, task)) instance.callbacks.onInputChange?.(event);
|
|
218
|
+
// Chrome may emit the final input after compositionend. Keep its original
|
|
219
|
+
// owner until that event batch has finished, before a session can change.
|
|
220
|
+
if (composition) setTimeout(() => {
|
|
221
|
+
if (instance.composition === composition) instance.composition = null;
|
|
222
|
+
composition.resolve();
|
|
223
|
+
}, 0);
|
|
224
|
+
});
|
|
168
225
|
bind(instance, instance.container.querySelector('[data-slash-menu]'), 'click', event => instance.callbacks.onSlashMenuClick?.(event));
|
|
169
226
|
bind(instance, instance.container.querySelector('[data-composer-skill-clear]'), 'click', () => instance.callbacks.onClearSkillInvocation?.());
|
|
170
227
|
bind(instance, instance.container.querySelector('[data-add-context]'), 'click', () => instance.callbacks.onAddContext?.());
|
|
@@ -181,8 +238,11 @@
|
|
|
181
238
|
}
|
|
182
239
|
for (const selector of ['[data-model]', '[data-reasoning]', '[data-speed]', '[data-mode]', '[data-task]', '[data-require-reviewing]', '[data-auto-recompile]']) {
|
|
183
240
|
const element = instance.container.querySelector(selector);
|
|
184
|
-
|
|
185
|
-
|
|
241
|
+
const changed = event => {
|
|
242
|
+
if (element !== task || acceptTaskInput(instance, task)) instance.callbacks.onInputChange?.(event);
|
|
243
|
+
};
|
|
244
|
+
bind(instance, element, 'change', changed);
|
|
245
|
+
bind(instance, element, 'input', changed);
|
|
186
246
|
}
|
|
187
247
|
}
|
|
188
248
|
|
|
@@ -210,6 +270,25 @@
|
|
|
210
270
|
}
|
|
211
271
|
}
|
|
212
272
|
|
|
273
|
+
function acceptTaskInput(instance, task) {
|
|
274
|
+
if (instance.composition && instance.composition.scope !== instance.callbacks.getTaskScope?.()) {
|
|
275
|
+
task.value = instance.callbacks.getTaskValue?.() || '';
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
return !instance.sessionTransition || Boolean(instance.composition);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function setSessionTransition(instance, pending) {
|
|
282
|
+
const task = instance.container.querySelector('[data-task]');
|
|
283
|
+
if (pending && !instance.sessionTransition) instance.previousReadOnly = task?.readOnly === true;
|
|
284
|
+
instance.sessionTransition = pending;
|
|
285
|
+
const composition = instance.composition;
|
|
286
|
+
if (pending && composition) task?.blur();
|
|
287
|
+
if (task) task.readOnly = pending || instance.previousReadOnly === true;
|
|
288
|
+
setRunning(instance, Boolean(instance.callbacks.isRunning?.()));
|
|
289
|
+
return pending ? composition?.done : undefined;
|
|
290
|
+
}
|
|
291
|
+
|
|
213
292
|
function setRunning(target, running) {
|
|
214
293
|
const instance = target?._instance || target;
|
|
215
294
|
const runButton = instance?.container?.querySelector('[data-run]');
|
|
@@ -217,14 +296,14 @@
|
|
|
217
296
|
return;
|
|
218
297
|
}
|
|
219
298
|
const hasInput = Boolean(String(instance?.container?.querySelector('[data-task]')?.value || '').trim());
|
|
220
|
-
const action = running && !hasInput ? 'cancel' : 'send';
|
|
299
|
+
const action = running && (!hasInput || instance.sessionTransition) ? 'cancel' : 'send';
|
|
221
300
|
const label = action === 'cancel'
|
|
222
301
|
? t(instance, 'cancelRun')
|
|
223
302
|
: running
|
|
224
303
|
? t(instance, 'queueNextInput')
|
|
225
304
|
: t(instance, 'send');
|
|
226
305
|
runButton.dataset.action = action;
|
|
227
|
-
runButton.disabled = action === 'send' && !hasInput;
|
|
306
|
+
runButton.disabled = action === 'send' && (!hasInput || instance.sessionTransition);
|
|
228
307
|
runButton.title = label;
|
|
229
308
|
runButton.setAttribute('aria-label', label);
|
|
230
309
|
}
|
|
@@ -293,6 +372,7 @@
|
|
|
293
372
|
}
|
|
294
373
|
|
|
295
374
|
function destroy(instance) {
|
|
375
|
+
instance.composition?.resolve();
|
|
296
376
|
instance.menuResizeObserver?.disconnect();
|
|
297
377
|
for (const { target, type, listener } of instance.listeners.splice(0)) {
|
|
298
378
|
target.removeEventListener?.(type, listener);
|