glad-web 1.0.34 → 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 +56 -6
- package/lib/commands/web.js +10 -0
- package/lib/web/codex.js +140 -9
- package/lib/web/core.js +5 -0
- package/lib/web/session.js +8 -0
- package/lib/web/styles.css +1 -0
- package/package.json +1 -1
|
@@ -197,6 +197,7 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
197
197
|
this.status = 'idle';
|
|
198
198
|
this.presentation = 'structured';
|
|
199
199
|
this.messages = [];
|
|
200
|
+
this.replayingHistory = false;
|
|
200
201
|
this.pendingPermissions = new Map();
|
|
201
202
|
this.completedPermissions = [];
|
|
202
203
|
this.threadId = options.resume || null;
|
|
@@ -251,13 +252,52 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
251
252
|
|
|
252
253
|
snapshot() {
|
|
253
254
|
return { id: this.id, name: this.name, tool: this.tool.displayName, toolKey: this.tool.key,
|
|
254
|
-
status: this.status, state: this.getControlState(), messages: this.messages,
|
|
255
|
+
status: this.status, state: this.getControlState(), messages: this.messages.map(item => this.toPublicMessage(item)),
|
|
255
256
|
pendingPermissions: [
|
|
256
257
|
...this.completedPermissions,
|
|
257
258
|
...Array.from(this.pendingPermissions.values()).map(item => item.public)
|
|
258
259
|
] };
|
|
259
260
|
}
|
|
260
261
|
|
|
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;
|
|
267
|
+
|
|
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];
|
|
273
|
+
}
|
|
274
|
+
if (item.error) message.hasError = true;
|
|
275
|
+
}
|
|
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;
|
|
284
|
+
}
|
|
285
|
+
message.hasDetail = hasDetail;
|
|
286
|
+
message.detailRevision = Number(item.updatedAt || item.createdAt || 0);
|
|
287
|
+
return message;
|
|
288
|
+
}
|
|
289
|
+
|
|
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 };
|
|
299
|
+
}
|
|
300
|
+
|
|
261
301
|
getControlState() {
|
|
262
302
|
const activeSubagentCount = Array.from(this.threadTurns.entries())
|
|
263
303
|
.filter(([threadId, turn]) => threadId !== this.threadId && turn?.status === 'running').length;
|
|
@@ -292,12 +332,17 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
292
332
|
isRunning() { return this.running && (this.presentation !== 'terminal' || Boolean(this.terminalSession)); }
|
|
293
333
|
|
|
294
334
|
createItem(item) { return { id: crypto.randomUUID(), createdAt: Date.now(), ...item }; }
|
|
295
|
-
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
|
+
}
|
|
296
341
|
patch(id, patch) {
|
|
297
342
|
const item = this.messages.find(message => message.id === id);
|
|
298
343
|
if (!item) return null;
|
|
299
344
|
Object.assign(item, { updatedAt: Date.now() }, patch);
|
|
300
|
-
this.emitEvent({ type: 'message-updated', message: item });
|
|
345
|
+
if (!this.replayingHistory) this.emitEvent({ type: 'message-updated', message: this.toPublicMessage(item) });
|
|
301
346
|
return item;
|
|
302
347
|
}
|
|
303
348
|
emitEvent(event) { this.emit('event', event); }
|
|
@@ -1064,10 +1109,12 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
1064
1109
|
if (!options.preserveEffort) this.effort = thread?.reasoningEffort || thread?.reasoning_effort || this.effort;
|
|
1065
1110
|
this.tokenUsage = thread?.tokenUsage || thread?.token_usage || this.tokenUsage;
|
|
1066
1111
|
this.messages = [];
|
|
1112
|
+
this.replayingHistory = true;
|
|
1067
1113
|
this.completedPermissions = [];
|
|
1068
1114
|
this.turnContexts.clear();
|
|
1069
1115
|
this.providerItemContexts.clear();
|
|
1070
|
-
|
|
1116
|
+
try {
|
|
1117
|
+
for (const turn of thread?.turns || []) {
|
|
1071
1118
|
const status = turn.status === 'failed' ? 'failed' : turn.status === 'interrupted' ? 'cancelled' : 'completed';
|
|
1072
1119
|
const startedAt = Number(turn.startedAt || turn.createdAt || 0);
|
|
1073
1120
|
const completedAt = Number(turn.completedAt || turn.updatedAt || 0);
|
|
@@ -1086,9 +1133,12 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
1086
1133
|
|| (startedAtMs && completedAtMs && completedAtMs >= startedAtMs ? completedAtMs - startedAtMs : null);
|
|
1087
1134
|
this.append({ kind: 'turn-end', turnId: turn.id, status, durationMs,
|
|
1088
1135
|
...(completedAtMs ? { createdAt: completedAtMs } : {}) });
|
|
1136
|
+
}
|
|
1137
|
+
if (eventText) this.append({ kind: 'event', level: 'info', text: eventText });
|
|
1138
|
+
} finally {
|
|
1139
|
+
this.replayingHistory = false;
|
|
1089
1140
|
}
|
|
1090
|
-
|
|
1091
|
-
this.emitEvent({ type: 'history-reset', messages: this.messages });
|
|
1141
|
+
this.emitEvent({ type: 'history-reset', messages: this.messages.map(item => this.toPublicMessage(item)) });
|
|
1092
1142
|
this.emitEvent({ type: 'state', state: this.getControlState() });
|
|
1093
1143
|
}
|
|
1094
1144
|
|
package/lib/commands/web.js
CHANGED
|
@@ -377,6 +377,16 @@ 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-detail-request') {
|
|
381
|
+
const codex = sessionManager.get(sessionId);
|
|
382
|
+
if (codex && codex.kind === 'codex-structured' && codex.presentation === 'structured') {
|
|
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
|
+
}));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
380
390
|
if (payload.type === 'codex-abort') {
|
|
381
391
|
sessionManager.abortCodex(sessionId);
|
|
382
392
|
}
|
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 '';
|
|
@@ -323,6 +337,88 @@
|
|
|
323
337
|
container.scrollTop = shouldStickToBottom ? container.scrollHeight : previousScrollTop;
|
|
324
338
|
}
|
|
325
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 });
|
|
355
|
+
}
|
|
356
|
+
else if (ids.length) void requestCodexDetails({ ids });
|
|
357
|
+
}
|
|
358
|
+
|
|
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;
|
|
382
|
+
}
|
|
383
|
+
|
|
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();
|
|
398
|
+
}
|
|
399
|
+
|
|
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}`);
|
|
405
|
+
}
|
|
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);
|
|
420
|
+
}
|
|
421
|
+
|
|
326
422
|
function applyCodexState(state = {}) {
|
|
327
423
|
const presentationChanged = state.presentation !== undefined && state.presentation !== codexState.presentation;
|
|
328
424
|
codexState = { ...codexState, ...state };
|
|
@@ -379,7 +475,18 @@
|
|
|
379
475
|
if (!pending.length) return false;
|
|
380
476
|
const request = pending[codexApprovalJumpIndex % pending.length];
|
|
381
477
|
codexApprovalJumpIndex = (codexApprovalJumpIndex + 1) % pending.length;
|
|
382
|
-
|
|
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));
|
|
383
490
|
}
|
|
384
491
|
|
|
385
492
|
function focusCodexApproval(permissionId, retry = true) {
|
|
@@ -443,9 +550,33 @@
|
|
|
443
550
|
|
|
444
551
|
function applyCodexEvent(event) {
|
|
445
552
|
if (!event) return;
|
|
446
|
-
if (event.type === 'message' && event.message)
|
|
447
|
-
|
|
448
|
-
|
|
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
|
+
}
|
|
567
|
+
else if (event.type === 'history-reset') {
|
|
568
|
+
codexMessages = event.messages || [];
|
|
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();
|
|
579
|
+
}
|
|
449
580
|
else if (event.type === 'permission-request' && event.request) { codexPendingPermissions = [...codexPendingPermissions.filter(item => item.id !== event.request.id), event.request]; }
|
|
450
581
|
else if (event.type === 'permission-updated' && event.request) codexPendingPermissions = codexPendingPermissions.map(item => item.id === event.request.id ? event.request : item);
|
|
451
582
|
if (event.state) applyCodexState(event.state); else renderCodexChat();
|
package/lib/web/core.js
CHANGED
|
@@ -52,6 +52,11 @@
|
|
|
52
52
|
let selectedCodexSkill = null;
|
|
53
53
|
let codexRenderFrame = null;
|
|
54
54
|
let codexApprovalJumpIndex = 0;
|
|
55
|
+
let codexDetailRequestSeq = 0;
|
|
56
|
+
let codexDetailRequests = new Map();
|
|
57
|
+
let codexDetailRevisions = new Map();
|
|
58
|
+
let codexDetailRefreshTimer = null;
|
|
59
|
+
let codexDetailRefreshIds = new Set();
|
|
55
60
|
const modifiers = { ctrl: false };
|
|
56
61
|
|
|
57
62
|
function log(msg) {
|
package/lib/web/session.js
CHANGED
|
@@ -143,6 +143,12 @@
|
|
|
143
143
|
codexSkillQuery = '';
|
|
144
144
|
codexSkillError = '';
|
|
145
145
|
selectedCodexSkill = null;
|
|
146
|
+
codexDetailRequestSeq = 0;
|
|
147
|
+
codexDetailRequests = new Map();
|
|
148
|
+
codexDetailRevisions = new Map();
|
|
149
|
+
clearTimeout(codexDetailRefreshTimer);
|
|
150
|
+
codexDetailRefreshTimer = null;
|
|
151
|
+
codexDetailRefreshIds = new Set();
|
|
146
152
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
147
153
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
148
154
|
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
@@ -152,6 +158,7 @@
|
|
|
152
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 };
|
|
153
159
|
setClaudeModeEnabled(false);
|
|
154
160
|
applyCodexState(codexState);
|
|
161
|
+
installCodexLazyDetailHandler();
|
|
155
162
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
156
163
|
currentSocket = new WebSocket(protocol + '//' + window.location.host + '?sessionId=' + sessionId);
|
|
157
164
|
currentSocket.onmessage = (e) => {
|
|
@@ -161,6 +168,7 @@
|
|
|
161
168
|
codexPendingPermissions = msg.snapshot.pendingPermissions || [];
|
|
162
169
|
applyCodexState(msg.snapshot.state || {});
|
|
163
170
|
}
|
|
171
|
+
if (msg.type === 'codex-detail-response' && msg.detail) applyCodexDetailResponse(msg);
|
|
164
172
|
if (msg.type === 'codex-event') {
|
|
165
173
|
applyCodexEvent(msg.event);
|
|
166
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; }
|