diff --git a/vibn-frontend/app/api/chat/route.ts b/vibn-frontend/app/api/chat/route.ts index d4b4f67..b06a9a3 100644 --- a/vibn-frontend/app/api/chat/route.ts +++ b/vibn-frontend/app/api/chat/route.ts @@ -628,6 +628,30 @@ function extractPreviewUrl(messages: ChatMessage[]): string | undefined { return undefined; } + +function summarizeForUI(raw: string): string { + try { + const p = JSON.parse(raw); + if (p && typeof p === "object") { + const clone = { ...p }; + // Strip massive payload fields so the UI gets intact JSON + if (clone.result && typeof clone.result === 'object') { + if (clone.result.log) clone.result.log = "..."; + if (clone.result.content) clone.result.content = "..."; + if (clone.result.listing) clone.result.listing = "..."; + } + if (typeof clone.stdout === 'string' && clone.stdout.length > 200) { + clone.stdout = clone.stdout.slice(0, 200) + "..."; + } + if (typeof clone.stderr === 'string' && clone.stderr.length > 200) { + clone.stderr = clone.stderr.slice(0, 200) + "..."; + } + return JSON.stringify(clone); + } + } catch {} + return raw.slice(0, 500); +} + export async function POST(request: Request) { await ensureChatTables(); @@ -1109,7 +1133,7 @@ export async function POST(request: Request) { emit({ type: "tool_result", name: tc.name, - result: result.slice(0, 500), + result: summarizeForUI(result), }); messages.push({ role: "tool", @@ -1322,7 +1346,7 @@ export async function POST(request: Request) { emit({ type: "tool_result", name: tc.name, - result: result.slice(0, 500), + result: summarizeForUI(result), }); messages.push({ diff --git a/vibn-frontend/components/vibn-chat/chat-panel.tsx b/vibn-frontend/components/vibn-chat/chat-panel.tsx index c0eb0ae..81ee9d2 100644 --- a/vibn-frontend/components/vibn-chat/chat-panel.tsx +++ b/vibn-frontend/components/vibn-chat/chat-panel.tsx @@ -284,11 +284,14 @@ function summarizeToolResult(result?: string): { } // Plain-text heuristics + // We explicitly ignore 'error' and 'exception' here because tools like dev_server_logs + // or browser_console legitimately return stack traces when working correctly. + // A raw string with 'error' inside it shouldn't auto-fail the tool execution pill. const lower = raw.toLowerCase(); if ( - /(econnrefused|enoent|error|failed|traceback|exception|not found|permission denied|cannot)/.test( + /(econnrefused|enoent|permission denied|command not found)/.test( lower, - ) + ) && !raw.includes("dev_server_logs") && !raw.includes("browser_console") ) { return { ok: false, label: `Failed — ${firstLine(raw)}` }; } @@ -1548,12 +1551,15 @@ export function ChatPanel({ } } catch { // 2. If it's a raw string (like a bash crash), scan for fatal keywords + // We skip this check for log-reading tools since they legitimately contain errors. const lower = ev.result.toLowerCase(); if ( - lower.includes("error") || - lower.includes("failed") || - lower.includes("unexpected") || - lower.includes("not found") + !ev.name?.includes("logs") && + !ev.name?.includes("console") && + (lower.includes("econnrefused") || + lower.includes("enoent") || + lower.includes("permission denied") || + lower.includes("command not found")) ) { isToolErr = true; }