diff --git a/docs/web/tui.md b/docs/web/tui.md index 67c22de6e80..252d133c7ce 100644 --- a/docs/web/tui.md +++ b/docs/web/tui.md @@ -54,7 +54,7 @@ Notes: - Header: connection URL, current agent, current session. - Chat log: user messages, assistant replies, system notices, tool cards. - Status line: connection/run state (connecting, running, streaming, idle, error). -- Footer: connection state + agent + session + model + goal state + think/fast/verbose/trace/reasoning + token counts + deliver. +- Footer: connection host when available + agent + session + model + goal state + think/fast/verbose/trace/reasoning + token counts + deliver. - Input: text editor with autocomplete. ## Mental model: agents + sessions @@ -67,7 +67,7 @@ Notes: - Session scope: - `per-sender` (default): each agent has many sessions. - `global`: the TUI always uses the `global` session (the picker may be empty). -- The current agent + session are always visible in the footer. +- For URL-backed connections, the footer includes the connection host alongside the current agent and session. - If the session has a [goal](/tools/goal), the footer shows its compact state such as `Pursuing goal`, `Goal paused (/goal resume)`, or `Goal achieved`. diff --git a/src/tui/tui-formatters.test.ts b/src/tui/tui-formatters.test.ts index 4664d8106e8..995130f1702 100644 --- a/src/tui/tui-formatters.test.ts +++ b/src/tui/tui-formatters.test.ts @@ -5,6 +5,7 @@ import { extractContentFromMessage, extractTextFromMessage, extractThinkingFromMessage, + formatConnectionHostFooter, formatGoalFooter, isCommandMessage, sanitizeRenderableText, @@ -45,6 +46,19 @@ describe("formatGoalFooter", () => { }); }); +describe("formatConnectionHostFooter", () => { + it("renders only the connection hostname", () => { + expect(formatConnectionHostFooter("ws://gateway-host:18789")).toBe("host gateway-host"); + expect( + formatConnectionHostFooter("wss://user:secret@example.com:443/path?token=redacted"), + ).toBe("host example.com"); + }); + + it("skips non-url local connection labels", () => { + expect(formatConnectionHostFooter("local embedded")).toBeNull(); + }); +}); + describe("extractTextFromMessage", () => { it("prefers final_answer text over commentary text for assistant messages", () => { const text = extractTextFromMessage({ diff --git a/src/tui/tui-formatters.ts b/src/tui/tui-formatters.ts index ea989e2e311..e6238b7d90f 100644 --- a/src/tui/tui-formatters.ts +++ b/src/tui/tui-formatters.ts @@ -443,6 +443,15 @@ export function formatTokens(total?: number | null, context?: number | null) { return `tokens ${totalLabel}/${formatTokenCount(context)}${pct !== null ? ` (${pct}%)` : ""}`; } +export function formatConnectionHostFooter(connectionUrl: string): string | null { + try { + const hostname = new URL(connectionUrl.trim()).hostname.trim(); + return hostname ? `host ${hostname}` : null; + } catch { + return null; + } +} + function formatGoalUsage(goal: SessionGoal): string | null { if (goal.tokenBudget === undefined) { return goal.tokensUsed > 0 ? formatTokenCount(goal.tokensUsed) : null; diff --git a/src/tui/tui-pty-harness.e2e.test.ts b/src/tui/tui-pty-harness.e2e.test.ts index 0bbcf7feae1..121c3ec8eb9 100644 --- a/src/tui/tui-pty-harness.e2e.test.ts +++ b/src/tui/tui-pty-harness.e2e.test.ts @@ -364,6 +364,7 @@ describe.sequential("TUI PTY harness", () => { it("renders local ready on startup", () => { expect(fixture.run.output()).toContain("local ready"); + expect(fixture.run.output()).toContain("host local"); }); it( diff --git a/src/tui/tui.ts b/src/tui/tui.ts index ee1b39b6ca4..1a488ca9d6e 100644 --- a/src/tui/tui.ts +++ b/src/tui/tui.ts @@ -35,7 +35,7 @@ import { editorTheme, theme } from "./theme/theme.js"; import type { TuiBackend } from "./tui-backend.js"; import { createCommandHandlers } from "./tui-command-handlers.js"; import { createEventHandlers } from "./tui-event-handlers.js"; -import { formatGoalFooter, formatTokens } from "./tui-formatters.js"; +import { formatConnectionHostFooter, formatGoalFooter, formatTokens } from "./tui-formatters.js"; import { buildTuiLastSessionScopeKey, readTuiLastSessionKey, @@ -1199,7 +1199,9 @@ export async function runTui(opts: RunTuiOptions): Promise { const reasoning = sessionInfo.reasoningLevel ?? "off"; const reasoningLabel = reasoning === "on" ? "reasoning" : reasoning === "stream" ? "reasoning:stream" : null; + const hostLabel = formatConnectionHostFooter(client.connection.url); const footerParts = [ + hostLabel, `agent ${agentLabel}`, `session ${sessionLabel}`, modelLabel,