glad-web 1.0.27 → 1.0.28
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 +52 -33
- package/lib/session/session-manager.js +8 -27
- package/lib/web/index.html +29 -18
- package/package.json +1 -1
|
@@ -43,6 +43,12 @@ function safeJson(value) {
|
|
|
43
43
|
try { return JSON.stringify(value || {}, null, 2); } catch (_) { return String(value || ''); }
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function toTimestampMs(value) {
|
|
47
|
+
const timestamp = Number(value || 0);
|
|
48
|
+
if (!timestamp) return null;
|
|
49
|
+
return timestamp < 100000000000 ? timestamp * 1000 : timestamp;
|
|
50
|
+
}
|
|
51
|
+
|
|
46
52
|
function appServerSpawnOptions({ cwd, env, platform = process.platform }) {
|
|
47
53
|
const options = { cwd, env, stdio: ['pipe', 'pipe', 'pipe'] };
|
|
48
54
|
|
|
@@ -247,7 +253,7 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
247
253
|
patch(id, patch) {
|
|
248
254
|
const item = this.messages.find(message => message.id === id);
|
|
249
255
|
if (!item) return null;
|
|
250
|
-
Object.assign(item, patch);
|
|
256
|
+
Object.assign(item, { updatedAt: Date.now() }, patch);
|
|
251
257
|
this.emitEvent({ type: 'message-updated', message: item });
|
|
252
258
|
return item;
|
|
253
259
|
}
|
|
@@ -514,8 +520,10 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
514
520
|
const trackedTurnId = threadId ? this.threadTurns.get(threadId)?.turnId : null;
|
|
515
521
|
const turnId = raw.turnId || context.turnId || trackedTurnId || this.currentTurnId;
|
|
516
522
|
const existingStartedAtMs = existing?.startedAtMs || existing?.createdAt || null;
|
|
517
|
-
const startedAtMs = context.startedAtMs || raw.startedAtMs ||
|
|
518
|
-
|
|
523
|
+
const startedAtMs = Number(context.startedAtMs || raw.startedAtMs || 0)
|
|
524
|
+
|| toTimestampMs(raw.startedAt || raw.createdAt) || existingStartedAtMs;
|
|
525
|
+
const completedAtMs = Number(context.completedAtMs || raw.completedAtMs || 0)
|
|
526
|
+
|| toTimestampMs(raw.completedAt || raw.updatedAt);
|
|
519
527
|
const durationMs = Number(raw.durationMs || 0)
|
|
520
528
|
|| (completedAtMs && startedAtMs && completedAtMs >= startedAtMs ? completedAtMs - startedAtMs : null)
|
|
521
529
|
|| Number(existing?.durationMs || 0) || null;
|
|
@@ -526,13 +534,13 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
526
534
|
};
|
|
527
535
|
const patch = kind === 'tool' ? { ...toolDetails(raw), threadId, turnId,
|
|
528
536
|
...timing, toolStatus: inferredToolStatus || raw.status || 'running' }
|
|
529
|
-
: { text, threadId, turnId, streaming: false };
|
|
537
|
+
: { text, threadId, turnId, streaming: false, ...(completedAtMs ? { completedAtMs } : {}) };
|
|
530
538
|
if (existing) {
|
|
531
539
|
this.patch(existing.id, patch);
|
|
532
540
|
} else if (kind === 'user') {
|
|
533
541
|
const local = [...this.messages].reverse().find(item => item.kind === 'user' && !item.providerId && item.text === text);
|
|
534
542
|
if (local) this.patch(local.id, { providerId, ...patch });
|
|
535
|
-
else this.append({ kind, providerId, ...patch });
|
|
543
|
+
else this.append({ kind, providerId, ...(startedAtMs ? { createdAt: startedAtMs } : {}), ...patch });
|
|
536
544
|
} else {
|
|
537
545
|
this.append({ kind, providerId, ...(startedAtMs ? { createdAt: startedAtMs } : {}), ...patch });
|
|
538
546
|
}
|
|
@@ -702,33 +710,41 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
702
710
|
text: prompt || '📷 Image attachment',
|
|
703
711
|
attachments: images.map(item => ({ id: item.id, name: item.name || 'image' }))
|
|
704
712
|
});
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
713
|
+
try {
|
|
714
|
+
await this.ensureProcess();
|
|
715
|
+
if (!this.threadId) {
|
|
716
|
+
const params = { cwd: this.workingDir };
|
|
717
|
+
if (this.hasModelOverride) params.model = this.model;
|
|
718
|
+
if (this.permissionMode) params.approvalPolicy = this.permissionMode;
|
|
719
|
+
if (this.sandboxMode) params.sandbox = this.sandboxMode;
|
|
720
|
+
const started = await this.request('thread/start', params);
|
|
721
|
+
this.threadId = started.thread?.id;
|
|
722
|
+
this.model = started.model || this.model;
|
|
723
|
+
this.effort = started.reasoningEffort || this.effort;
|
|
724
|
+
this.effectivePermissionMode = started.approvalPolicy || this.effectivePermissionMode;
|
|
725
|
+
this.effectiveSandboxMode = sandboxModeFromPolicy(started.sandbox) || this.effectiveSandboxMode;
|
|
726
|
+
this.emitEvent({ type: 'state', state: this.getControlState() });
|
|
727
|
+
}
|
|
728
|
+
this.setStatus('running');
|
|
729
|
+
const input = [];
|
|
730
|
+
if (prompt) input.push({ type: 'text', text: prompt });
|
|
731
|
+
for (const image of images) input.push({ type: 'localImage', path: image.path });
|
|
732
|
+
const params = { threadId: this.threadId, input, cwd: this.workingDir, summary: 'auto' };
|
|
708
733
|
if (this.hasModelOverride) params.model = this.model;
|
|
734
|
+
if (this.hasEffortOverride) params.effort = this.effort;
|
|
709
735
|
if (this.permissionMode) params.approvalPolicy = this.permissionMode;
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
this.
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
this.
|
|
717
|
-
this.
|
|
736
|
+
const sandboxPolicy = sandboxPolicyFor(this.sandboxMode, this.workingDir);
|
|
737
|
+
if (sandboxPolicy) params.sandboxPolicy = sandboxPolicy;
|
|
738
|
+
const started = await this.request('turn/start', params);
|
|
739
|
+
this.currentTurnId = started?.turn?.id || started?.turnId || this.currentTurnId;
|
|
740
|
+
return true;
|
|
741
|
+
} catch (error) {
|
|
742
|
+
this.currentTurnId = null;
|
|
743
|
+
this.currentTurnStartedAt = null;
|
|
744
|
+
this.setStatus('idle');
|
|
745
|
+
this.append({ kind: 'event', level: 'error', text: `Unable to send message: ${error.message}` });
|
|
746
|
+
throw error;
|
|
718
747
|
}
|
|
719
|
-
this.setStatus('running');
|
|
720
|
-
const input = [];
|
|
721
|
-
if (prompt) input.push({ type: 'text', text: prompt });
|
|
722
|
-
for (const image of images) input.push({ type: 'localImage', path: image.path });
|
|
723
|
-
const params = { threadId: this.threadId, input, cwd: this.workingDir, summary: 'auto' };
|
|
724
|
-
if (this.hasModelOverride) params.model = this.model;
|
|
725
|
-
if (this.hasEffortOverride) params.effort = this.effort;
|
|
726
|
-
if (this.permissionMode) params.approvalPolicy = this.permissionMode;
|
|
727
|
-
const sandboxPolicy = sandboxPolicyFor(this.sandboxMode, this.workingDir);
|
|
728
|
-
if (sandboxPolicy) params.sandboxPolicy = sandboxPolicy;
|
|
729
|
-
const started = await this.request('turn/start', params);
|
|
730
|
-
this.currentTurnId = started?.turn?.id || started?.turnId || this.currentTurnId;
|
|
731
|
-
return true;
|
|
732
748
|
}
|
|
733
749
|
|
|
734
750
|
write(data) {
|
|
@@ -736,9 +752,7 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
736
752
|
const text = String(data || '').replace(/\r/g, '\n');
|
|
737
753
|
const prompt = text.trim();
|
|
738
754
|
if (prompt) void this.sendUserMessage(prompt).catch(error => {
|
|
739
|
-
this.
|
|
740
|
-
this.setStatus('idle');
|
|
741
|
-
this.append({ kind: 'event', level: 'error', text: error.message });
|
|
755
|
+
this.logger.debugInfo?.(`[codex-app-server] send failed: ${error.message}`);
|
|
742
756
|
});
|
|
743
757
|
return true;
|
|
744
758
|
}
|
|
@@ -830,7 +844,12 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
830
844
|
const startedAtMs = toMilliseconds(startedAt);
|
|
831
845
|
const completedAtMs = toMilliseconds(completedAt);
|
|
832
846
|
this.append({ kind: 'turn-start', turnId: turn.id, ...(startedAtMs ? { createdAt: startedAtMs } : {}) });
|
|
833
|
-
for (const item of turn.items || [])
|
|
847
|
+
for (const item of turn.items || []) {
|
|
848
|
+
this.applyProviderItem({ ...item, turnId: turn.id }, status === 'failed' ? 'failed' : 'completed', {
|
|
849
|
+
startedAtMs,
|
|
850
|
+
completedAtMs
|
|
851
|
+
});
|
|
852
|
+
}
|
|
834
853
|
const durationMs = Number(turn.durationMs || turn.duration_ms || 0)
|
|
835
854
|
|| (startedAtMs && completedAtMs && completedAtMs >= startedAtMs ? completedAtMs - startedAtMs : null);
|
|
836
855
|
this.append({ kind: 'turn-end', turnId: turn.id, status, durationMs,
|
|
@@ -439,42 +439,23 @@ class SessionManager extends EventEmitter {
|
|
|
439
439
|
}
|
|
440
440
|
|
|
441
441
|
async forkCodex(id, threadId) {
|
|
442
|
-
const
|
|
443
|
-
if (!
|
|
444
|
-
if (
|
|
442
|
+
const session = this.get(id);
|
|
443
|
+
if (!session || session.kind !== 'codex-structured') return null;
|
|
444
|
+
if (session.presentation !== 'structured' || session.status !== 'idle') {
|
|
445
445
|
const error = new Error('Codex must be idle in chat mode before forking');
|
|
446
446
|
error.statusCode = 409;
|
|
447
447
|
throw error;
|
|
448
448
|
}
|
|
449
|
-
const sourceThreadId = String(threadId ||
|
|
449
|
+
const sourceThreadId = String(threadId || session.threadId || '').trim();
|
|
450
450
|
if (!sourceThreadId) {
|
|
451
451
|
const error = new Error('Choose a Codex thread to fork');
|
|
452
452
|
error.statusCode = 400;
|
|
453
453
|
throw error;
|
|
454
454
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
name: `${source.name} Fork`,
|
|
460
|
-
codexOptions: {
|
|
461
|
-
...(source.hasModelOverride && source.model ? { model: source.model } : {}),
|
|
462
|
-
...(source.hasEffortOverride && source.effort ? { effort: source.effort } : {}),
|
|
463
|
-
...(source.permissionMode ? { permissionMode: source.permissionMode } : {}),
|
|
464
|
-
...(source.sandboxMode ? { sandboxMode: source.sandboxMode } : {})
|
|
465
|
-
}
|
|
466
|
-
});
|
|
467
|
-
target.parentSessionId = source.id;
|
|
468
|
-
target.forkedFromThreadId = sourceThreadId;
|
|
469
|
-
try {
|
|
470
|
-
const result = await target.forkFrom(sourceThreadId);
|
|
471
|
-
if (!result) throw new Error('Unable to fork the selected Codex thread');
|
|
472
|
-
source.append({ kind: 'event', level: 'info', text: `Forked a new session: ${target.name}` });
|
|
473
|
-
return target;
|
|
474
|
-
} catch (error) {
|
|
475
|
-
this.kill(target.id);
|
|
476
|
-
throw error;
|
|
477
|
-
}
|
|
455
|
+
const result = await session.forkFrom(sourceThreadId);
|
|
456
|
+
if (!result) throw new Error('Unable to fork the selected Codex thread');
|
|
457
|
+
session.forkedFromThreadId = sourceThreadId;
|
|
458
|
+
return session;
|
|
478
459
|
}
|
|
479
460
|
|
|
480
461
|
listCodexResumeThreads(id) {
|
package/lib/web/index.html
CHANGED
|
@@ -99,9 +99,13 @@
|
|
|
99
99
|
.codex-conversation { width: min(100%, var(--chat-content-max)); margin: 0 auto; }
|
|
100
100
|
.codex-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; }
|
|
101
101
|
.codex-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; }
|
|
102
|
-
.codex-message { max-width: 100%; margin: 0 0 12px;
|
|
103
|
-
.codex-message.user { max-width: 92%; margin-left: auto;
|
|
102
|
+
.codex-message-block { max-width: 100%; margin: 0 0 12px; }
|
|
103
|
+
.codex-message-block.user { max-width: 92%; margin-left: auto; }
|
|
104
|
+
.codex-message { max-width: 100%; margin: 0; color: #f5f5f7; font-size: 14px; line-height: 1.45; overflow-wrap: anywhere; }
|
|
105
|
+
.codex-message.user { padding: 8px 12px; border: 1px solid rgba(0,122,255,.32); border-radius: 12px; background: rgba(0,122,255,.24); }
|
|
104
106
|
.codex-message.event { color: var(--text-dim); text-align: center; font-size: 12px; }
|
|
107
|
+
.codex-message-time { display: block; width: max-content; margin-top: 4px; padding: 0 2px; color: #8e8e93; font-size: 10px; font-weight: 500; line-height: 1; font-variant-numeric: tabular-nums; }
|
|
108
|
+
.codex-message-block.user .codex-message-time { margin-left: auto; }
|
|
105
109
|
.codex-status-card { margin: 10px 0 14px; border: 1px solid rgba(100,210,255,.2); border-radius: 10px; background: rgba(28,28,30,.72); padding: 11px; color: #f5f5f7; }
|
|
106
110
|
.codex-status-title { margin-bottom: 9px; color: #64d2ff; font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; }
|
|
107
111
|
.codex-status-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; }
|
|
@@ -327,7 +331,7 @@
|
|
|
327
331
|
.codex-control-page .claude-ctrl-btn { height: 36px; padding-left: 4px; padding-right: 4px; }
|
|
328
332
|
.codex-select-control { height: 36px; }
|
|
329
333
|
#codex-chat-container { padding: 10px 10px calc(22px + env(safe-area-inset-bottom)); }
|
|
330
|
-
.codex-message.user { max-width: 94%; }
|
|
334
|
+
.codex-message-block.user { max-width: 94%; }
|
|
331
335
|
.claude-permission-actions { justify-content: flex-start; }
|
|
332
336
|
}
|
|
333
337
|
@media (max-width: 430px) {
|
|
@@ -451,7 +455,7 @@
|
|
|
451
455
|
<button id="codex-status-btn" class="claude-ctrl-btn" onclick="requestCodexStatus()" title="Show Codex account, limits, and context status">Status</button>
|
|
452
456
|
<button id="codex-abort-btn" class="claude-ctrl-btn danger" onclick="abortCodexSession()" title="Abort current Codex turn">Abort</button>
|
|
453
457
|
<button id="codex-resume-btn" class="claude-ctrl-btn primary" onclick="toggleCodexResumePanel()" title="Resume a Codex thread">Resume</button>
|
|
454
|
-
<button id="codex-fork-btn" class="claude-ctrl-btn primary" onclick="toggleCodexForkPanel()" title="Fork a Codex thread
|
|
458
|
+
<button id="codex-fork-btn" class="claude-ctrl-btn primary" onclick="toggleCodexForkPanel()" title="Fork a Codex thread in this conversation">Fork</button>
|
|
455
459
|
</div>
|
|
456
460
|
<div class="codex-control-page">
|
|
457
461
|
<label class="codex-select-control" title="Sandbox mode">
|
|
@@ -2197,7 +2201,16 @@
|
|
|
2197
2201
|
}).join('');
|
|
2198
2202
|
const label = running ? 'Working' : failed ? 'Work finished with errors' : duration ? `Worked for ${duration}` : 'Worked';
|
|
2199
2203
|
const key = items.map(item => item.id || item.providerId || '').join('-');
|
|
2200
|
-
return `<details class="codex-work-group" data-codex-key="group-${escapeHtml(key)}"
|
|
2204
|
+
return `<details class="codex-work-group" data-codex-key="group-${escapeHtml(key)}"><summary>${label} · ${items.length} ${items.length === 1 ? 'tool' : 'tools'}</summary><div class="codex-work-group-body">${tools}</div></details>`;
|
|
2205
|
+
}
|
|
2206
|
+
function renderCodexMessageTime(item, finalOnly = false) {
|
|
2207
|
+
if (!item || (finalOnly && item.streaming)) return '';
|
|
2208
|
+
const timestamp = Number(finalOnly ? (item.completedAtMs || item.updatedAt || item.createdAt) : item.createdAt);
|
|
2209
|
+
if (!Number.isFinite(timestamp) || timestamp <= 0) return '';
|
|
2210
|
+
const date = new Date(timestamp);
|
|
2211
|
+
if (Number.isNaN(date.getTime())) return '';
|
|
2212
|
+
const label = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
2213
|
+
return `<time class="codex-message-time" datetime="${escapeHtml(date.toISOString())}" title="${escapeHtml(date.toLocaleString())}">${escapeHtml(label)}</time>`;
|
|
2201
2214
|
}
|
|
2202
2215
|
function syncCodexDom(current, next) {
|
|
2203
2216
|
if (!current || !next) return;
|
|
@@ -2243,6 +2256,10 @@
|
|
|
2243
2256
|
function commitCodexChatRender() {
|
|
2244
2257
|
const container = document.getElementById('codex-chat-container');
|
|
2245
2258
|
if (!container) return;
|
|
2259
|
+
const wasEmpty = !container.firstElementChild;
|
|
2260
|
+
const previousScrollTop = container.scrollTop;
|
|
2261
|
+
const distanceFromBottom = container.scrollHeight - container.clientHeight - previousScrollTop;
|
|
2262
|
+
const shouldStickToBottom = wasEmpty || distanceFromBottom <= 64;
|
|
2246
2263
|
const parts = [];
|
|
2247
2264
|
const permissionById = new Map(codexPendingPermissions.map(item => [String(item.id), item]));
|
|
2248
2265
|
const usedPermissions = new Set();
|
|
@@ -2255,17 +2272,12 @@
|
|
|
2255
2272
|
const tools = [];
|
|
2256
2273
|
const turnId = item.turnId;
|
|
2257
2274
|
while (i < visible.length && visible[i].kind === 'tool' && visible[i].turnId === turnId) tools.push(visible[i++]);
|
|
2258
|
-
|
|
2275
|
+
parts.push(renderCodexToolGroup(tools, permissionById, usedPermissions,
|
|
2259
2276
|
turnEndById.get(String(turnId || ''))));
|
|
2260
|
-
else {
|
|
2261
|
-
const permission = permissionById.get(String(tools[0].providerId || ''));
|
|
2262
|
-
if (permission) usedPermissions.add(permission.id);
|
|
2263
|
-
parts.push(renderCodexTool(tools[0], permission));
|
|
2264
|
-
}
|
|
2265
2277
|
continue;
|
|
2266
2278
|
}
|
|
2267
|
-
if (item.kind === 'assistant') parts.push(`<div class="codex-message assistant
|
|
2268
|
-
else if (item.kind === 'user') parts.push(`<div class="codex-message user
|
|
2279
|
+
if (item.kind === 'assistant') parts.push(`<div class="codex-message-block assistant" data-codex-key="message-${escapeHtml(item.id || '')}"><div class="codex-message assistant claude-md">${renderMarkdown(item.text || '')}</div>${renderCodexMessageTime(item, true)}</div>`);
|
|
2280
|
+
else if (item.kind === 'user') parts.push(`<div class="codex-message-block user" data-codex-key="message-${escapeHtml(item.id || '')}"><div class="codex-message user claude-md">${renderMarkdown(item.text || '')}</div>${renderCodexMessageTime(item)}</div>`);
|
|
2269
2281
|
else if (item.kind === 'status') parts.push(renderCodexStatus(item));
|
|
2270
2282
|
else parts.push(`<div class="codex-message ${escapeHtml(item.level === 'error' ? 'event error' : item.kind || 'event')}">${codexText(item.text)}</div>`);
|
|
2271
2283
|
i += 1;
|
|
@@ -2278,7 +2290,7 @@
|
|
|
2278
2290
|
const current = container.firstElementChild;
|
|
2279
2291
|
if (!current) container.appendChild(next);
|
|
2280
2292
|
else syncCodexDom(current, next);
|
|
2281
|
-
|
|
2293
|
+
container.scrollTop = shouldStickToBottom ? container.scrollHeight : previousScrollTop;
|
|
2282
2294
|
}
|
|
2283
2295
|
|
|
2284
2296
|
function applyCodexState(state = {}) {
|
|
@@ -2307,7 +2319,7 @@
|
|
|
2307
2319
|
const abort = document.getElementById('codex-abort-btn');
|
|
2308
2320
|
if (abort) abort.disabled = !codexState.canAbort;
|
|
2309
2321
|
const fork = document.getElementById('codex-fork-btn');
|
|
2310
|
-
if (fork) fork.disabled = !(codexState.presentation === 'structured' && codexState.status === 'idle'
|
|
2322
|
+
if (fork) fork.disabled = !(codexState.presentation === 'structured' && codexState.status === 'idle');
|
|
2311
2323
|
const terminal = document.getElementById('codex-terminal-switch');
|
|
2312
2324
|
if (terminal) { terminal.textContent = codexState.presentation === 'terminal' ? 'CHAT' : 'TERM'; terminal.disabled = codexState.presentation === 'structured' && !codexState.canSwitchToTerminal; terminal.title = codexState.presentation === 'terminal' ? 'Return to Codex chat' : 'Switch to Codex terminal'; }
|
|
2313
2325
|
renderCodexStateBar();
|
|
@@ -2393,7 +2405,7 @@
|
|
|
2393
2405
|
await loadCodexThreadPanel(panel, 'resume');
|
|
2394
2406
|
}
|
|
2395
2407
|
async function toggleCodexForkPanel() {
|
|
2396
|
-
if (!(codexState.presentation === 'structured' && codexState.status === 'idle'
|
|
2408
|
+
if (!(codexState.presentation === 'structured' && codexState.status === 'idle')) return;
|
|
2397
2409
|
codexForkPanelOpen = !codexForkPanelOpen;
|
|
2398
2410
|
codexModelPanelOpen = false;
|
|
2399
2411
|
codexResumePanelOpen = false;
|
|
@@ -2429,7 +2441,7 @@
|
|
|
2429
2441
|
}
|
|
2430
2442
|
async function selectCodexForkThread(threadId) {
|
|
2431
2443
|
const panel = document.getElementById('codex-fork-panel');
|
|
2432
|
-
panel.innerHTML = '<div class="claude-resume-meta" style="padding:12px;">Forking
|
|
2444
|
+
panel.innerHTML = '<div class="claude-resume-meta" style="padding:12px;">Forking and switching this conversation...</div>';
|
|
2433
2445
|
updateTerminalControlsHeight();
|
|
2434
2446
|
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-fork`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ threadId }) }, 60000);
|
|
2435
2447
|
const data = await res.json();
|
|
@@ -2442,7 +2454,6 @@
|
|
|
2442
2454
|
panel.classList.remove('active');
|
|
2443
2455
|
panel.innerHTML = '';
|
|
2444
2456
|
updateTerminalControlsHeight();
|
|
2445
|
-
refreshSessionsNow();
|
|
2446
2457
|
}
|
|
2447
2458
|
async function toggleCodexPresentation() {
|
|
2448
2459
|
const presentation = codexState.presentation === 'terminal' ? 'structured' : 'terminal';
|