From 9eef7b7728f94b4ecfa139d59aceb7ca20a0a431 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Sun, 21 Dec 2025 17:08:31 -0600 Subject: [PATCH] ## CHANGES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bumped Codex session cache version to refresh improved previews! ๐Ÿš€ - Skips system/environment context tags when building first-message previews! โœจ - Adds robust system-context detection for cleaner session list summaries! ๐Ÿงน - Improves first user-message capture, avoiding noisy XML-like metadata! ๐ŸŽฏ - Refines CWD extraction flow, reusing parsed text more efficiently! โšก - Resets aggregate stats when agentId changes, not just cwd! ๐Ÿ”„ - Simplifies total token display layout with cleaner inline computation! ๐Ÿง  - Shows tokens and context percentage together for faster readability! ๐Ÿ‘€ - Enhances tab naming across Claude, OpenCode, and Codex session IDs! ๐Ÿท๏ธ - Adds smart session-id truncation fallbacks for consistent tab labels! โœ… --- src/main/storage/codex-session-storage.ts | 55 ++++++++++++------- .../components/AgentSessionsBrowser.tsx | 26 ++++----- src/renderer/components/TabBar.tsx | 30 +++++++++- 3 files changed, 71 insertions(+), 40 deletions(-) diff --git a/src/main/storage/codex-session-storage.ts b/src/main/storage/codex-session-storage.ts index 8d10c83d..34c37a2f 100644 --- a/src/main/storage/codex-session-storage.ts +++ b/src/main/storage/codex-session-storage.ts @@ -52,7 +52,7 @@ function getCodexSessionsDir(): string { const CODEX_SESSIONS_DIR = getCodexSessionsDir(); -const CODEX_SESSION_CACHE_VERSION = 1; +const CODEX_SESSION_CACHE_VERSION = 2; // Bumped: skip system context in firstMessage preview const CODEX_SESSION_CACHE_FILENAME = 'codex-sessions-cache.json'; /** @@ -135,6 +135,19 @@ function extractTextFromContent(content: CodexMessageContent[] | undefined): str return textParts.join(' '); } +/** + * Check if text is a system/environment context message that should be skipped for preview + */ +function isSystemContextMessage(text: string): boolean { + if (!text) return false; + const trimmed = text.trim(); + // Skip messages that start with environment/system context XML tags + return trimmed.startsWith('') || + trimmed.startsWith('') || + trimmed.startsWith('') || + trimmed.startsWith(''); +} + function extractCwdFromText(text: string): string | null { const match = text.match(/([^<]+)<\/cwd>/i); return match ? match[1].trim() : null; @@ -273,19 +286,19 @@ async function parseSessionFile( if (entry.type === 'message') { if (entry.role === 'user') { userMessageCount++; - // Capture first user message as fallback preview - if (!firstUserMessage && entry.content) { + // Extract text and check for system context + if (entry.content) { const text = extractTextFromContent(entry.content); - if (text.trim()) { + // Capture first user message as fallback preview (skip system context) + if (!firstUserMessage && text.trim() && !isSystemContextMessage(text)) { firstUserMessage = text; } - } - // Fallback: extract cwd from message content if not in session_meta - if (!sessionProjectPath && entry.content) { - const text = extractTextFromContent(entry.content); - const cwd = extractCwdFromText(text); - if (cwd) { - sessionProjectPath = cwd; + // Fallback: extract cwd from message content if not in session_meta + if (!sessionProjectPath) { + const cwd = extractCwdFromText(text); + if (cwd) { + sessionProjectPath = cwd; + } } } } else if (entry.role === 'assistant') { @@ -304,19 +317,19 @@ async function parseSessionFile( if (entry.type === 'response_item' && entry.payload?.type === 'message') { if (entry.payload.role === 'user') { userMessageCount++; - // Capture first user message as fallback preview - if (!firstUserMessage && entry.payload.content) { + // Extract text and check for system context + if (entry.payload.content) { const text = extractTextFromContent(entry.payload.content); - if (text.trim()) { + // Capture first user message as fallback preview (skip system context) + if (!firstUserMessage && text.trim() && !isSystemContextMessage(text)) { firstUserMessage = text; } - } - // Fallback: extract cwd from message content if not in session_meta - if (!sessionProjectPath && entry.payload.content) { - const text = extractTextFromContent(entry.payload.content); - const cwd = extractCwdFromText(text); - if (cwd) { - sessionProjectPath = cwd; + // Fallback: extract cwd from message content if not in session_meta + if (!sessionProjectPath) { + const cwd = extractCwdFromText(text); + if (cwd) { + sessionProjectPath = cwd; + } } } } else if (entry.payload.role === 'assistant') { diff --git a/src/renderer/components/AgentSessionsBrowser.tsx b/src/renderer/components/AgentSessionsBrowser.tsx index 344d92d6..b207b114 100644 --- a/src/renderer/components/AgentSessionsBrowser.tsx +++ b/src/renderer/components/AgentSessionsBrowser.tsx @@ -176,10 +176,10 @@ export function AgentSessionsBrowser({ prevViewingSessionRef.current = viewingSession; }, [viewingSession]); - // Reset aggregate stats when cwd changes (session loading is handled by useSessionPagination) + // Reset aggregate stats when cwd or agentId changes (session loading is handled by useSessionPagination) useEffect(() => { setAggregateStats({ totalSessions: 0, totalMessages: 0, totalCostUsd: 0, totalSizeBytes: 0, totalTokens: 0, oldestTimestamp: null, isComplete: false }); - }, [activeSession?.cwd]); + }, [activeSession?.cwd, agentId]); // Listen for progressive stats updates (Claude-specific) useEffect(() => { @@ -758,20 +758,14 @@ export function AgentSessionsBrowser({ Total Tokens - {(() => { - const totalTokens = viewingSession.inputTokens + viewingSession.outputTokens; - const contextUsage = Math.min(100, (totalTokens / 200000) * 100); - return ( - <> - - {formatNumber(totalTokens)} - - - of 200k context {contextUsage.toFixed(1)}% - - - ); - })()} +
+ + {formatNumber(viewingSession.inputTokens + viewingSession.outputTokens)} + + + of 200k context {Math.min(100, ((viewingSession.inputTokens + viewingSession.outputTokens) / 200000) * 100).toFixed(1)}% + +
{/* Messages */} diff --git a/src/renderer/components/TabBar.tsx b/src/renderer/components/TabBar.tsx index a702651c..5ef49f02 100644 --- a/src/renderer/components/TabBar.tsx +++ b/src/renderer/components/TabBar.tsx @@ -44,15 +44,39 @@ interface TabProps { /** * Get the display name for a tab. - * Priority: name > first UUID octet > "New" + * Priority: name > truncated session ID > "New" + * + * Handles different agent session ID formats: + * - Claude UUID: "abc123-def456-ghi789" โ†’ "ABC123" (first octet) + * - OpenCode: "SES_4BCDFE8C5FFE4KC1UV9NSMYEDB" โ†’ "SES_4BCD" (prefix + 4 chars) + * - Codex: "thread_abc123..." โ†’ "THR_ABC1" (prefix + 4 chars) */ function getTabDisplayName(tab: AITab): string { if (tab.name) { return tab.name; } if (tab.agentSessionId) { - // Return first octet of UUID in uppercase - return tab.agentSessionId.split('-')[0].toUpperCase(); + const id = tab.agentSessionId; + + // OpenCode format: ses_XXXX... or SES_XXXX... + if (id.toLowerCase().startsWith('ses_')) { + // Return "SES_" + first 4 chars of the ID portion + return `SES_${id.slice(4, 8).toUpperCase()}`; + } + + // Codex format: thread_XXXX... + if (id.toLowerCase().startsWith('thread_')) { + // Return "THR_" + first 4 chars of the ID portion + return `THR_${id.slice(7, 11).toUpperCase()}`; + } + + // Claude UUID format: has dashes, return first octet + if (id.includes('-')) { + return id.split('-')[0].toUpperCase(); + } + + // Generic fallback: first 8 chars uppercase + return id.slice(0, 8).toUpperCase(); } return 'New Session'; }