diff --git a/plugins/codebuddy-hud/src/config.ts b/plugins/codebuddy-hud/src/config.ts index 2daecc9..d8262ef 100644 --- a/plugins/codebuddy-hud/src/config.ts +++ b/plugins/codebuddy-hud/src/config.ts @@ -19,6 +19,7 @@ export const DEFAULT_CONFIG: HudConfig = { tasks: false, contextUsage: true, contextValues: false, + sessionId: false, }, colors: { model: 'blue', diff --git a/plugins/codebuddy-hud/src/render/identity.ts b/plugins/codebuddy-hud/src/render/identity.ts index 6e56287..91780a1 100644 --- a/plugins/codebuddy-hud/src/render/identity.ts +++ b/plugins/codebuddy-hud/src/render/identity.ts @@ -1,9 +1,9 @@ import type { RenderContext } from '../types.js'; -import { getModelName, getProjectPath, getVersion } from '../stdin.js'; -import { colorize } from './colors.js'; +import { getModelName, getProjectPath, getVersion, getSessionId } from '../stdin.js'; +import { colorize, dim } from './colors.js'; export function renderIdentityLine(ctx: RenderContext): string | null { - const { config, stdin, gitStatus } = ctx; + const { config, stdin, gitStatus, transcript } = ctx; const { display, colors } = config; const parts: string[] = []; @@ -32,6 +32,18 @@ export function renderIdentityLine(ctx: RenderContext): string | null { } } + if (display.sessionId) { + const sessionName = transcript?.sessionName; + if (sessionName) { + parts.push(colorize(sessionName, 'brightCyan')); + } else { + const sid = getSessionId(stdin); + if (sid) { + parts.push(dim(`#${sid}`)); + } + } + } + if (parts.length === 0) return null; return parts.join(' | '); diff --git a/plugins/codebuddy-hud/src/render/index.ts b/plugins/codebuddy-hud/src/render/index.ts index 0e3be15..6e4d1b1 100644 --- a/plugins/codebuddy-hud/src/render/index.ts +++ b/plugins/codebuddy-hud/src/render/index.ts @@ -3,8 +3,8 @@ import { renderIdentityLine } from './identity.js'; import { renderStatsLine, renderContextBar } from './stats.js'; import { renderActivityLine } from './activity.js'; import { getTerminalWidth, wrapLineToWidth } from './width.js'; -import { getModelName, getProjectPath } from '../stdin.js'; -import { colorize } from './colors.js'; +import { getModelName, getProjectPath, getSessionId } from '../stdin.js'; +import { colorize, dim } from './colors.js'; const RESET = '\x1b[0m'; @@ -52,6 +52,11 @@ function renderCompact(ctx: RenderContext): string[] { if (ver) parts.push(`v${ver}`); } + if (display.sessionId) { + const sid = getSessionId(stdin); + if (sid) parts.push(dim(`#${sid}`)); + } + // Stats (without context, since it's already shown above) const stats = renderStatsLine(ctx, { includeContext: false }); if (stats) parts.push(stats); diff --git a/plugins/codebuddy-hud/src/stdin.ts b/plugins/codebuddy-hud/src/stdin.ts index 24017ee..1bc8d01 100644 --- a/plugins/codebuddy-hud/src/stdin.ts +++ b/plugins/codebuddy-hud/src/stdin.ts @@ -82,3 +82,9 @@ export function getContextWindow(stdin: Partial): ContextWindowInfo { export function getVersion(stdin: Partial): string | null { return stdin.version ?? null; } + +export function getSessionId(stdin: Partial, length = 8): string | null { + const id = stdin.session_id ?? null; + if (!id) return null; + return id.slice(0, length); +} diff --git a/plugins/codebuddy-hud/src/transcript.ts b/plugins/codebuddy-hud/src/transcript.ts index e6466f8..f662fd9 100644 --- a/plugins/codebuddy-hud/src/transcript.ts +++ b/plugins/codebuddy-hud/src/transcript.ts @@ -6,7 +6,14 @@ import * as os from 'node:os'; import type { TranscriptData, ToolEntry, AgentEntry, TaskItem } from './types.js'; interface TranscriptLine { + type?: string; timestamp?: string; + aiTitle?: string; + customTitle?: string; + callId?: string; + name?: string; + arguments?: string; + isError?: boolean; message?: { content?: ContentBlock[]; }; @@ -44,6 +51,7 @@ interface CacheFile { agents: SerializedAgentEntry[]; tasks: TaskItem[]; sessionStart?: string; + sessionName?: string; }; } @@ -88,6 +96,7 @@ function readCache(transcriptPath: string, state: FileState): TranscriptData | n })), tasks: cached.data.tasks, sessionStart: cached.data.sessionStart ? new Date(cached.data.sessionStart) : undefined, + sessionName: cached.data.sessionName, }; } catch { return null; @@ -113,6 +122,7 @@ function writeCache(transcriptPath: string, state: FileState, data: TranscriptDa })), tasks: data.tasks, sessionStart: data.sessionStart?.toISOString(), + sessionName: data.sessionName, }, }; fs.writeFileSync(getCachePath(transcriptPath), JSON.stringify(payload), 'utf8'); @@ -175,6 +185,7 @@ export async function parseTranscript(transcriptPath: string): Promise(); let sessionStart: Date | undefined; + let sessionName: string | undefined; let parsedCleanly = false; try { @@ -193,6 +204,83 @@ export async function parseTranscript(transcriptPath: string): Promise { try { return JSON.parse(entry.arguments) as Record; } catch { return undefined; } })() + : undefined; + if (entry.name === 'Task') { + agentMap.set(entry.callId, { + id: entry.callId, + type: (input?.subagent_type as string) ?? 'unknown', + model: (input?.model as string) ?? undefined, + description: (input?.description as string) ?? undefined, + status: 'running', + startTime: timestamp, + }); + } else if (entry.name === 'TaskCreate') { + const subject = typeof input?.subject === 'string' ? input.subject : ''; + const description = typeof input?.description === 'string' ? input.description : ''; + const taskContent = subject || description || 'Untitled task'; + const status = normalizeTaskStatus(input?.status) ?? 'pending'; + tasks.push({ content: taskContent, status }); + const taskId = typeof input?.taskId === 'string' || typeof input?.taskId === 'number' + ? String(input.taskId) + : entry.callId; + taskIdToIndex.set(taskId, tasks.length - 1); + } else if (entry.name === 'TaskUpdate') { + const taskId = typeof input?.taskId === 'string' || typeof input?.taskId === 'number' + ? String(input.taskId) + : undefined; + if (taskId) { + let index = taskIdToIndex.get(taskId); + if (index === undefined && /^\d+$/.test(taskId)) { + const numIdx = parseInt(taskId, 10) - 1; + if (numIdx >= 0 && numIdx < tasks.length) index = numIdx; + } + if (index !== undefined) { + const newStatus = normalizeTaskStatus(input?.status); + if (newStatus) tasks[index].status = newStatus; + const newSubject = typeof input?.subject === 'string' ? input.subject : ''; + const newDesc = typeof input?.description === 'string' ? input.description : ''; + const newContent = newSubject || newDesc; + if (newContent) tasks[index].content = newContent; + } + } + } else { + toolMap.set(entry.callId, { + id: entry.callId, + name: entry.name, + target: extractTarget(entry.name, input), + status: 'running', + startTime: timestamp, + }); + } + continue; + } + + if (entry.type === 'function_call_result' && entry.callId) { + const tool = toolMap.get(entry.callId); + if (tool) { + tool.status = entry.isError ? 'error' : 'completed'; + tool.endTime = timestamp; + } + const agent = agentMap.get(entry.callId); + if (agent) { + agent.status = 'completed'; + agent.endTime = timestamp; + } + continue; + } + const content = entry.message?.content; if (!content || !Array.isArray(content)) continue; @@ -281,6 +369,7 @@ export async function parseTranscript(transcriptPath: string): Promise