From abd37342fa7fde2bf3d3217c62f7cb2f07420e8f Mon Sep 17 00:00:00 2001 From: William Valentin Date: Wed, 18 Feb 2026 16:18:27 -0800 Subject: [PATCH] fix(webchat): route slash commands through agent command fast-path --- docs/plans/state.json | 13 +++++- src/gateway/ui/pages/chat.js | 82 +++++++++++++----------------------- 2 files changed, 42 insertions(+), 53 deletions(-) diff --git a/docs/plans/state.json b/docs/plans/state.json index 3a1d144..e473ef0 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -1,6 +1,6 @@ { "version": "1.0", - "updated_at": "2026-02-18", + "updated_at": "2026-02-19", "description": "Tracks the status of all Flynn plans and implementation phases", "plans": { "makefile-skills-convenience-targets": { @@ -5548,6 +5548,17 @@ "docs/plans/state.json" ], "test_status": "pnpm test:run src/channels/telegram/adapter.test.ts src/config/schema.test.ts src/gateway/handlers/services.test.ts src/cli/doctor.test.ts + pnpm typecheck passing" + }, + "webchat-slash-command-agent-fastpath-fix": { + "status": "completed", + "date": "2026-02-19", + "updated": "2026-02-19", + "summary": "Fixed WebChat slash command reliability by routing /reset, /compact, /usage, /status, and /model through agent.send command metadata fast-path instead of mixed local RPC shortcuts. This resolves /model returning unknown and keeps command behavior aligned with backend command registry.", + "files_modified": [ + "src/gateway/ui/pages/chat.js", + "docs/plans/state.json" + ], + "test_status": "pnpm typecheck + pnpm test:run src/commands/registry.test.ts src/commands/builtin/index.test.ts passing" } }, "overall_progress": { diff --git a/src/gateway/ui/pages/chat.js b/src/gateway/ui/pages/chat.js index 43aaa0c..10bb173 100644 --- a/src/gateway/ui/pages/chat.js +++ b/src/gateway/ui/pages/chat.js @@ -368,6 +368,20 @@ function showSystemMessage(content) { scrollToBottom(); } +async function executeAgentSlashCommand(client, command, commandArgs = '') { + const normalizedArgs = (commandArgs ?? '').trim(); + const message = normalizedArgs ? `/${command} ${normalizedArgs}` : `/${command}`; + const metadata = { + isCommand: true, + command, + ...(normalizedArgs ? { commandArgs: normalizedArgs } : {}), + }; + + const stream = client.stream('agent.send', { message, metadata }); + const done = await stream.result; + return done?.content ?? done?.text ?? ''; +} + async function handleSlashCommand(cmd, client) { switch (cmd.type) { case 'help': { @@ -381,7 +395,7 @@ async function handleSlashCommand(cmd, client) { '| `/compact` | Ask the agent to compact context |', '| `/usage` | Show token usage stats |', '| `/status` | Show system health |', - '| `/model` | Show current model info |', + '| `/model [tier|provider]` | Show or set model tier/provider |', '', 'Type `/` to see autocomplete suggestions.', ]; @@ -391,14 +405,9 @@ async function handleSlashCommand(cmd, client) { case 'reset': { try { - // Send reset command via metadata - const stream = client.stream('agent.send', { - message: '/reset', - metadata: { isCommand: true, command: 'reset' }, - }); - await stream.result; + const result = await executeAgentSlashCommand(client, 'reset'); _elements.messages.innerHTML = ''; - showSystemMessage('Session reset.'); + showSystemMessage(result || 'Session reset.'); } catch (err) { showSystemMessage(`Failed to reset: ${err.message}`); } @@ -406,33 +415,19 @@ async function handleSlashCommand(cmd, client) { } case 'compact': { - // Send as a regular message — the agent will interpret the request - showSystemMessage('Requesting context compaction...'); - return false; // Let it pass through as a normal message + try { + const result = await executeAgentSlashCommand(client, 'compact'); + showSystemMessage(result || 'Compaction requested.'); + } catch (err) { + showSystemMessage(`Failed to compact: ${err.message}`); + } + return true; } case 'usage': { try { - const result = await client.call('system.tokenUsage'); - const sessions = result.sessions ?? []; - if (sessions.length === 0) { - showSystemMessage('No usage data available.'); - } else { - const lines = ['**Token Usage**', '']; - let totalIn = 0, totalOut = 0, totalCalls = 0; - for (const s of sessions) { - totalIn += s.total?.inputTokens ?? 0; - totalOut += s.total?.outputTokens ?? 0; - totalCalls += s.total?.calls ?? 0; - } - lines.push(`**Input:** ${totalIn.toLocaleString()} tokens`); - lines.push(`**Output:** ${totalOut.toLocaleString()} tokens`); - lines.push(`**API Calls:** ${totalCalls}`); - if (sessions.length > 1) { - lines.push(`**Sessions:** ${sessions.length}`); - } - showSystemMessage(lines.join('\n')); - } + const result = await executeAgentSlashCommand(client, 'usage'); + showSystemMessage(result || 'No usage data available.'); } catch (err) { showSystemMessage(`Failed to fetch usage: ${err.message}`); } @@ -441,24 +436,8 @@ async function handleSlashCommand(cmd, client) { case 'status': { try { - const result = await client.call('system.health'); - const lines = [ - '**System Status**', - '', - `**Uptime:** ${result.uptime ?? 'unknown'}`, - `**Status:** ${result.status ?? 'unknown'}`, - ]; - if (result.channels) { - lines.push('', '**Channels:**'); - for (const ch of result.channels) { - const dot = ch.status === 'connected' ? '\\*' : '-'; - lines.push(` ${dot} ${ch.name}: ${ch.status}`); - } - } - if (result.model) { - lines.push('', `**Model:** ${result.model}`); - } - showSystemMessage(lines.join('\n')); + const result = await executeAgentSlashCommand(client, 'status'); + showSystemMessage(result || 'Status unavailable.'); } catch (err) { showSystemMessage(`Failed to fetch status: ${err.message}`); } @@ -467,9 +446,8 @@ async function handleSlashCommand(cmd, client) { case 'model': { try { - const result = await client.call('system.health'); - const model = result.model ?? result.config?.model ?? 'unknown'; - showSystemMessage(`**Current Model:** ${model}`); + const result = await executeAgentSlashCommand(client, 'model', cmd.args ?? ''); + showSystemMessage(result || 'Model info unavailable.'); } catch (err) { showSystemMessage(`Failed to fetch model info: ${err.message}`); }