glad-web 1.0.31 → 1.0.32
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 +125 -15
- package/lib/server/routes/providers.js +13 -0
- package/lib/session/session-manager.js +6 -0
- package/lib/web/codex.js +95 -0
- package/lib/web/core.js +7 -0
- package/lib/web/index.html +2 -0
- package/lib/web/session.js +8 -0
- package/lib/web/styles.css +17 -2
- package/package.json +1 -1
|
@@ -85,6 +85,27 @@ function recentUserQuestions(thread, limit = 2) {
|
|
|
85
85
|
return questions;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
function userPromptsFromThread(thread, fallbackTimestamp = null) {
|
|
89
|
+
const prompts = [];
|
|
90
|
+
const threadId = String(thread?.id || '');
|
|
91
|
+
for (const turn of Array.isArray(thread?.turns) ? thread.turns : []) {
|
|
92
|
+
const turnTimestamp = toTimestampMs(turn.startedAt || turn.createdAt || turn.completedAt || turn.updatedAt)
|
|
93
|
+
|| fallbackTimestamp;
|
|
94
|
+
for (const item of Array.isArray(turn?.items) ? turn.items : []) {
|
|
95
|
+
if (item?.type !== 'userMessage') continue;
|
|
96
|
+
const prompt = (textFromInputItems(item.content) || item.text || '').trim();
|
|
97
|
+
if (!prompt) continue;
|
|
98
|
+
prompts.push({
|
|
99
|
+
id: String(item.id || `${threadId}:${turn.id || 'turn'}:${prompts.length}`),
|
|
100
|
+
threadId,
|
|
101
|
+
text: prompt,
|
|
102
|
+
createdAt: toTimestampMs(item.createdAt || item.updatedAt) || turnTimestamp || null
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return prompts;
|
|
107
|
+
}
|
|
108
|
+
|
|
88
109
|
function toolDetails(raw) {
|
|
89
110
|
if (raw.type === 'commandExecution') {
|
|
90
111
|
return {
|
|
@@ -195,6 +216,8 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
195
216
|
this.inputSeq = 0;
|
|
196
217
|
this.completionReadInputSeq = 0;
|
|
197
218
|
this.timedInputs = new Map();
|
|
219
|
+
this.promptHistoryCache = null;
|
|
220
|
+
this.deferredWarnings = null;
|
|
198
221
|
this.ptyManager = {
|
|
199
222
|
workingDir,
|
|
200
223
|
isRunning: () => this.isRunning(),
|
|
@@ -491,7 +514,9 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
491
514
|
return;
|
|
492
515
|
}
|
|
493
516
|
if (method === 'warning' || method === 'guardianWarning') {
|
|
494
|
-
|
|
517
|
+
const warning = { kind: 'event', level: 'warning', text: params.message || params.warning || 'Codex warning.' };
|
|
518
|
+
if (this.deferredWarnings) this.deferredWarnings.push(warning);
|
|
519
|
+
else this.append(warning);
|
|
495
520
|
return;
|
|
496
521
|
}
|
|
497
522
|
if (method === 'item/commandExecution/outputDelta' || method === 'item/fileChange/outputDelta') {
|
|
@@ -655,6 +680,68 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
655
680
|
return items;
|
|
656
681
|
}
|
|
657
682
|
|
|
683
|
+
async listPromptHistory({ offset = 0, limit = 30 } = {}) {
|
|
684
|
+
await this.ensureProcess();
|
|
685
|
+
const safeOffset = Math.max(0, Math.min(199, Number(offset) || 0));
|
|
686
|
+
const safeLimit = Math.max(1, Math.min(30, Number(limit) || 30));
|
|
687
|
+
const cacheFresh = this.promptHistoryCache
|
|
688
|
+
&& Date.now() - this.promptHistoryCache.loadedAt < 15000;
|
|
689
|
+
|
|
690
|
+
if (!cacheFresh) {
|
|
691
|
+
const prompts = [];
|
|
692
|
+
let cursor = null;
|
|
693
|
+
let pageCount = 0;
|
|
694
|
+
let capped = false;
|
|
695
|
+
do {
|
|
696
|
+
const result = await this.request('thread/list', {
|
|
697
|
+
cursor,
|
|
698
|
+
limit: 20,
|
|
699
|
+
sortKey: 'updated_at',
|
|
700
|
+
sortDirection: 'desc',
|
|
701
|
+
archived: false,
|
|
702
|
+
cwd: this.workingDir
|
|
703
|
+
});
|
|
704
|
+
const threads = (result?.data || []).filter(item => !item.parentThreadId);
|
|
705
|
+
const histories = await Promise.all(threads.map(async item => {
|
|
706
|
+
try {
|
|
707
|
+
const history = await this.request('thread/read', { threadId: item.id, includeTurns: true });
|
|
708
|
+
const fallbackTimestamp = toTimestampMs(item.updatedAt || item.createdAt);
|
|
709
|
+
return userPromptsFromThread(history?.thread || { id: item.id, turns: [] }, fallbackTimestamp)
|
|
710
|
+
.map(prompt => ({ ...prompt, threadId: prompt.threadId || item.id }));
|
|
711
|
+
} catch (error) {
|
|
712
|
+
this.logger.debugInfo?.(`[codex-app-server] unable to read prompt history for ${item.id}: ${error.message}`);
|
|
713
|
+
return [];
|
|
714
|
+
}
|
|
715
|
+
}));
|
|
716
|
+
prompts.push(...histories.flat());
|
|
717
|
+
cursor = result?.nextCursor || null;
|
|
718
|
+
pageCount += 1;
|
|
719
|
+
if (prompts.length >= 200 || pageCount >= 5) {
|
|
720
|
+
capped = Boolean(cursor) || prompts.length > 200;
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
} while (cursor);
|
|
724
|
+
|
|
725
|
+
prompts.sort((a, b) => Number(b.createdAt || 0) - Number(a.createdAt || 0));
|
|
726
|
+
this.promptHistoryCache = {
|
|
727
|
+
loadedAt: Date.now(),
|
|
728
|
+
items: prompts.slice(0, 200),
|
|
729
|
+
capped
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const items = this.promptHistoryCache.items.slice(safeOffset, safeOffset + safeLimit);
|
|
734
|
+
const nextOffset = safeOffset + items.length;
|
|
735
|
+
return {
|
|
736
|
+
items,
|
|
737
|
+
offset: safeOffset,
|
|
738
|
+
nextOffset,
|
|
739
|
+
total: this.promptHistoryCache.items.length,
|
|
740
|
+
hasMore: nextOffset < this.promptHistoryCache.items.length,
|
|
741
|
+
capped: this.promptHistoryCache.capped
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
|
|
658
745
|
contextStatus(tokenUsage = this.tokenUsage) {
|
|
659
746
|
const usage = tokenUsage || {};
|
|
660
747
|
const selectedModel = this.models.find(item => item.id === this.model);
|
|
@@ -757,6 +844,7 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
757
844
|
.filter(item => item && typeof item.path === 'string' && item.path);
|
|
758
845
|
if ((!prompt && images.length === 0) || this.presentation !== 'structured' || this.status !== 'idle') return false;
|
|
759
846
|
this.hasUnreadCompletion = false;
|
|
847
|
+
this.promptHistoryCache = null;
|
|
760
848
|
this.append({
|
|
761
849
|
kind: 'user',
|
|
762
850
|
text: prompt || '📷 Image attachment',
|
|
@@ -882,25 +970,46 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
882
970
|
const target = String(threadId || this.threadId || '').trim();
|
|
883
971
|
if (!target || this.presentation !== 'structured' || this.status !== 'idle') return false;
|
|
884
972
|
await this.ensureProcess();
|
|
973
|
+
const selectedModel = this.model;
|
|
974
|
+
const selectedEffort = this.effort;
|
|
975
|
+
const resumeWithModelOverride = Boolean(this.hasModelOverride && selectedModel);
|
|
976
|
+
const resumeWithEffortOverride = Boolean(this.hasEffortOverride && selectedEffort);
|
|
885
977
|
const params = { threadId: target, cwd: this.workingDir };
|
|
886
978
|
if (this.permissionMode) params.approvalPolicy = this.permissionMode;
|
|
887
979
|
if (this.sandboxMode) params.sandbox = this.sandboxMode;
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
this.
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
980
|
+
if (resumeWithModelOverride) params.model = selectedModel;
|
|
981
|
+
if (resumeWithEffortOverride) params.config = { model_reasoning_effort: selectedEffort };
|
|
982
|
+
this.deferredWarnings = [];
|
|
983
|
+
try {
|
|
984
|
+
const result = await this.request('thread/resume', params);
|
|
985
|
+
this.threadId = result.thread?.id || target;
|
|
986
|
+
this.hasModelOverride = resumeWithModelOverride;
|
|
987
|
+
this.hasEffortOverride = resumeWithEffortOverride;
|
|
988
|
+
this.model = result.model || result.thread?.model || selectedModel;
|
|
989
|
+
this.effort = result.reasoningEffort || result.thread?.reasoningEffort || selectedEffort;
|
|
990
|
+
this.effectivePermissionMode = result.approvalPolicy || this.effectivePermissionMode;
|
|
991
|
+
this.effectiveSandboxMode = sandboxModeFromPolicy(result.sandbox) || this.effectiveSandboxMode;
|
|
992
|
+
const history = await this.request('thread/read', { threadId: this.threadId, includeTurns: true });
|
|
993
|
+
this.restoreThreadHistory(history?.thread, `Resumed Codex thread ${this.threadId}`, {
|
|
994
|
+
preserveModel: resumeWithModelOverride,
|
|
995
|
+
preserveEffort: resumeWithEffortOverride
|
|
996
|
+
});
|
|
997
|
+
const warnings = this.deferredWarnings;
|
|
998
|
+
this.deferredWarnings = null;
|
|
999
|
+
for (const warning of warnings) this.append(warning);
|
|
1000
|
+
this.promptHistoryCache = null;
|
|
1001
|
+
return true;
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
const warnings = this.deferredWarnings || [];
|
|
1004
|
+
this.deferredWarnings = null;
|
|
1005
|
+
for (const warning of warnings) this.append(warning);
|
|
1006
|
+
throw error;
|
|
1007
|
+
}
|
|
899
1008
|
}
|
|
900
1009
|
|
|
901
|
-
restoreThreadHistory(thread, eventText = '') {
|
|
902
|
-
this.model = thread?.model || this.model;
|
|
903
|
-
this.effort = thread?.reasoningEffort || thread?.reasoning_effort || this.effort;
|
|
1010
|
+
restoreThreadHistory(thread, eventText = '', options = {}) {
|
|
1011
|
+
if (!options.preserveModel) this.model = thread?.model || this.model;
|
|
1012
|
+
if (!options.preserveEffort) this.effort = thread?.reasoningEffort || thread?.reasoning_effort || this.effort;
|
|
904
1013
|
this.tokenUsage = thread?.tokenUsage || thread?.token_usage || this.tokenUsage;
|
|
905
1014
|
this.messages = [];
|
|
906
1015
|
this.completedPermissions = [];
|
|
@@ -948,6 +1057,7 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
948
1057
|
this.effectivePermissionMode = result.approvalPolicy || this.effectivePermissionMode;
|
|
949
1058
|
this.effectiveSandboxMode = sandboxModeFromPolicy(result.sandbox) || this.effectiveSandboxMode;
|
|
950
1059
|
this.restoreThreadHistory(forkedThread, `Forked from Codex thread ${sourceThreadId}`);
|
|
1060
|
+
this.promptHistoryCache = null;
|
|
951
1061
|
return { threadId: this.threadId };
|
|
952
1062
|
}
|
|
953
1063
|
|
|
@@ -55,6 +55,19 @@ function registerProviderRoutes(app, { sessionManager }) {
|
|
|
55
55
|
}
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
app.get('/api/sessions/:id/codex-prompts', async (req, res) => {
|
|
59
|
+
try {
|
|
60
|
+
const result = await sessionManager.listCodexPrompts(req.params.id, {
|
|
61
|
+
offset: req.query?.offset,
|
|
62
|
+
limit: req.query?.limit
|
|
63
|
+
});
|
|
64
|
+
if (!result) return res.status(404).json({ error: 'Codex session not found' });
|
|
65
|
+
res.json({ success: true, ...result });
|
|
66
|
+
} catch (error) {
|
|
67
|
+
res.status(400).json({ error: error.message });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
58
71
|
app.post('/api/sessions/:id/codex-abort', (req, res) => {
|
|
59
72
|
const success = sessionManager.abortCodex(req.params.id);
|
|
60
73
|
if (!success) return res.status(409).json({ error: 'Codex session is idle or unavailable' });
|
|
@@ -431,6 +431,12 @@ class SessionManager extends EventEmitter {
|
|
|
431
431
|
return session.listResumeThreads();
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
listCodexPrompts(id, options) {
|
|
435
|
+
const session = this.get(id);
|
|
436
|
+
if (!session || session.kind !== 'codex-structured') return null;
|
|
437
|
+
return session.listPromptHistory(options);
|
|
438
|
+
}
|
|
439
|
+
|
|
434
440
|
switchCodexPresentation(id, presentation) {
|
|
435
441
|
const session = this.get(id);
|
|
436
442
|
if (!session || session.kind !== 'codex-structured') return Promise.resolve(false);
|
package/lib/web/codex.js
CHANGED
|
@@ -103,6 +103,9 @@
|
|
|
103
103
|
const detail = running ? 'Compacting context…' : ['Completed', time].filter(Boolean).join(' · ');
|
|
104
104
|
return `<div class="codex-compaction-card${running ? ' running' : ''}" data-codex-key="compaction-${escapeHtml(item.id || item.providerId || '')}"><span class="codex-compaction-icon" aria-hidden="true">⇣</span><div><div class="codex-compaction-title">${running ? 'Compacting context' : 'Context compacted'}</div><div class="codex-compaction-meta">${escapeHtml(detail)}</div></div></div>`;
|
|
105
105
|
}
|
|
106
|
+
function renderCodexWarning(item) {
|
|
107
|
+
return `<div class="codex-warning-card" data-codex-key="warning-${escapeHtml(item.id || '')}" role="status"><span class="codex-warning-icon" aria-hidden="true">!</span><div><div class="codex-warning-title">Codex warning</div><div class="codex-warning-text">${codexText(item.text || 'Codex reported a warning.')}</div></div></div>`;
|
|
108
|
+
}
|
|
106
109
|
function renderCodexTool(item, permission = null) {
|
|
107
110
|
const status = codexToolStatus(item);
|
|
108
111
|
const isError = status === 'failed' || Boolean(item.error) || (item.exitCode != null && item.exitCode !== 0);
|
|
@@ -297,6 +300,7 @@
|
|
|
297
300
|
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>${renderCodexMessageMeta(item)}</div>`);
|
|
298
301
|
else if (item.kind === 'compaction') parts.push(renderCodexCompaction(item));
|
|
299
302
|
else if (item.kind === 'status') parts.push(renderCodexStatus(item));
|
|
303
|
+
else if (item.kind === 'event' && item.level === 'warning') parts.push(renderCodexWarning(item));
|
|
300
304
|
else parts.push(`<div class="codex-message ${escapeHtml(item.level === 'error' ? 'event error' : item.kind || 'event')}">${codexText(item.text)}</div>`);
|
|
301
305
|
i += 1;
|
|
302
306
|
}
|
|
@@ -395,9 +399,11 @@
|
|
|
395
399
|
codexModelPanelOpen = !codexModelPanelOpen;
|
|
396
400
|
codexResumePanelOpen = false;
|
|
397
401
|
codexForkPanelOpen = false;
|
|
402
|
+
codexPromptPanelOpen = false;
|
|
398
403
|
codexModelCandidate = codexState.model || codexState.models?.[0]?.id || null;
|
|
399
404
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
400
405
|
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
406
|
+
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
401
407
|
renderCodexModelPanel();
|
|
402
408
|
updateTerminalControlsHeight();
|
|
403
409
|
}
|
|
@@ -453,8 +459,10 @@
|
|
|
453
459
|
codexResumePanelOpen = !codexResumePanelOpen;
|
|
454
460
|
codexModelPanelOpen = false;
|
|
455
461
|
codexForkPanelOpen = false;
|
|
462
|
+
codexPromptPanelOpen = false;
|
|
456
463
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
457
464
|
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
465
|
+
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
458
466
|
const panel = document.getElementById('codex-resume-panel');
|
|
459
467
|
panel.classList.toggle('active', codexResumePanelOpen);
|
|
460
468
|
updateTerminalControlsHeight();
|
|
@@ -466,8 +474,10 @@
|
|
|
466
474
|
codexForkPanelOpen = !codexForkPanelOpen;
|
|
467
475
|
codexModelPanelOpen = false;
|
|
468
476
|
codexResumePanelOpen = false;
|
|
477
|
+
codexPromptPanelOpen = false;
|
|
469
478
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
470
479
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
480
|
+
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
471
481
|
const panel = document.getElementById('codex-fork-panel');
|
|
472
482
|
panel.classList.toggle('active', codexForkPanelOpen);
|
|
473
483
|
updateTerminalControlsHeight();
|
|
@@ -489,6 +499,91 @@
|
|
|
489
499
|
} catch (e) { panel.innerHTML = `<div class="claude-resume-meta" style="padding:12px;color:#ff6b61;">${escapeHtml(e.message)}</div>`; }
|
|
490
500
|
updateTerminalControlsHeight();
|
|
491
501
|
}
|
|
502
|
+
function formatCodexPromptTime(timestamp) {
|
|
503
|
+
const value = Number(timestamp || 0);
|
|
504
|
+
if (!value) return '';
|
|
505
|
+
const date = new Date(value);
|
|
506
|
+
return Number.isNaN(date.getTime()) ? '' : date.toLocaleString([], {
|
|
507
|
+
month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit'
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
function renderCodexPromptPanel(error = '') {
|
|
511
|
+
const panel = document.getElementById('codex-prompt-panel');
|
|
512
|
+
if (!panel) return;
|
|
513
|
+
const previousScrollTop = panel.scrollTop;
|
|
514
|
+
panel.classList.toggle('active', codexPromptPanelOpen);
|
|
515
|
+
if (!codexPromptPanelOpen) return;
|
|
516
|
+
const countLabel = codexPromptTotal ? `${codexPromptItems.length} / ${codexPromptTotal}${codexPromptTotal >= 200 ? ' max' : ''}` : '';
|
|
517
|
+
const items = codexPromptItems.map((item, index) => {
|
|
518
|
+
const expanded = codexExpandedPrompts.has(index);
|
|
519
|
+
return `<div class="codex-prompt-item"><button type="button" class="codex-prompt-text${expanded ? ' expanded' : ''}" onclick="toggleCodexPromptExpanded(${index})" title="${expanded ? 'Collapse prompt' : 'Expand prompt'}">${escapeHtml(item.text || '')}</button><div class="codex-prompt-actions"><span class="codex-prompt-time">${escapeHtml(formatCodexPromptTime(item.createdAt))}</span><button type="button" class="small-btn codex-prompt-copy" data-codex-prompt-copy="${index}" onclick="copyCodexPrompt(${index})">Copy</button></div></div>`;
|
|
520
|
+
}).join('');
|
|
521
|
+
const empty = !items && !codexPromptLoading && !error
|
|
522
|
+
? '<div class="claude-resume-meta" style="padding:12px;">No text prompts found for this folder.</div>' : '';
|
|
523
|
+
const footer = codexPromptHasMore || codexPromptLoading
|
|
524
|
+
? `<div class="codex-prompt-footer"><button type="button" class="small-btn primary" onclick="loadMoreCodexPrompts()"${codexPromptLoading ? ' disabled' : ''}>${codexPromptLoading ? 'Loading…' : 'Load more'}</button></div>` : '';
|
|
525
|
+
panel.innerHTML = `<div class="codex-prompt-header"><span>Prompt history</span><span>${escapeHtml(countLabel)}</span></div>${error ? `<div class="claude-resume-meta" style="padding:12px;color:#ff6b61;">${escapeHtml(error)}</div>` : ''}${items}${empty}${footer}`;
|
|
526
|
+
panel.scrollTop = previousScrollTop;
|
|
527
|
+
updateTerminalControlsHeight();
|
|
528
|
+
}
|
|
529
|
+
async function toggleCodexPromptPanel() {
|
|
530
|
+
codexPromptPanelOpen = !codexPromptPanelOpen;
|
|
531
|
+
codexModelPanelOpen = false;
|
|
532
|
+
codexResumePanelOpen = false;
|
|
533
|
+
codexForkPanelOpen = false;
|
|
534
|
+
document.getElementById('codex-model-panel').classList.remove('active');
|
|
535
|
+
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
536
|
+
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
537
|
+
if (!codexPromptPanelOpen) {
|
|
538
|
+
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
539
|
+
updateTerminalControlsHeight();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
codexPromptItems = [];
|
|
543
|
+
codexPromptNextOffset = 0;
|
|
544
|
+
codexPromptHasMore = false;
|
|
545
|
+
codexPromptTotal = 0;
|
|
546
|
+
codexExpandedPrompts = new Set();
|
|
547
|
+
renderCodexPromptPanel();
|
|
548
|
+
await loadMoreCodexPrompts();
|
|
549
|
+
}
|
|
550
|
+
async function loadMoreCodexPrompts() {
|
|
551
|
+
if (codexPromptLoading || !codexPromptPanelOpen || codexPromptNextOffset >= 200) return;
|
|
552
|
+
codexPromptLoading = true;
|
|
553
|
+
renderCodexPromptPanel();
|
|
554
|
+
try {
|
|
555
|
+
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-prompts?offset=${codexPromptNextOffset}&limit=30`, {}, 60000);
|
|
556
|
+
const data = await res.json();
|
|
557
|
+
if (!res.ok || !data.success) throw new Error(data.error || 'Failed to load prompt history');
|
|
558
|
+
codexPromptItems.push(...(data.items || []));
|
|
559
|
+
codexPromptNextOffset = Number(data.nextOffset || codexPromptItems.length);
|
|
560
|
+
codexPromptHasMore = Boolean(data.hasMore) && codexPromptNextOffset < 200;
|
|
561
|
+
codexPromptTotal = Math.min(200, Number(data.total || codexPromptItems.length));
|
|
562
|
+
codexPromptLoading = false;
|
|
563
|
+
renderCodexPromptPanel();
|
|
564
|
+
} catch (error) {
|
|
565
|
+
codexPromptLoading = false;
|
|
566
|
+
renderCodexPromptPanel(error.message);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function toggleCodexPromptExpanded(index) {
|
|
570
|
+
if (codexExpandedPrompts.has(index)) codexExpandedPrompts.delete(index);
|
|
571
|
+
else codexExpandedPrompts.add(index);
|
|
572
|
+
renderCodexPromptPanel();
|
|
573
|
+
}
|
|
574
|
+
async function copyCodexPrompt(index) {
|
|
575
|
+
const prompt = codexPromptItems[index]?.text;
|
|
576
|
+
if (!prompt) return;
|
|
577
|
+
try {
|
|
578
|
+
await copyTextToClipboard(prompt);
|
|
579
|
+
const button = document.querySelector(`[data-codex-prompt-copy="${index}"]`);
|
|
580
|
+
if (!button) return;
|
|
581
|
+
button.textContent = 'Copied';
|
|
582
|
+
setTimeout(() => { if (button.isConnected) button.textContent = 'Copy'; }, 1200);
|
|
583
|
+
} catch (_) {
|
|
584
|
+
alert('Copy failed.');
|
|
585
|
+
}
|
|
586
|
+
}
|
|
492
587
|
async function selectCodexResumeThread(threadId) {
|
|
493
588
|
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-resume`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ threadId }) }, 30000);
|
|
494
589
|
if (!res.ok) { alert((await res.json()).error || 'Unable to resume Codex thread'); return; }
|
package/lib/web/core.js
CHANGED
|
@@ -37,6 +37,13 @@
|
|
|
37
37
|
let codexModelCandidate = null;
|
|
38
38
|
let codexResumePanelOpen = false;
|
|
39
39
|
let codexForkPanelOpen = false;
|
|
40
|
+
let codexPromptPanelOpen = false;
|
|
41
|
+
let codexPromptItems = [];
|
|
42
|
+
let codexPromptNextOffset = 0;
|
|
43
|
+
let codexPromptHasMore = false;
|
|
44
|
+
let codexPromptTotal = 0;
|
|
45
|
+
let codexPromptLoading = false;
|
|
46
|
+
let codexExpandedPrompts = new Set();
|
|
40
47
|
let codexRenderFrame = null;
|
|
41
48
|
let codexApprovalJumpIndex = 0;
|
|
42
49
|
const modifiers = { ctrl: false };
|
package/lib/web/index.html
CHANGED
|
@@ -122,6 +122,7 @@
|
|
|
122
122
|
<button id="codex-fork-btn" class="claude-ctrl-btn primary" onclick="toggleCodexForkPanel()" title="Fork a Codex thread in this conversation">Fork</button>
|
|
123
123
|
</div>
|
|
124
124
|
<div class="codex-control-page">
|
|
125
|
+
<button id="codex-prompts-btn" class="claude-ctrl-btn" onclick="toggleCodexPromptPanel()" title="Browse and copy recent prompts">Prompts</button>
|
|
125
126
|
<button id="codex-compact-btn" class="claude-ctrl-btn" onclick="compactCodexContext()" title="Compact the current Codex context">Compact</button>
|
|
126
127
|
<label class="codex-select-control" title="Sandbox mode">
|
|
127
128
|
<span class="codex-select-label" aria-hidden="true">Sandbox</span>
|
|
@@ -141,6 +142,7 @@
|
|
|
141
142
|
<div id="codex-model-panel"></div>
|
|
142
143
|
<div id="codex-resume-panel"></div>
|
|
143
144
|
<div id="codex-fork-panel"></div>
|
|
145
|
+
<div id="codex-prompt-panel"></div>
|
|
144
146
|
</div>
|
|
145
147
|
<div id="timed-send-panel">
|
|
146
148
|
<div class="timed-row">
|
package/lib/web/session.js
CHANGED
|
@@ -130,9 +130,17 @@
|
|
|
130
130
|
codexModelCandidate = null;
|
|
131
131
|
codexResumePanelOpen = false;
|
|
132
132
|
codexForkPanelOpen = false;
|
|
133
|
+
codexPromptPanelOpen = false;
|
|
134
|
+
codexPromptItems = [];
|
|
135
|
+
codexPromptNextOffset = 0;
|
|
136
|
+
codexPromptHasMore = false;
|
|
137
|
+
codexPromptTotal = 0;
|
|
138
|
+
codexPromptLoading = false;
|
|
139
|
+
codexExpandedPrompts = new Set();
|
|
133
140
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
134
141
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
135
142
|
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
143
|
+
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
136
144
|
document.getElementById('codex-control-rail').scrollLeft = 0;
|
|
137
145
|
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 };
|
|
138
146
|
setClaudeModeEnabled(false);
|
package/lib/web/styles.css
CHANGED
|
@@ -105,6 +105,10 @@
|
|
|
105
105
|
.codex-compaction-title { font-size: 12px; font-weight: 800; }
|
|
106
106
|
.codex-compaction-meta { margin-top: 3px; color: #a9a9b0; font-size: 10px; font-variant-numeric: tabular-nums; }
|
|
107
107
|
.codex-compaction-card.running .codex-compaction-icon { animation: codex-pulse 1.1s ease-in-out infinite alternate; }
|
|
108
|
+
.codex-warning-card { display: flex; align-items: flex-start; gap: 10px; margin: 10px 0 14px; padding: 10px 12px; border: 1px solid rgba(255,204,0,.34); border-radius: 10px; background: linear-gradient(135deg, rgba(255,204,0,.13), rgba(255,159,10,.07)); color: #f5f5f7; }
|
|
109
|
+
.codex-warning-icon { display: grid; width: 28px; height: 28px; flex: 0 0 28px; place-items: center; border-radius: 50%; background: rgba(255,204,0,.16); color: #ffd60a; font-size: 15px; font-weight: 900; }
|
|
110
|
+
.codex-warning-title { color: #ffe680; font-size: 12px; font-weight: 800; }
|
|
111
|
+
.codex-warning-text { margin-top: 3px; color: #d8d3bf; font-size: 11px; line-height: 1.4; overflow-wrap: anywhere; }
|
|
108
112
|
.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; }
|
|
109
113
|
.codex-status-title { margin-bottom: 9px; color: #64d2ff; font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; }
|
|
110
114
|
.codex-status-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; }
|
|
@@ -173,8 +177,8 @@
|
|
|
173
177
|
.codex-select-control:focus-within .codex-select-label { background: rgba(255,255,255,.14); border-color: rgba(0,122,255,.45); }
|
|
174
178
|
.codex-select { appearance: none; position: absolute; inset: 0; width: 100%; min-width: 0; height: 100%; opacity: 0; cursor: pointer; }
|
|
175
179
|
.codex-select option { background: #1c1c1e; color: #fff; }
|
|
176
|
-
#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; }
|
|
177
|
-
#codex-model-panel.active, #codex-resume-panel.active, #codex-fork-panel.active { display: block; }
|
|
180
|
+
#codex-model-panel, #codex-resume-panel, #codex-fork-panel, #codex-prompt-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; }
|
|
181
|
+
#codex-model-panel.active, #codex-resume-panel.active, #codex-fork-panel.active, #codex-prompt-panel.active { display: block; }
|
|
178
182
|
#codex-model-panel { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); max-height: min(300px, 42dvh); }
|
|
179
183
|
#codex-model-panel.active { display: grid; }
|
|
180
184
|
.codex-picker-column { min-width: 0; overflow-y: auto; }
|
|
@@ -184,6 +188,17 @@
|
|
|
184
188
|
#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; }
|
|
185
189
|
#codex-state-bar::-webkit-scrollbar { display: none; }
|
|
186
190
|
#codex-resume-panel, #codex-fork-panel { max-height: min(280px, 38dvh); overflow-y: auto; }
|
|
191
|
+
#codex-prompt-panel { max-height: min(440px, 52dvh); overflow-y: auto; }
|
|
192
|
+
.codex-prompt-header { position: sticky; top: 0; z-index: 2; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 11px; border-bottom: 1px solid rgba(255,255,255,.08); background: rgba(28,28,30,.97); color: var(--text-dim); font-size: 11px; font-weight: 800; }
|
|
193
|
+
.codex-prompt-item { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: end; padding: 10px 11px; border-bottom: 1px solid rgba(255,255,255,.06); }
|
|
194
|
+
.codex-prompt-item:last-of-type { border-bottom: 0; }
|
|
195
|
+
.codex-prompt-text { display: -webkit-box; width: 100%; min-width: 0; max-height: calc(1.42em * 4); overflow: hidden; padding: 0; border: 0; background: transparent; color: #f5f5f7; font: 12px/1.42 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; text-align: left; white-space: pre-wrap; overflow-wrap: anywhere; -webkit-box-orient: vertical; -webkit-line-clamp: 4; cursor: pointer; }
|
|
196
|
+
.codex-prompt-text.expanded { display: block; max-height: none; -webkit-line-clamp: initial; }
|
|
197
|
+
.codex-prompt-actions { display: flex; min-width: 64px; flex-direction: column; align-items: flex-end; gap: 6px; }
|
|
198
|
+
.codex-prompt-time { color: #77777e; font-size: 10px; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
199
|
+
.codex-prompt-copy { min-width: 54px; min-height: 28px; padding: 0 9px; }
|
|
200
|
+
.codex-prompt-footer { padding: 9px 11px; text-align: center; }
|
|
201
|
+
.codex-prompt-footer .small-btn { min-width: 112px; }
|
|
187
202
|
.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; }
|
|
188
203
|
.claude-message-block { max-width: 100%; margin: 0 0 12px; }
|
|
189
204
|
.claude-message-block > .claude-message { margin-bottom: 0; }
|