glad-web 1.0.26 → 1.0.27
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 +30 -6
- package/lib/commands/web.js +8 -0
- package/lib/session/session-manager.js +39 -0
- package/lib/web/index.html +79 -26
- package/package.json +1 -1
|
@@ -812,12 +812,17 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
812
812
|
this.effectivePermissionMode = result.approvalPolicy || this.effectivePermissionMode;
|
|
813
813
|
this.effectiveSandboxMode = sandboxModeFromPolicy(result.sandbox) || this.effectiveSandboxMode;
|
|
814
814
|
const history = await this.request('thread/read', { threadId: this.threadId, includeTurns: true });
|
|
815
|
-
this.
|
|
816
|
-
|
|
817
|
-
|
|
815
|
+
this.restoreThreadHistory(history?.thread, `Resumed Codex thread ${this.threadId}`);
|
|
816
|
+
return true;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
restoreThreadHistory(thread, eventText = '') {
|
|
820
|
+
this.model = thread?.model || this.model;
|
|
821
|
+
this.effort = thread?.reasoningEffort || thread?.reasoning_effort || this.effort;
|
|
822
|
+
this.tokenUsage = thread?.tokenUsage || thread?.token_usage || this.tokenUsage;
|
|
818
823
|
this.messages = [];
|
|
819
824
|
this.completedPermissions = [];
|
|
820
|
-
for (const turn of
|
|
825
|
+
for (const turn of thread?.turns || []) {
|
|
821
826
|
const status = turn.status === 'failed' ? 'failed' : turn.status === 'interrupted' ? 'cancelled' : 'completed';
|
|
822
827
|
const startedAt = Number(turn.startedAt || turn.createdAt || 0);
|
|
823
828
|
const completedAt = Number(turn.completedAt || turn.updatedAt || 0);
|
|
@@ -831,10 +836,29 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
831
836
|
this.append({ kind: 'turn-end', turnId: turn.id, status, durationMs,
|
|
832
837
|
...(completedAtMs ? { createdAt: completedAtMs } : {}) });
|
|
833
838
|
}
|
|
834
|
-
this.append({ kind: 'event', level: 'info', text:
|
|
839
|
+
if (eventText) this.append({ kind: 'event', level: 'info', text: eventText });
|
|
835
840
|
this.emitEvent({ type: 'history-reset', messages: this.messages });
|
|
836
841
|
this.emitEvent({ type: 'state', state: this.getControlState() });
|
|
837
|
-
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
async forkFrom(threadId) {
|
|
845
|
+
const sourceThreadId = String(threadId || '').trim();
|
|
846
|
+
if (!sourceThreadId || this.presentation !== 'structured' || this.status !== 'idle') return false;
|
|
847
|
+
await this.ensureProcess();
|
|
848
|
+
const params = { threadId: sourceThreadId, cwd: this.workingDir, ephemeral: false, threadSource: null };
|
|
849
|
+
if (this.hasModelOverride) params.model = this.model;
|
|
850
|
+
if (this.permissionMode) params.approvalPolicy = this.permissionMode;
|
|
851
|
+
if (this.sandboxMode) params.sandbox = this.sandboxMode;
|
|
852
|
+
const result = await this.request('thread/fork', params);
|
|
853
|
+
const forkedThread = result?.thread;
|
|
854
|
+
if (!forkedThread?.id) throw new Error('Codex did not return a forked thread');
|
|
855
|
+
this.threadId = forkedThread.id;
|
|
856
|
+
this.model = result.model || forkedThread.model || this.model;
|
|
857
|
+
this.effort = result.reasoningEffort || forkedThread.reasoningEffort || forkedThread.reasoning_effort || this.effort;
|
|
858
|
+
this.effectivePermissionMode = result.approvalPolicy || this.effectivePermissionMode;
|
|
859
|
+
this.effectiveSandboxMode = sandboxModeFromPolicy(result.sandbox) || this.effectiveSandboxMode;
|
|
860
|
+
this.restoreThreadHistory(forkedThread, `Forked from Codex thread ${sourceThreadId}`);
|
|
861
|
+
return { threadId: this.threadId };
|
|
838
862
|
}
|
|
839
863
|
|
|
840
864
|
async switchToTerminal() {
|
package/lib/commands/web.js
CHANGED
|
@@ -379,6 +379,14 @@ async function webCommand(options) {
|
|
|
379
379
|
} catch (e) { res.status(400).json({ error: e.message }); }
|
|
380
380
|
});
|
|
381
381
|
|
|
382
|
+
app.post('/api/sessions/:id/codex-fork', async (req, res) => {
|
|
383
|
+
try {
|
|
384
|
+
const session = await sessionManager.forkCodex(req.params.id, req.body && req.body.threadId);
|
|
385
|
+
if (!session) return res.status(404).json({ error: 'Codex session not found' });
|
|
386
|
+
res.json({ success: true, id: session.id, name: session.name, threadId: session.threadId });
|
|
387
|
+
} catch (e) { res.status(e.statusCode || 400).json({ error: e.message }); }
|
|
388
|
+
});
|
|
389
|
+
|
|
382
390
|
app.post('/api/sessions/:id/codex-presentation', async (req, res) => {
|
|
383
391
|
const presentation = req.body && req.body.presentation;
|
|
384
392
|
if (!['terminal', 'structured'].includes(presentation)) return res.status(400).json({ error: 'Invalid presentation' });
|
|
@@ -438,6 +438,45 @@ class SessionManager extends EventEmitter {
|
|
|
438
438
|
return session && session.kind === 'codex-structured' ? session.resume(threadId) : false;
|
|
439
439
|
}
|
|
440
440
|
|
|
441
|
+
async forkCodex(id, threadId) {
|
|
442
|
+
const source = this.get(id);
|
|
443
|
+
if (!source || source.kind !== 'codex-structured') return null;
|
|
444
|
+
if (source.presentation !== 'structured' || source.status !== 'idle') {
|
|
445
|
+
const error = new Error('Codex must be idle in chat mode before forking');
|
|
446
|
+
error.statusCode = 409;
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
const sourceThreadId = String(threadId || source.threadId || '').trim();
|
|
450
|
+
if (!sourceThreadId) {
|
|
451
|
+
const error = new Error('Choose a Codex thread to fork');
|
|
452
|
+
error.statusCode = 400;
|
|
453
|
+
throw error;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const target = this.createCodexStructuredSession({
|
|
457
|
+
tool: source.tool,
|
|
458
|
+
workingDirectory: this.getSessionWorkingDirectory(source),
|
|
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
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
441
480
|
listCodexResumeThreads(id) {
|
|
442
481
|
const session = this.get(id);
|
|
443
482
|
if (!session || session.kind !== 'codex-structured') return null;
|
package/lib/web/index.html
CHANGED
|
@@ -143,16 +143,18 @@
|
|
|
143
143
|
.codex-inline-permission .claude-permission-actions { margin-top: 8px; }
|
|
144
144
|
@keyframes codex-spin { to { transform: rotate(360deg); } }
|
|
145
145
|
#codex-control-panel { display: none; width: min(100%, var(--control-content-max)); margin: 0 auto; padding: 8px 14px 10px; border-bottom: 1px solid #222; background: #121212; box-sizing: border-box; }
|
|
146
|
-
.codex-control-
|
|
147
|
-
.codex-control-
|
|
148
|
-
.codex-control-
|
|
146
|
+
.codex-control-rail { display: flex; gap: 14px; overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
|
|
147
|
+
.codex-control-rail::-webkit-scrollbar { display: none; }
|
|
148
|
+
.codex-control-page { flex: 0 0 100%; display: grid; grid-template-columns: repeat(5, minmax(0, 116px)); grid-template-rows: auto; justify-content: center; gap: clamp(2px, .8vw, 7px); align-items: center; white-space: nowrap; scroll-snap-align: start; box-sizing: border-box; }
|
|
149
|
+
.codex-control-page > * { width: 100%; min-width: 0; overflow: hidden; }
|
|
150
|
+
.codex-control-page .claude-ctrl-btn, .codex-control-page .codex-select-label { padding-left: clamp(2px, 1vw, 10px); padding-right: clamp(2px, 1vw, 10px); font-size: clamp(8px, 2.2vw, 11px); text-overflow: ellipsis; overflow: hidden; }
|
|
149
151
|
.codex-select-control { position: relative; display: block; min-width: 0; height: 32px; }
|
|
150
152
|
.codex-select-label { display: flex; width: 100%; height: 100%; align-items: center; justify-content: center; box-sizing: border-box; border: 1px solid rgba(255,255,255,.1); border-radius: 16px; background: rgba(255,255,255,.08); color: #f5f5f7; font-size: 11px; font-weight: 800; white-space: nowrap; }
|
|
151
153
|
.codex-select-control:focus-within .codex-select-label { background: rgba(255,255,255,.14); border-color: rgba(0,122,255,.45); }
|
|
152
154
|
.codex-select { appearance: none; position: absolute; inset: 0; width: 100%; min-width: 0; height: 100%; opacity: 0; cursor: pointer; }
|
|
153
155
|
.codex-select option { background: #1c1c1e; color: #fff; }
|
|
154
|
-
#codex-model-panel, #codex-resume-panel { display: none; margin-top: 8px; border: 1px solid rgba(255,255,255,.1); background: rgba(28,28,30,.99); border-radius: 8px; box-shadow: 0 16px 36px rgba(0,0,0,.34); overflow: hidden; }
|
|
155
|
-
#codex-model-panel.active, #codex-resume-panel.active { display: block; }
|
|
156
|
+
#codex-model-panel, #codex-resume-panel, #codex-fork-panel { display: none; margin-top: 8px; border: 1px solid rgba(255,255,255,.1); background: rgba(28,28,30,.99); border-radius: 8px; box-shadow: 0 16px 36px rgba(0,0,0,.34); overflow: hidden; }
|
|
157
|
+
#codex-model-panel.active, #codex-resume-panel.active, #codex-fork-panel.active { display: block; }
|
|
156
158
|
#codex-model-panel { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); max-height: min(300px, 42dvh); }
|
|
157
159
|
#codex-model-panel.active { display: grid; }
|
|
158
160
|
.codex-picker-column { min-width: 0; overflow-y: auto; }
|
|
@@ -161,7 +163,7 @@
|
|
|
161
163
|
.codex-picker-option.selected { background: rgba(0,122,255,.18); color: #fff; }
|
|
162
164
|
#codex-state-bar { display: flex; align-items: center; gap: 7px; min-height: 24px; margin-top: 8px; color: var(--text-dim); font-size: 11px; overflow-x: auto; scrollbar-width: none; white-space: nowrap; }
|
|
163
165
|
#codex-state-bar::-webkit-scrollbar { display: none; }
|
|
164
|
-
#codex-resume-panel { max-height: min(280px, 38dvh); overflow-y: auto; }
|
|
166
|
+
#codex-resume-panel, #codex-fork-panel { max-height: min(280px, 38dvh); overflow-y: auto; }
|
|
165
167
|
.claude-context-size-badge { position: sticky; top: 0; z-index: 2; width: max-content; max-width: 100%; margin: 0 0 8px auto; border: 1px solid rgba(255,255,255,0.1); background: rgba(28,28,30,0.94); border-radius: 999px; padding: 4px 9px; color: #d1d5db; font-size: 11px; font-weight: 800; box-shadow: 0 8px 18px rgba(0,0,0,0.24); }
|
|
166
168
|
.claude-message { max-width: 92%; margin: 0 0 10px 0; padding: 10px 12px; border-radius: 8px; overflow-wrap: anywhere; line-height: 1.45; font-size: 14px; }
|
|
167
169
|
.claude-message.user { margin-left: auto; background: rgba(0,122,255,0.24); border: 1px solid rgba(0,122,255,0.32); color: #fff; border-radius: 12px; padding-top: 7px; padding-bottom: 7px; }
|
|
@@ -321,8 +323,8 @@
|
|
|
321
323
|
#nav-bar > div:last-child .icon-btn { min-width: 34px; min-height: 30px; padding: 5px 7px !important; }
|
|
322
324
|
#input-row { padding-left: 10px; padding-right: 10px; }
|
|
323
325
|
#codex-control-panel { padding-left: 8px; padding-right: 8px; }
|
|
324
|
-
.codex-control-
|
|
325
|
-
.codex-control-
|
|
326
|
+
.codex-control-page { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
|
327
|
+
.codex-control-page .claude-ctrl-btn { height: 36px; padding-left: 4px; padding-right: 4px; }
|
|
326
328
|
.codex-select-control { height: 36px; }
|
|
327
329
|
#codex-chat-container { padding: 10px 10px calc(22px + env(safe-area-inset-bottom)); }
|
|
328
330
|
.codex-message.user { max-width: 94%; }
|
|
@@ -443,27 +445,33 @@
|
|
|
443
445
|
<div id="claude-resume-panel"></div>
|
|
444
446
|
</div>
|
|
445
447
|
<div id="codex-control-panel">
|
|
446
|
-
<div class="codex-control-
|
|
447
|
-
<
|
|
448
|
-
<
|
|
449
|
-
<
|
|
450
|
-
|
|
451
|
-
</
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
<
|
|
456
|
-
<
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
448
|
+
<div id="codex-control-rail" class="codex-control-rail">
|
|
449
|
+
<div class="codex-control-page">
|
|
450
|
+
<button id="codex-model-btn" class="claude-ctrl-btn" onclick="toggleCodexModelPanel()" title="Model and effort">Model</button>
|
|
451
|
+
<button id="codex-status-btn" class="claude-ctrl-btn" onclick="requestCodexStatus()" title="Show Codex account, limits, and context status">Status</button>
|
|
452
|
+
<button id="codex-abort-btn" class="claude-ctrl-btn danger" onclick="abortCodexSession()" title="Abort current Codex turn">Abort</button>
|
|
453
|
+
<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 into a new Glad session">Fork</button>
|
|
455
|
+
</div>
|
|
456
|
+
<div class="codex-control-page">
|
|
457
|
+
<label class="codex-select-control" title="Sandbox mode">
|
|
458
|
+
<span class="codex-select-label" aria-hidden="true">Sandbox</span>
|
|
459
|
+
<select id="codex-sandbox-select" class="codex-select" aria-label="Sandbox mode" onchange="updateCodexSettingsFromControls()">
|
|
460
|
+
<option value="default">Default</option><option value="read-only">Read only</option><option value="workspace-write">Workspace write</option><option value="danger-full-access">Full access</option>
|
|
461
|
+
</select>
|
|
462
|
+
</label>
|
|
463
|
+
<label class="codex-select-control" title="Approval policy">
|
|
464
|
+
<span class="codex-select-label" aria-hidden="true">Ask</span>
|
|
465
|
+
<select id="codex-permission-select" class="codex-select" aria-label="Approval policy" onchange="updateCodexSettingsFromControls()">
|
|
466
|
+
<option value="default">Default</option><option value="untrusted">Untrusted</option><option value="on-request">On request</option><option value="never">Never ask</option>
|
|
467
|
+
</select>
|
|
468
|
+
</label>
|
|
469
|
+
</div>
|
|
463
470
|
</div>
|
|
464
471
|
<div id="codex-state-bar"></div>
|
|
465
472
|
<div id="codex-model-panel"></div>
|
|
466
473
|
<div id="codex-resume-panel"></div>
|
|
474
|
+
<div id="codex-fork-panel"></div>
|
|
467
475
|
</div>
|
|
468
476
|
<div id="timed-send-panel">
|
|
469
477
|
<div class="timed-row">
|
|
@@ -660,6 +668,7 @@
|
|
|
660
668
|
let codexModelPanelOpen = false;
|
|
661
669
|
let codexModelCandidate = null;
|
|
662
670
|
let codexResumePanelOpen = false;
|
|
671
|
+
let codexForkPanelOpen = false;
|
|
663
672
|
let codexRenderFrame = null;
|
|
664
673
|
const modifiers = { ctrl: false };
|
|
665
674
|
|
|
@@ -2297,6 +2306,8 @@
|
|
|
2297
2306
|
if (modelButton) modelButton.textContent = 'Model';
|
|
2298
2307
|
const abort = document.getElementById('codex-abort-btn');
|
|
2299
2308
|
if (abort) abort.disabled = !codexState.canAbort;
|
|
2309
|
+
const fork = document.getElementById('codex-fork-btn');
|
|
2310
|
+
if (fork) fork.disabled = !(codexState.presentation === 'structured' && codexState.status === 'idle' && codexState.threadId);
|
|
2300
2311
|
const terminal = document.getElementById('codex-terminal-switch');
|
|
2301
2312
|
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'; }
|
|
2302
2313
|
renderCodexStateBar();
|
|
@@ -2320,8 +2331,10 @@
|
|
|
2320
2331
|
function toggleCodexModelPanel() {
|
|
2321
2332
|
codexModelPanelOpen = !codexModelPanelOpen;
|
|
2322
2333
|
codexResumePanelOpen = false;
|
|
2334
|
+
codexForkPanelOpen = false;
|
|
2323
2335
|
codexModelCandidate = codexState.model || codexState.models?.[0]?.id || null;
|
|
2324
2336
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
2337
|
+
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
2325
2338
|
renderCodexModelPanel();
|
|
2326
2339
|
updateTerminalControlsHeight();
|
|
2327
2340
|
}
|
|
@@ -2370,11 +2383,29 @@
|
|
|
2370
2383
|
async function toggleCodexResumePanel() {
|
|
2371
2384
|
codexResumePanelOpen = !codexResumePanelOpen;
|
|
2372
2385
|
codexModelPanelOpen = false;
|
|
2386
|
+
codexForkPanelOpen = false;
|
|
2373
2387
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
2388
|
+
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
2374
2389
|
const panel = document.getElementById('codex-resume-panel');
|
|
2375
2390
|
panel.classList.toggle('active', codexResumePanelOpen);
|
|
2376
2391
|
updateTerminalControlsHeight();
|
|
2377
2392
|
if (!codexResumePanelOpen) return;
|
|
2393
|
+
await loadCodexThreadPanel(panel, 'resume');
|
|
2394
|
+
}
|
|
2395
|
+
async function toggleCodexForkPanel() {
|
|
2396
|
+
if (!(codexState.presentation === 'structured' && codexState.status === 'idle' && codexState.threadId)) return;
|
|
2397
|
+
codexForkPanelOpen = !codexForkPanelOpen;
|
|
2398
|
+
codexModelPanelOpen = false;
|
|
2399
|
+
codexResumePanelOpen = false;
|
|
2400
|
+
document.getElementById('codex-model-panel').classList.remove('active');
|
|
2401
|
+
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
2402
|
+
const panel = document.getElementById('codex-fork-panel');
|
|
2403
|
+
panel.classList.toggle('active', codexForkPanelOpen);
|
|
2404
|
+
updateTerminalControlsHeight();
|
|
2405
|
+
if (!codexForkPanelOpen) return;
|
|
2406
|
+
await loadCodexThreadPanel(panel, 'fork');
|
|
2407
|
+
}
|
|
2408
|
+
async function loadCodexThreadPanel(panel, action) {
|
|
2378
2409
|
panel.innerHTML = '<div class="claude-resume-meta" style="padding:12px;">Loading sessions...</div>';
|
|
2379
2410
|
try {
|
|
2380
2411
|
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-resume-threads`, {}, 30000);
|
|
@@ -2383,9 +2414,11 @@
|
|
|
2383
2414
|
const items = data.items || [];
|
|
2384
2415
|
panel.innerHTML = items.length ? items.map(item => {
|
|
2385
2416
|
const questions = Array.isArray(item.questions) ? item.questions : [];
|
|
2386
|
-
|
|
2417
|
+
const handler = action === 'fork' ? 'selectCodexForkThread' : 'selectCodexResumeThread';
|
|
2418
|
+
return `<button class="claude-resume-item" onclick="${handler}(decodePathValue('${encodePathValue(item.id)}'))"><div class="claude-resume-title"><span>${escapeHtml((questions[0] || 'Codex session').slice(0, 120))}${item.current ? ' · current' : ''}</span><span>${escapeHtml(item.updatedAt ? new Date(item.updatedAt).toLocaleString() : '')}</span></div><div class="codex-resume-question-secondary">${escapeHtml((questions[1] || '').slice(0, 120))}</div></button>`;
|
|
2387
2419
|
}).join('') : '<div class="claude-resume-meta" style="padding:12px;">No Codex sessions found for this folder.</div>';
|
|
2388
2420
|
} catch (e) { panel.innerHTML = `<div class="claude-resume-meta" style="padding:12px;color:#ff6b61;">${escapeHtml(e.message)}</div>`; }
|
|
2421
|
+
updateTerminalControlsHeight();
|
|
2389
2422
|
}
|
|
2390
2423
|
async function selectCodexResumeThread(threadId) {
|
|
2391
2424
|
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-resume`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ threadId }) }, 30000);
|
|
@@ -2394,6 +2427,23 @@
|
|
|
2394
2427
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
2395
2428
|
updateTerminalControlsHeight();
|
|
2396
2429
|
}
|
|
2430
|
+
async function selectCodexForkThread(threadId) {
|
|
2431
|
+
const panel = document.getElementById('codex-fork-panel');
|
|
2432
|
+
panel.innerHTML = '<div class="claude-resume-meta" style="padding:12px;">Forking into a new session...</div>';
|
|
2433
|
+
updateTerminalControlsHeight();
|
|
2434
|
+
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-fork`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ threadId }) }, 60000);
|
|
2435
|
+
const data = await res.json();
|
|
2436
|
+
if (!res.ok || !data.success) {
|
|
2437
|
+
panel.innerHTML = `<div class="claude-resume-meta" style="padding:12px;color:#ff6b61;">${escapeHtml(data.error || 'Unable to fork Codex thread')}</div>`;
|
|
2438
|
+
updateTerminalControlsHeight();
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2441
|
+
codexForkPanelOpen = false;
|
|
2442
|
+
panel.classList.remove('active');
|
|
2443
|
+
panel.innerHTML = '';
|
|
2444
|
+
updateTerminalControlsHeight();
|
|
2445
|
+
refreshSessionsNow();
|
|
2446
|
+
}
|
|
2397
2447
|
async function toggleCodexPresentation() {
|
|
2398
2448
|
const presentation = codexState.presentation === 'terminal' ? 'structured' : 'terminal';
|
|
2399
2449
|
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-presentation`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ presentation }) }, 30000);
|
|
@@ -2528,8 +2578,11 @@
|
|
|
2528
2578
|
codexModelPanelOpen = false;
|
|
2529
2579
|
codexModelCandidate = null;
|
|
2530
2580
|
codexResumePanelOpen = false;
|
|
2581
|
+
codexForkPanelOpen = false;
|
|
2531
2582
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
2532
2583
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
2584
|
+
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
2585
|
+
document.getElementById('codex-control-rail').scrollLeft = 0;
|
|
2533
2586
|
codexState = { permissionMode: 'default', sandboxMode: 'default', effectivePermissionMode: null, effectiveSandboxMode: null, model: null, effort: null, status: 'idle', threadId: null, presentation: 'structured', models: [], canAbort: false, canSwitchToTerminal: false, canSwitchToStructured: false };
|
|
2534
2587
|
setClaudeModeEnabled(false);
|
|
2535
2588
|
applyCodexState(codexState);
|