/** * live-session.ts — Full live session TUI orchestrator. * * Opens an overlay showing a running agent's conversation in real-time, * with a status bar, streaming conversation view, tool call visualization, * and an input harness for injecting messages. * * Uses ctx.ui.custom() for rendering — the same pattern as ConversationViewer. */ import type { AgentSession, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; import { SessionEventStream } from "../session-events.js"; import type { AgentRecord } from "../types.js"; import type { AgentActivity } from "./agent-widget.js"; import { SessionInputHarness } from "./session-input-harness.js"; import { SessionStatusBar } from "./session-status-bar.js"; import { SessionStreamPane } from "./session-stream-pane.js"; import { SessionToolCalls } from "./session-tool-calls.js"; /** * Open the live session TUI overlay for watching and interacting with a running agent. * * The overlay shows: * 1. Status bar (agent name, elapsed time, tool/turn counts) * 2. Streaming conversation pane (scrollable, auto-follow) * 3. Tool call boxes (inline, active/completed) * 4. Input harness (type messages to inject into the session) * * @param ctx Extension command context from pi-coding-agent * @param session The agent session to observe * @param record Agent record with metadata * @param activity Optional activity tracker for live stats */ export async function showLiveSession( ctx: ExtensionCommandContext, session: AgentSession, record: AgentRecord, _activity: AgentActivity | undefined, ): Promise { const events = new SessionEventStream(session, record); await ctx.ui.custom( (tui, theme, _keybindings, done) => { const statusBar = new SessionStatusBar(tui, events, theme, record); const streamPane = new SessionStreamPane(tui, events, theme, record); const toolCalls = new SessionToolCalls(tui, events, theme); const harness = new SessionInputHarness(tui, events, theme, () => done(undefined)); return { render(width: number): string[] { if (width < 10) return []; const SEP = "─".repeat(Math.max(1, width - 2)); const lines: string[] = []; // 1. Status bar at top (1 line) const statusLines = statusBar.render(width); lines.push(...statusLines); // Separator lines.push(SEP); // 2. Stream pane (main content — fills remaining viewport space) const streamLines = streamPane.render(width); lines.push(...streamLines); // 3. Tool calls (inline section if any are active) const toolLines = toolCalls.render(width); if (toolLines.length > 0) { lines.push(""); // spacing before tools lines.push(...toolLines); } // 4. Input harness at the bottom const harnessLines = harness.render(width); lines.push(...harnessLines); return lines; }, handleInput(data: string): void { if (harness.focused) { harness.handleInput(data); } else { streamPane.handleInput(data); } // Tab to toggle focus between harness and stream // Done inside harness.handleInput for tab }, invalidate(): void { // Sub-components use tui.requestRender() internally }, }; }, { overlay: true, overlayOptions: { anchor: "center", width: "90%" }, }, ); events.close(); }