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.
@@ -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
- this.append({ kind: 'event', level: 'warning', text: params.message || params.warning || 'Codex warning.' });
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
- const result = await this.request('thread/resume', params);
889
- this.threadId = result.thread?.id || target;
890
- this.hasModelOverride = false;
891
- this.hasEffortOverride = false;
892
- this.model = result.model || result.thread?.model || this.model;
893
- this.effort = result.reasoningEffort || result.thread?.reasoningEffort || this.effort;
894
- this.effectivePermissionMode = result.approvalPolicy || this.effectivePermissionMode;
895
- this.effectiveSandboxMode = sandboxModeFromPolicy(result.sandbox) || this.effectiveSandboxMode;
896
- const history = await this.request('thread/read', { threadId: this.threadId, includeTurns: true });
897
- this.restoreThreadHistory(history?.thread, `Resumed Codex thread ${this.threadId}`);
898
- return true;
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 };
@@ -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">
@@ -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);
@@ -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; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glad-web",
3
- "version": "1.0.31",
3
+ "version": "1.0.32",
4
4
  "description": "Glad transforms terminal-based AI coding tools into a polished, mobile-friendly local Web interface.",
5
5
  "bin": {
6
6
  "glad": "bin/cli.js"