diff --git a/apps/app/src/components/ui/log-viewer.tsx b/apps/app/src/components/ui/log-viewer.tsx index 059ebc78..d962a4fc 100644 --- a/apps/app/src/components/ui/log-viewer.tsx +++ b/apps/app/src/components/ui/log-viewer.tsx @@ -156,6 +156,13 @@ function LogEntryItem({ entry, isExpanded, onToggle }: LogEntryItemProps) { content = content.trim(); } + // For summary entries, remove the and tags + if (entry.title === "Summary") { + content = content.replace(/^\s*/i, ""); + content = content.replace(/\s*<\/summary>\s*$/i, ""); + content = content.trim(); + } + // Try to find and format JSON blocks const jsonRegex = /(\{[\s\S]*?\}|\[[\s\S]*?\])/g; let lastIndex = 0; @@ -192,7 +199,7 @@ function LogEntryItem({ entry, isExpanded, onToggle }: LogEntryItemProps) { } return parts.length > 0 ? parts : [{ type: "text" as const, content }]; - }, [entry.content, isToolCall]); + }, [entry.content, entry.title, isToolCall]); // Get colors - use tool category colors for tool_call entries const colorParts = toolCategoryColors.split(" "); diff --git a/apps/app/src/lib/log-parser.ts b/apps/app/src/lib/log-parser.ts index 725b9b24..85fa96c6 100644 --- a/apps/app/src/lib/log-parser.ts +++ b/apps/app/src/lib/log-parser.ts @@ -126,7 +126,9 @@ function detectEntryType(content: string): LogEntryType { trimmed.startsWith("✅") || trimmed.toLowerCase().includes("success") || trimmed.toLowerCase().includes("completed") || - // Markdown summary headers + // Summary tags (preferred format from agent) + trimmed.startsWith("") || + // Markdown summary headers (fallback) trimmed.match(/^##\s+(Summary|Feature|Changes|Implementation)/i) || trimmed.match(/^(I've|I have) (successfully |now )?(completed|finished|implemented)/i) ) { @@ -138,10 +140,11 @@ function detectEntryType(content: string): LogEntryType { return "warning"; } - // Thinking/Preparation info + // Thinking/Preparation info (be specific to avoid matching summary content) if ( trimmed.toLowerCase().includes("ultrathink") || - trimmed.toLowerCase().includes("thinking level") || + trimmed.match(/thinking level[:\s]*(low|medium|high|none|\d)/i) || + trimmed.match(/^thinking level\s*$/i) || trimmed.toLowerCase().includes("estimated cost") || trimmed.toLowerCase().includes("estimated time") || trimmed.toLowerCase().includes("budget tokens") || @@ -346,6 +349,9 @@ function generateTitle(type: LogEntryType, content: string): string { return "Error"; case "success": { // Check if it's a summary section + if (content.startsWith("") || content.includes("")) { + return "Summary"; + } if (content.match(/^##\s+(Summary|Feature|Changes|Implementation)/i)) { return "Summary"; } @@ -420,6 +426,9 @@ export function parseLogOutput(rawOutput: string): LogEntry[] { let jsonBraceDepth = 0; let jsonBracketDepth = 0; + // Summary tag accumulation state + let inSummaryAccumulation = false; + const finalizeEntry = () => { if (currentEntry && currentContent.length > 0) { currentEntry.content = currentContent.join("\n").trim(); @@ -451,6 +460,7 @@ export function parseLogOutput(rawOutput: string): LogEntry[] { inJsonAccumulation = false; jsonBraceDepth = 0; jsonBracketDepth = 0; + inSummaryAccumulation = false; }; let lineIndex = 0; @@ -480,6 +490,18 @@ export function parseLogOutput(rawOutput: string): LogEntry[] { continue; } + // If we're in summary accumulation mode, keep accumulating until + if (inSummaryAccumulation) { + currentContent.push(line); + // Summary is complete when we see closing tag + if (trimmedLine.includes("")) { + inSummaryAccumulation = false; + // Don't finalize here - let normal flow handle it + } + lineIndex++; + continue; + } + // Detect if this line starts a new entry const lineType = detectEntryType(trimmedLine); const isNewEntry = @@ -498,8 +520,10 @@ export function parseLogOutput(rawOutput: string): LogEntry[] { trimmedLine.match(/\[ERROR\]/i) || trimmedLine.match(/\[Status\]/i) || trimmedLine.toLowerCase().includes("ultrathink preparation") || - trimmedLine.toLowerCase().includes("thinking level") || - // Agent summary sections (markdown headers after tool calls) + trimmedLine.match(/thinking level[:\s]*(low|medium|high|none|\d)/i) || + // Summary tags (preferred format from agent) + trimmedLine.startsWith("") || + // Agent summary sections (markdown headers - fallback) trimmedLine.match(/^##\s+(Summary|Feature|Changes|Implementation)/i) || // Summary introduction lines trimmedLine.match(/^All tasks completed/i) || @@ -526,6 +550,11 @@ export function parseLogOutput(rawOutput: string): LogEntry[] { }, }; currentContent.push(trimmedLine); + + // If this is a tag, start summary accumulation mode + if (trimmedLine.startsWith("") && !trimmedLine.includes("")) { + inSummaryAccumulation = true; + } } else if (isInputLine && currentEntry) { // Start JSON accumulation mode currentContent.push(trimmedLine); diff --git a/apps/server/src/services/auto-mode-service.ts b/apps/server/src/services/auto-mode-service.ts index 1f81ff0c..40c434c0 100644 --- a/apps/server/src/services/auto-mode-service.ts +++ b/apps/server/src/services/auto-mode-service.ts @@ -1143,7 +1143,22 @@ Implement this feature by: 4. Add or update tests as needed 5. Ensure the code follows existing patterns and conventions -When done, summarize what you implemented and any notes for the developer.`; +When done, wrap your final summary in tags like this: + + +## Summary: [Feature Title] + +### Changes Implemented +- [List of changes made] + +### Files Modified +- [List of files] + +### Notes for Developer +- [Any important notes] + + +This helps parse your summary correctly in the output logs.`; return prompt; }