diff --git a/appcast.xml b/appcast.xml index a076cd946..ed3682187 100644 --- a/appcast.xml +++ b/appcast.xml @@ -4,9 +4,9 @@ Clawdis 2.0.0-beta5 - Sat, 03 Jan 2026 06:11:43 +0100 + Sat, 03 Jan 2026 07:15:16 +0100 https://raw.githubusercontent.com/steipete/clawdis/main/appcast.xml - 2.0.0-beta5 + 2765 2.0.0-beta5 15.0 Clawdis 2.0.0-beta5 @@ -62,7 +62,8 @@
  • CLI: add configure, doctor, and update wizards for ongoing setup, health checks, and modernization.
  • CLI: add Signal CLI auto-install from GitHub releases in the wizard and persist wizard run metadata in config.
  • CLI: add remote gateway client config (gateway.remote.*) with Bonjour-assisted discovery.
  • -
  • CLI: add clawdis tui gateway-connected terminal UI (local or remote).
  • +
  • CLI: enhance clawdis tui with model/session pickers, tool cards, and slash commands (local or remote).
  • +
  • Gateway: allow sessions.patch to set per-session model overrides (used by the TUI /model flow).
  • Skills: allow bun as a node manager for skill installs.
  • Skills: add things-mac (Things 3 CLI) for read/search plus add/update via URL scheme.
  • Skills: add Apple Notes + Reminders skills via memo CLI (thanks @tylerwince).
  • @@ -205,7 +206,7 @@

    View full changelog

    ]]>
    - +
    \ No newline at end of file diff --git a/src/tui/components/chat-log.ts b/src/tui/components/chat-log.ts index 8129707f6..dae49be4e 100644 --- a/src/tui/components/chat-log.ts +++ b/src/tui/components/chat-log.ts @@ -8,6 +8,7 @@ export class ChatLog extends Container { private toolById = new Map(); private streamingAssistant: AssistantMessageComponent | null = null; private streamingRunId: string | null = null; + private streamingText: string | null = null; private toolsExpanded = false; clearAll() { @@ -15,6 +16,7 @@ export class ChatLog extends Container { this.toolById.clear(); this.streamingAssistant = null; this.streamingRunId = null; + this.streamingText = null; } addSystem(text: string) { @@ -30,6 +32,7 @@ export class ChatLog extends Container { const component = new AssistantMessageComponent(text); this.streamingAssistant = component; this.streamingRunId = runId ?? null; + this.streamingText = text; this.addChild(component); return component; } @@ -42,17 +45,23 @@ export class ChatLog extends Container { this.startAssistant(text, runId); return; } + this.streamingText = text; this.streamingAssistant.setText(text); } finalizeAssistant(text: string, runId?: string) { - if (this.streamingAssistant && (!runId || runId === this.streamingRunId)) { + if ( + this.streamingAssistant && + (!runId || runId === this.streamingRunId || text === this.streamingText) + ) { + this.streamingText = text; this.streamingAssistant.setText(text); } else { this.startAssistant(text, runId); } this.streamingAssistant = null; this.streamingRunId = null; + this.streamingText = null; } startTool(toolCallId: string, toolName: string, args: unknown) { diff --git a/src/tui/tui.ts b/src/tui/tui.ts index 9e3218045..b785f6847 100644 --- a/src/tui/tui.ts +++ b/src/tui/tui.ts @@ -110,6 +110,7 @@ export async function runTui(opts: TuiOptions) { let currentSessionKey = defaultSession; let currentSessionId: string | null = null; let activeChatRunId: string | null = null; + const finalizedRuns = new Map(); let historyLoaded = false; let isConnected = false; let toolsExpanded = false; @@ -296,10 +297,30 @@ export async function runTui(opts: TuiOptions) { tui.requestRender(); }; + const noteFinalizedRun = (runId: string) => { + finalizedRuns.set(runId, Date.now()); + if (finalizedRuns.size <= 200) return; + const keepUntil = Date.now() - 10 * 60 * 1000; + for (const [key, ts] of finalizedRuns) { + if (finalizedRuns.size <= 150) break; + if (ts < keepUntil) finalizedRuns.delete(key); + } + if (finalizedRuns.size > 200) { + for (const key of finalizedRuns.keys()) { + finalizedRuns.delete(key); + if (finalizedRuns.size <= 150) break; + } + } + }; + const handleChatEvent = (payload: unknown) => { if (!payload || typeof payload !== "object") return; const evt = payload as ChatEvent; if (evt.sessionKey !== currentSessionKey) return; + if (finalizedRuns.has(evt.runId)) { + if (evt.state === "delta") return; + if (evt.state === "final") return; + } if (evt.state === "delta") { const text = extractTextFromMessage(evt.message, { includeThinking: showThinking, @@ -313,6 +334,7 @@ export async function runTui(opts: TuiOptions) { includeThinking: showThinking, }); chatLog.finalizeAssistant(text || "(no output)", evt.runId); + noteFinalizedRun(evt.runId); activeChatRunId = null; setStatus("idle"); }