glad-web 1.0.35 → 1.0.36
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/lib/codex/structured-session.js +49 -49
- package/lib/commands/web.js +6 -2
- package/lib/web/codex.js +130 -43
- package/lib/web/core.js +5 -5
- package/lib/web/session.js +8 -8
- package/lib/web/styles.css +1 -0
- package/package.json +1 -1
|
@@ -4,8 +4,6 @@ const readline = require('readline');
|
|
|
4
4
|
const crypto = require('crypto');
|
|
5
5
|
const PTYManager = require('../session/pty-manager');
|
|
6
6
|
|
|
7
|
-
const CODEX_MESSAGE_PAGE_BYTES = 200 * 1024;
|
|
8
|
-
|
|
9
7
|
const PERMISSION_MODES = new Set(['untrusted', 'on-request', 'never']);
|
|
10
8
|
const SANDBOX_MODES = new Set(['read-only', 'workspace-write', 'danger-full-access']);
|
|
11
9
|
const FALLBACK_EFFORTS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'ultra'];
|
|
@@ -199,6 +197,7 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
199
197
|
this.status = 'idle';
|
|
200
198
|
this.presentation = 'structured';
|
|
201
199
|
this.messages = [];
|
|
200
|
+
this.replayingHistory = false;
|
|
202
201
|
this.pendingPermissions = new Map();
|
|
203
202
|
this.completedPermissions = [];
|
|
204
203
|
this.threadId = options.resume || null;
|
|
@@ -252,58 +251,51 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
252
251
|
}
|
|
253
252
|
|
|
254
253
|
snapshot() {
|
|
255
|
-
const historyPage = this.getMessagePage();
|
|
256
|
-
const { messages, ...historyPageMeta } = historyPage;
|
|
257
254
|
return { id: this.id, name: this.name, tool: this.tool.displayName, toolKey: this.tool.key,
|
|
258
|
-
status: this.status, state: this.getControlState(), messages
|
|
255
|
+
status: this.status, state: this.getControlState(), messages: this.messages.map(item => this.toPublicMessage(item)),
|
|
259
256
|
pendingPermissions: [
|
|
260
257
|
...this.completedPermissions,
|
|
261
258
|
...Array.from(this.pendingPermissions.values()).map(item => item.public)
|
|
262
259
|
] };
|
|
263
260
|
}
|
|
264
261
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (beforeIndex < 0) {
|
|
271
|
-
return { messages: [], hasMore: false, beforeId: null, bytes: 2, maxBytes };
|
|
272
|
-
}
|
|
273
|
-
end = beforeIndex;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (end <= 0) return { messages: [], hasMore: false, beforeId: null, bytes: 2, maxBytes };
|
|
262
|
+
toPublicMessage(item) {
|
|
263
|
+
if (!item || typeof item !== 'object') return item;
|
|
264
|
+
const message = { ...item };
|
|
265
|
+
const isSubagent = Boolean(message.threadId && this.threadId && message.threadId !== this.threadId);
|
|
266
|
+
let hasDetail = false;
|
|
277
267
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
268
|
+
if (message.kind === 'tool') {
|
|
269
|
+
for (const field of ['result', 'input', 'changes', 'error', 'agentsStates']) {
|
|
270
|
+
const value = message[field];
|
|
271
|
+
if (value != null && value !== '' && (!Array.isArray(value) || value.length)) hasDetail = true;
|
|
272
|
+
delete message[field];
|
|
283
273
|
}
|
|
274
|
+
if (item.error) message.hasError = true;
|
|
284
275
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (start < end && candidateBytes > maxBytes) break;
|
|
294
|
-
start = candidateStart;
|
|
295
|
-
selectedBytes = candidateBytes;
|
|
296
|
-
if (candidateBytes >= maxBytes) break;
|
|
276
|
+
if (isSubagent && ['user', 'assistant', 'reasoning', 'event'].includes(message.kind)) {
|
|
277
|
+
if (message.text) hasDetail = true;
|
|
278
|
+
delete message.text;
|
|
279
|
+
delete message.skills;
|
|
280
|
+
}
|
|
281
|
+
if (message.kind === 'reasoning') {
|
|
282
|
+
if (message.text) hasDetail = true;
|
|
283
|
+
delete message.text;
|
|
297
284
|
}
|
|
285
|
+
message.hasDetail = hasDetail;
|
|
286
|
+
message.detailRevision = Number(item.updatedAt || item.createdAt || 0);
|
|
287
|
+
return message;
|
|
288
|
+
}
|
|
298
289
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
};
|
|
290
|
+
getMessageDetails({ ids = [], threadId = null } = {}) {
|
|
291
|
+
const requestedIds = new Set((Array.isArray(ids) ? ids : [])
|
|
292
|
+
.map(value => String(value || '')).filter(Boolean));
|
|
293
|
+
const requestedThreadId = threadId == null ? '' : String(threadId);
|
|
294
|
+
const messages = this.messages.filter(item => {
|
|
295
|
+
if (requestedThreadId && String(item.threadId || '') === requestedThreadId) return true;
|
|
296
|
+
return requestedIds.has(String(item.id || ''));
|
|
297
|
+
}).map(item => ({ ...item, detailLoaded: true }));
|
|
298
|
+
return { messages, threadId: requestedThreadId || null };
|
|
307
299
|
}
|
|
308
300
|
|
|
309
301
|
getControlState() {
|
|
@@ -340,12 +332,17 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
340
332
|
isRunning() { return this.running && (this.presentation !== 'terminal' || Boolean(this.terminalSession)); }
|
|
341
333
|
|
|
342
334
|
createItem(item) { return { id: crypto.randomUUID(), createdAt: Date.now(), ...item }; }
|
|
343
|
-
append(item) {
|
|
335
|
+
append(item) {
|
|
336
|
+
const next = this.createItem(item);
|
|
337
|
+
this.messages.push(next);
|
|
338
|
+
if (!this.replayingHistory) this.emitEvent({ type: 'message', message: this.toPublicMessage(next) });
|
|
339
|
+
return next;
|
|
340
|
+
}
|
|
344
341
|
patch(id, patch) {
|
|
345
342
|
const item = this.messages.find(message => message.id === id);
|
|
346
343
|
if (!item) return null;
|
|
347
344
|
Object.assign(item, { updatedAt: Date.now() }, patch);
|
|
348
|
-
this.emitEvent({ type: 'message-updated', message: item });
|
|
345
|
+
if (!this.replayingHistory) this.emitEvent({ type: 'message-updated', message: this.toPublicMessage(item) });
|
|
349
346
|
return item;
|
|
350
347
|
}
|
|
351
348
|
emitEvent(event) { this.emit('event', event); }
|
|
@@ -1112,10 +1109,12 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
1112
1109
|
if (!options.preserveEffort) this.effort = thread?.reasoningEffort || thread?.reasoning_effort || this.effort;
|
|
1113
1110
|
this.tokenUsage = thread?.tokenUsage || thread?.token_usage || this.tokenUsage;
|
|
1114
1111
|
this.messages = [];
|
|
1112
|
+
this.replayingHistory = true;
|
|
1115
1113
|
this.completedPermissions = [];
|
|
1116
1114
|
this.turnContexts.clear();
|
|
1117
1115
|
this.providerItemContexts.clear();
|
|
1118
|
-
|
|
1116
|
+
try {
|
|
1117
|
+
for (const turn of thread?.turns || []) {
|
|
1119
1118
|
const status = turn.status === 'failed' ? 'failed' : turn.status === 'interrupted' ? 'cancelled' : 'completed';
|
|
1120
1119
|
const startedAt = Number(turn.startedAt || turn.createdAt || 0);
|
|
1121
1120
|
const completedAt = Number(turn.completedAt || turn.updatedAt || 0);
|
|
@@ -1134,11 +1133,12 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
1134
1133
|
|| (startedAtMs && completedAtMs && completedAtMs >= startedAtMs ? completedAtMs - startedAtMs : null);
|
|
1135
1134
|
this.append({ kind: 'turn-end', turnId: turn.id, status, durationMs,
|
|
1136
1135
|
...(completedAtMs ? { createdAt: completedAtMs } : {}) });
|
|
1136
|
+
}
|
|
1137
|
+
if (eventText) this.append({ kind: 'event', level: 'info', text: eventText });
|
|
1138
|
+
} finally {
|
|
1139
|
+
this.replayingHistory = false;
|
|
1137
1140
|
}
|
|
1138
|
-
|
|
1139
|
-
const historyPage = this.getMessagePage();
|
|
1140
|
-
const { messages, ...historyPageMeta } = historyPage;
|
|
1141
|
-
this.emitEvent({ type: 'history-reset', messages, historyPage: historyPageMeta });
|
|
1141
|
+
this.emitEvent({ type: 'history-reset', messages: this.messages.map(item => this.toPublicMessage(item)) });
|
|
1142
1142
|
this.emitEvent({ type: 'state', state: this.getControlState() });
|
|
1143
1143
|
}
|
|
1144
1144
|
|
package/lib/commands/web.js
CHANGED
|
@@ -377,10 +377,14 @@ async function webCommand(options) {
|
|
|
377
377
|
if (payload.type === 'codex-compact') {
|
|
378
378
|
sessionManager.compactCodexContext(sessionId).catch(error => logger.error(`Codex compact error: ${error.message}`));
|
|
379
379
|
}
|
|
380
|
-
if (payload.type === 'codex-
|
|
380
|
+
if (payload.type === 'codex-detail-request') {
|
|
381
381
|
const codex = sessionManager.get(sessionId);
|
|
382
382
|
if (codex && codex.kind === 'codex-structured' && codex.presentation === 'structured') {
|
|
383
|
-
ws.send(JSON.stringify({
|
|
383
|
+
ws.send(JSON.stringify({
|
|
384
|
+
type: 'codex-detail-response',
|
|
385
|
+
requestId: payload.requestId || null,
|
|
386
|
+
detail: codex.getMessageDetails({ ids: payload.ids, threadId: payload.threadId })
|
|
387
|
+
}));
|
|
384
388
|
}
|
|
385
389
|
}
|
|
386
390
|
if (payload.type === 'codex-abort') {
|
package/lib/web/codex.js
CHANGED
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
if (status === 'completed') return item.exitCode && item.exitCode !== 0 ? 'failed' : 'completed';
|
|
11
11
|
return status;
|
|
12
12
|
}
|
|
13
|
+
function codexMessageNeedsDetail(item) {
|
|
14
|
+
if (!item?.hasDetail) return false;
|
|
15
|
+
const loadedRevision = Number(codexDetailRevisions.get(String(item.id)) || 0);
|
|
16
|
+
return !item.detailLoaded || loadedRevision < Number(item.detailRevision || 0);
|
|
17
|
+
}
|
|
13
18
|
function formatCodexDuration(durationMs) {
|
|
14
19
|
const value = Number(durationMs || 0);
|
|
15
20
|
if (!(value > 0)) return '';
|
|
@@ -108,7 +113,7 @@
|
|
|
108
113
|
}
|
|
109
114
|
function renderCodexTool(item, permission = null) {
|
|
110
115
|
const status = codexToolStatus(item);
|
|
111
|
-
const isError = status === 'failed' || Boolean(item.error) || (item.exitCode != null && item.exitCode !== 0);
|
|
116
|
+
const isError = status === 'failed' || Boolean(item.error) || Boolean(item.hasError) || (item.exitCode != null && item.exitCode !== 0);
|
|
112
117
|
const runningClass = status === 'running' ? ' running' : '';
|
|
113
118
|
if (item.name === 'CodexPatch') {
|
|
114
119
|
return `<div class="codex-tool${isError ? ' error' : ''}">${renderCodexPatch(item)}${permission ? renderCodexPermission(permission, true) : ''}</div>`;
|
|
@@ -138,7 +143,11 @@
|
|
|
138
143
|
}).join('');
|
|
139
144
|
const label = running ? 'Working' : failed ? 'Work finished with errors' : duration ? `Worked for ${duration}` : 'Worked';
|
|
140
145
|
const key = items.map(item => item.id || item.providerId || '').join('-');
|
|
141
|
-
|
|
146
|
+
const allDetailIds = items.filter(item => item.hasDetail).map(item => item.id).filter(Boolean);
|
|
147
|
+
const detailIds = items.filter(codexMessageNeedsDetail).map(item => item.id).filter(Boolean);
|
|
148
|
+
const lazy = detailIds.length
|
|
149
|
+
? '<div class="codex-lazy-detail">Open to load tool details…</div>' : '';
|
|
150
|
+
return `<details class="codex-work-group" data-codex-key="group-${escapeHtml(key)}"${allDetailIds.length ? ` data-codex-detail-ids="${escapeHtml(allDetailIds.join(','))}"` : ''}><summary>${label} · ${items.length} ${items.length === 1 ? 'tool' : 'tools'}</summary><div class="codex-work-group-body">${tools}${lazy}</div></details>`;
|
|
142
151
|
}
|
|
143
152
|
function isCodexSubagentItem(item) {
|
|
144
153
|
return Boolean(item?.threadId && codexState.threadId && item.threadId !== codexState.threadId);
|
|
@@ -159,8 +168,13 @@
|
|
|
159
168
|
if (tools.length) counts.push(`${tools.length} ${tools.length === 1 ? 'tool' : 'tools'}`);
|
|
160
169
|
if (messages.length) counts.push(`${messages.length} ${messages.length === 1 ? 'message' : 'messages'}`);
|
|
161
170
|
const label = running ? 'Subagent working' : duration ? `Subagent worked for ${duration}` : 'Subagent worked';
|
|
171
|
+
const needsDetail = items.some(codexMessageNeedsDetail);
|
|
172
|
+
const deferContent = needsDetail && !items.some(item => item.detailLoaded);
|
|
162
173
|
const body = [];
|
|
163
|
-
|
|
174
|
+
if (needsDetail) {
|
|
175
|
+
body.push(`<div class="codex-lazy-detail">${deferContent ? 'Open to load subagent details…' : 'Updating subagent details…'}</div>`);
|
|
176
|
+
}
|
|
177
|
+
for (let i = 0; !deferContent && i < content.length;) {
|
|
164
178
|
const item = content[i];
|
|
165
179
|
if (item.kind === 'tool') {
|
|
166
180
|
const group = [];
|
|
@@ -171,14 +185,14 @@
|
|
|
171
185
|
continue;
|
|
172
186
|
}
|
|
173
187
|
if (item.kind === 'assistant' || item.kind === 'user') {
|
|
174
|
-
body.push(`<div class="codex-subagent-message${item.kind === 'user' ? ' task' : ''}">${renderMarkdown(item.text
|
|
188
|
+
if (item.text) body.push(`<div class="codex-subagent-message${item.kind === 'user' ? ' task' : ''}">${renderMarkdown(item.text)}</div>`);
|
|
175
189
|
} else if (item.text) {
|
|
176
190
|
body.push(`<div class="codex-subagent-message">${codexText(item.text)}</div>`);
|
|
177
191
|
}
|
|
178
192
|
i += 1;
|
|
179
193
|
}
|
|
180
194
|
const suffix = counts.length ? ` · ${counts.join(' · ')}` : '';
|
|
181
|
-
return `<details class="codex-work-group codex-subagent-group" data-codex-key="subagent-${escapeHtml(threadId)}"><summary>${escapeHtml(label + suffix)}</summary><div class="codex-work-group-body">${body.join('')}</div></details>`;
|
|
195
|
+
return `<details class="codex-work-group codex-subagent-group" data-codex-key="subagent-${escapeHtml(threadId)}" data-codex-thread-id="${escapeHtml(threadId)}"><summary>${escapeHtml(label + suffix)}</summary><div class="codex-work-group-body">${body.join('')}</div></details>`;
|
|
182
196
|
}
|
|
183
197
|
function renderCodexMessageTime(item, finalOnly = false) {
|
|
184
198
|
if (!item || (finalOnly && item.streaming)) return '';
|
|
@@ -320,46 +334,89 @@
|
|
|
320
334
|
const current = container.firstElementChild;
|
|
321
335
|
if (!current) container.appendChild(next);
|
|
322
336
|
else syncCodexDom(current, next);
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
337
|
+
container.scrollTop = shouldStickToBottom ? container.scrollHeight : previousScrollTop;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function installCodexLazyDetailHandler() {
|
|
341
|
+
const container = document.getElementById('codex-chat-container');
|
|
342
|
+
if (!container || container.dataset.lazyDetailHandler === 'true') return;
|
|
343
|
+
container.dataset.lazyDetailHandler = 'true';
|
|
344
|
+
container.addEventListener('toggle', handleCodexLazyToggle, true);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function handleCodexLazyToggle(event) {
|
|
348
|
+
const group = event.target;
|
|
349
|
+
if (!(group instanceof HTMLDetailsElement) || !group.open || !group.classList.contains('codex-work-group')) return;
|
|
350
|
+
const threadId = group.dataset.codexThreadId || '';
|
|
351
|
+
const ids = String(group.dataset.codexDetailIds || '').split(',').filter(Boolean)
|
|
352
|
+
.filter(id => codexMessageNeedsDetail(codexMessages.find(item => item.id === id)));
|
|
353
|
+
if (threadId && codexMessages.some(item => item.threadId === threadId && codexMessageNeedsDetail(item))) {
|
|
354
|
+
void requestCodexDetails({ threadId });
|
|
329
355
|
}
|
|
356
|
+
else if (ids.length) void requestCodexDetails({ ids });
|
|
330
357
|
}
|
|
331
358
|
|
|
332
|
-
function
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
359
|
+
function requestCodexDetails({ ids = [], threadId = '' } = {}) {
|
|
360
|
+
if (currentSocket?.readyState !== 1) return Promise.resolve(false);
|
|
361
|
+
const normalizedIds = Array.from(new Set(ids.map(value => String(value || '')).filter(Boolean))).sort();
|
|
362
|
+
const key = threadId ? `thread:${threadId}` : `ids:${normalizedIds.join(',')}`;
|
|
363
|
+
for (const pending of codexDetailRequests.values()) {
|
|
364
|
+
if (pending.key === key) return pending.promise;
|
|
365
|
+
}
|
|
366
|
+
const requestId = `detail-${++codexDetailRequestSeq}`;
|
|
367
|
+
let resolveRequest;
|
|
368
|
+
const promise = new Promise(resolve => { resolveRequest = resolve; });
|
|
369
|
+
const timer = setTimeout(() => {
|
|
370
|
+
const pending = codexDetailRequests.get(requestId);
|
|
371
|
+
if (!pending) return;
|
|
372
|
+
codexDetailRequests.delete(requestId);
|
|
373
|
+
pending.resolve(false);
|
|
374
|
+
}, 15000);
|
|
375
|
+
codexDetailRequests.set(requestId, { key, promise, resolve: resolveRequest, timer });
|
|
376
|
+
currentSocket.send(JSON.stringify({
|
|
377
|
+
type: 'codex-detail-request',
|
|
378
|
+
requestId,
|
|
379
|
+
...(threadId ? { threadId } : { ids: normalizedIds })
|
|
380
|
+
}));
|
|
381
|
+
return promise;
|
|
336
382
|
}
|
|
337
383
|
|
|
338
|
-
function
|
|
339
|
-
|
|
340
|
-
if (!
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
384
|
+
function applyCodexDetailResponse(response) {
|
|
385
|
+
const pending = codexDetailRequests.get(response.requestId);
|
|
386
|
+
if (!pending) return;
|
|
387
|
+
const detail = response.detail || {};
|
|
388
|
+
for (const message of detail.messages || []) {
|
|
389
|
+
const index = codexMessages.findIndex(item => item.id === message.id);
|
|
390
|
+
if (index >= 0) codexMessages[index] = { ...codexMessages[index], ...message, detailLoaded: true };
|
|
391
|
+
else codexMessages.push({ ...message, detailLoaded: true });
|
|
392
|
+
codexDetailRevisions.set(String(message.id), Number(message.updatedAt || message.createdAt || 0));
|
|
393
|
+
}
|
|
394
|
+
clearTimeout(pending.timer);
|
|
395
|
+
codexDetailRequests.delete(response.requestId);
|
|
396
|
+
pending.resolve(true);
|
|
397
|
+
renderCodexChat();
|
|
347
398
|
}
|
|
348
399
|
|
|
349
|
-
function
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (uniqueOlder.length && container) {
|
|
355
|
-
codexHistoryPrependAnchor = {
|
|
356
|
-
scrollTop: container.scrollTop,
|
|
357
|
-
scrollHeight: container.scrollHeight
|
|
358
|
-
};
|
|
359
|
-
codexMessages = [...uniqueOlder, ...codexMessages];
|
|
400
|
+
function codexDetailIsOpen(message) {
|
|
401
|
+
if (!message?.id) return false;
|
|
402
|
+
if (isCodexSubagentItem(message)) {
|
|
403
|
+
return Array.from(document.querySelectorAll('.codex-subagent-group[open]'))
|
|
404
|
+
.some(group => group.dataset.codexKey === `subagent-${message.threadId}`);
|
|
360
405
|
}
|
|
361
|
-
|
|
362
|
-
|
|
406
|
+
return Array.from(document.querySelectorAll('.codex-work-group[open][data-codex-detail-ids]'))
|
|
407
|
+
.some(group => String(group.dataset.codexDetailIds || '').split(',').includes(String(message.id)));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function scheduleCodexDetailRefresh(message) {
|
|
411
|
+
if (!message || !codexDetailIsOpen(message)) return;
|
|
412
|
+
if (!message.id || (!message.detailLoaded && !isCodexSubagentItem(message))) return;
|
|
413
|
+
codexDetailRefreshIds.add(String(message.id));
|
|
414
|
+
clearTimeout(codexDetailRefreshTimer);
|
|
415
|
+
codexDetailRefreshTimer = setTimeout(() => {
|
|
416
|
+
const ids = Array.from(codexDetailRefreshIds);
|
|
417
|
+
codexDetailRefreshIds.clear();
|
|
418
|
+
if (ids.length) void requestCodexDetails({ ids });
|
|
419
|
+
}, 300);
|
|
363
420
|
}
|
|
364
421
|
|
|
365
422
|
function applyCodexState(state = {}) {
|
|
@@ -418,7 +475,18 @@
|
|
|
418
475
|
if (!pending.length) return false;
|
|
419
476
|
const request = pending[codexApprovalJumpIndex % pending.length];
|
|
420
477
|
codexApprovalJumpIndex = (codexApprovalJumpIndex + 1) % pending.length;
|
|
421
|
-
|
|
478
|
+
void loadAndFocusCodexApproval(request);
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function loadAndFocusCodexApproval(request) {
|
|
483
|
+
const permissionId = String(request?.id || '');
|
|
484
|
+
const message = codexMessages.find(item => String(item.providerId || item.id || '') === permissionId);
|
|
485
|
+
if (message && codexMessageNeedsDetail(message)) {
|
|
486
|
+
if (isCodexSubagentItem(message)) await requestCodexDetails({ threadId: String(message.threadId) });
|
|
487
|
+
else await requestCodexDetails({ ids: [String(message.id)] });
|
|
488
|
+
}
|
|
489
|
+
requestAnimationFrame(() => focusCodexApproval(permissionId));
|
|
422
490
|
}
|
|
423
491
|
|
|
424
492
|
function focusCodexApproval(permissionId, retry = true) {
|
|
@@ -482,13 +550,32 @@
|
|
|
482
550
|
|
|
483
551
|
function applyCodexEvent(event) {
|
|
484
552
|
if (!event) return;
|
|
485
|
-
if (event.type === 'message' && event.message)
|
|
486
|
-
|
|
553
|
+
if (event.type === 'message' && event.message) {
|
|
554
|
+
codexMessages.push(event.message);
|
|
555
|
+
scheduleCodexDetailRefresh(event.message);
|
|
556
|
+
}
|
|
557
|
+
else if (event.type === 'message-updated' && event.message) {
|
|
558
|
+
const i = codexMessages.findIndex(item => item.id === event.message.id);
|
|
559
|
+
if (i >= 0) {
|
|
560
|
+
const existing = codexMessages[i];
|
|
561
|
+
codexMessages[i] = existing.detailLoaded
|
|
562
|
+
? { ...existing, ...event.message, detailLoaded: true }
|
|
563
|
+
: event.message;
|
|
564
|
+
scheduleCodexDetailRefresh(codexMessages[i]);
|
|
565
|
+
} else codexMessages.push(event.message);
|
|
566
|
+
}
|
|
487
567
|
else if (event.type === 'history-reset') {
|
|
488
568
|
codexMessages = event.messages || [];
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
569
|
+
codexDetailRevisions.clear();
|
|
570
|
+
clearTimeout(codexDetailRefreshTimer);
|
|
571
|
+
codexDetailRefreshTimer = null;
|
|
572
|
+
codexDetailRefreshIds.clear();
|
|
573
|
+
for (const pending of codexDetailRequests.values()) {
|
|
574
|
+
clearTimeout(pending.timer);
|
|
575
|
+
pending.resolve(false);
|
|
576
|
+
}
|
|
577
|
+
codexDetailRequests.clear();
|
|
578
|
+
document.getElementById('codex-chat-container')?.replaceChildren();
|
|
492
579
|
}
|
|
493
580
|
else if (event.type === 'permission-request' && event.request) { codexPendingPermissions = [...codexPendingPermissions.filter(item => item.id !== event.request.id), event.request]; }
|
|
494
581
|
else if (event.type === 'permission-updated' && event.request) codexPendingPermissions = codexPendingPermissions.map(item => item.id === event.request.id ? event.request : item);
|
package/lib/web/core.js
CHANGED
|
@@ -52,11 +52,11 @@
|
|
|
52
52
|
let selectedCodexSkill = null;
|
|
53
53
|
let codexRenderFrame = null;
|
|
54
54
|
let codexApprovalJumpIndex = 0;
|
|
55
|
-
let
|
|
56
|
-
let
|
|
57
|
-
let
|
|
58
|
-
let
|
|
59
|
-
let
|
|
55
|
+
let codexDetailRequestSeq = 0;
|
|
56
|
+
let codexDetailRequests = new Map();
|
|
57
|
+
let codexDetailRevisions = new Map();
|
|
58
|
+
let codexDetailRefreshTimer = null;
|
|
59
|
+
let codexDetailRefreshIds = new Set();
|
|
60
60
|
const modifiers = { ctrl: false };
|
|
61
61
|
|
|
62
62
|
function log(msg) {
|
package/lib/web/session.js
CHANGED
|
@@ -143,11 +143,12 @@
|
|
|
143
143
|
codexSkillQuery = '';
|
|
144
144
|
codexSkillError = '';
|
|
145
145
|
selectedCodexSkill = null;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
codexDetailRequestSeq = 0;
|
|
147
|
+
codexDetailRequests = new Map();
|
|
148
|
+
codexDetailRevisions = new Map();
|
|
149
|
+
clearTimeout(codexDetailRefreshTimer);
|
|
150
|
+
codexDetailRefreshTimer = null;
|
|
151
|
+
codexDetailRefreshIds = new Set();
|
|
151
152
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
152
153
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
153
154
|
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
@@ -157,7 +158,7 @@
|
|
|
157
158
|
codexState = { permissionMode: 'default', sandboxMode: 'default', effectivePermissionMode: null, effectiveSandboxMode: null, model: null, effort: null, status: 'idle', threadId: null, presentation: 'structured', models: [], canAbort: false, canCompact: false, compacting: false, canSwitchToTerminal: false, canSwitchToStructured: false };
|
|
158
159
|
setClaudeModeEnabled(false);
|
|
159
160
|
applyCodexState(codexState);
|
|
160
|
-
|
|
161
|
+
installCodexLazyDetailHandler();
|
|
161
162
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
162
163
|
currentSocket = new WebSocket(protocol + '//' + window.location.host + '?sessionId=' + sessionId);
|
|
163
164
|
currentSocket.onmessage = (e) => {
|
|
@@ -165,10 +166,9 @@
|
|
|
165
166
|
if (msg.type === 'codex-snapshot' && msg.snapshot) {
|
|
166
167
|
codexMessages = msg.snapshot.messages || [];
|
|
167
168
|
codexPendingPermissions = msg.snapshot.pendingPermissions || [];
|
|
168
|
-
applyCodexHistoryPageMeta(msg.snapshot.historyPage);
|
|
169
169
|
applyCodexState(msg.snapshot.state || {});
|
|
170
170
|
}
|
|
171
|
-
if (msg.type === 'codex-
|
|
171
|
+
if (msg.type === 'codex-detail-response' && msg.detail) applyCodexDetailResponse(msg);
|
|
172
172
|
if (msg.type === 'codex-event') {
|
|
173
173
|
applyCodexEvent(msg.event);
|
|
174
174
|
if (msg.event?.type === 'presentation' && msg.event.presentation === 'terminal') {
|
package/lib/web/styles.css
CHANGED
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
#claude-chat-container { display: none; flex: 1; min-height: 0; overflow-y: auto; background: #050505; padding: 12px 12px 24px 12px; box-sizing: border-box; -webkit-overflow-scrolling: touch; }
|
|
86
86
|
.claude-conversation { width: min(100%, var(--chat-content-max)); min-height: 100%; margin: 0 auto; }
|
|
87
87
|
#codex-chat-container { display: none; flex: 1; min-height: 0; overflow-y: auto; background: #050505; padding: 12px 12px 24px 12px; box-sizing: border-box; -webkit-overflow-scrolling: touch; }
|
|
88
|
+
.codex-lazy-detail { padding: 10px 12px; color: var(--text-dim); font-size: 12px; }
|
|
88
89
|
.codex-conversation { width: min(100%, var(--chat-content-max)); margin: 0 auto; }
|
|
89
90
|
.codex-working-indicator, .claude-working-indicator { position: sticky; top: 0; z-index: 4; width: 28px; height: 28px; margin: 0 0 -28px auto; border: 1px solid rgba(255,255,255,.1); border-radius: 50%; background: rgba(28,28,30,.68); box-shadow: 0 5px 16px rgba(0,0,0,.24); backdrop-filter: blur(8px); pointer-events: none; }
|
|
90
91
|
.codex-working-indicator::after, .claude-working-indicator::after { content: ''; position: absolute; inset: 8px; border: 1.5px solid rgba(255,255,255,.7); border-top-color: transparent; border-radius: 50%; animation: codex-spin .8s linear infinite; }
|