feat: enhance log parsing to support <summary> tags for structured output

- Introduced support for <summary> tags in log entries, allowing for better organization and parsing of summary content.
- Updated the detectEntryType function to recognize <summary> tags as a preferred format for summaries.
- Implemented summary accumulation logic to handle content between <summary> and </summary> tags.
- Modified the prompt in auto-mode service to instruct users to wrap their summaries in <summary> tags for consistency in log output.
This commit is contained in:
Kacper
2025-12-17 14:33:13 +01:00
parent fe56ba133e
commit 2a782392bc
3 changed files with 58 additions and 7 deletions

View File

@@ -156,6 +156,13 @@ function LogEntryItem({ entry, isExpanded, onToggle }: LogEntryItemProps) {
content = content.trim(); content = content.trim();
} }
// For summary entries, remove the <summary> and </summary> tags
if (entry.title === "Summary") {
content = content.replace(/^<summary>\s*/i, "");
content = content.replace(/\s*<\/summary>\s*$/i, "");
content = content.trim();
}
// Try to find and format JSON blocks // Try to find and format JSON blocks
const jsonRegex = /(\{[\s\S]*?\}|\[[\s\S]*?\])/g; const jsonRegex = /(\{[\s\S]*?\}|\[[\s\S]*?\])/g;
let lastIndex = 0; let lastIndex = 0;
@@ -192,7 +199,7 @@ function LogEntryItem({ entry, isExpanded, onToggle }: LogEntryItemProps) {
} }
return parts.length > 0 ? parts : [{ type: "text" as const, content }]; 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 // Get colors - use tool category colors for tool_call entries
const colorParts = toolCategoryColors.split(" "); const colorParts = toolCategoryColors.split(" ");

View File

@@ -126,7 +126,9 @@ function detectEntryType(content: string): LogEntryType {
trimmed.startsWith("✅") || trimmed.startsWith("✅") ||
trimmed.toLowerCase().includes("success") || trimmed.toLowerCase().includes("success") ||
trimmed.toLowerCase().includes("completed") || trimmed.toLowerCase().includes("completed") ||
// Markdown summary headers // Summary tags (preferred format from agent)
trimmed.startsWith("<summary>") ||
// Markdown summary headers (fallback)
trimmed.match(/^##\s+(Summary|Feature|Changes|Implementation)/i) || trimmed.match(/^##\s+(Summary|Feature|Changes|Implementation)/i) ||
trimmed.match(/^(I've|I have) (successfully |now )?(completed|finished|implemented)/i) trimmed.match(/^(I've|I have) (successfully |now )?(completed|finished|implemented)/i)
) { ) {
@@ -138,10 +140,11 @@ function detectEntryType(content: string): LogEntryType {
return "warning"; return "warning";
} }
// Thinking/Preparation info // Thinking/Preparation info (be specific to avoid matching summary content)
if ( if (
trimmed.toLowerCase().includes("ultrathink") || 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 cost") ||
trimmed.toLowerCase().includes("estimated time") || trimmed.toLowerCase().includes("estimated time") ||
trimmed.toLowerCase().includes("budget tokens") || trimmed.toLowerCase().includes("budget tokens") ||
@@ -346,6 +349,9 @@ function generateTitle(type: LogEntryType, content: string): string {
return "Error"; return "Error";
case "success": { case "success": {
// Check if it's a summary section // Check if it's a summary section
if (content.startsWith("<summary>") || content.includes("<summary>")) {
return "Summary";
}
if (content.match(/^##\s+(Summary|Feature|Changes|Implementation)/i)) { if (content.match(/^##\s+(Summary|Feature|Changes|Implementation)/i)) {
return "Summary"; return "Summary";
} }
@@ -420,6 +426,9 @@ export function parseLogOutput(rawOutput: string): LogEntry[] {
let jsonBraceDepth = 0; let jsonBraceDepth = 0;
let jsonBracketDepth = 0; let jsonBracketDepth = 0;
// Summary tag accumulation state
let inSummaryAccumulation = false;
const finalizeEntry = () => { const finalizeEntry = () => {
if (currentEntry && currentContent.length > 0) { if (currentEntry && currentContent.length > 0) {
currentEntry.content = currentContent.join("\n").trim(); currentEntry.content = currentContent.join("\n").trim();
@@ -451,6 +460,7 @@ export function parseLogOutput(rawOutput: string): LogEntry[] {
inJsonAccumulation = false; inJsonAccumulation = false;
jsonBraceDepth = 0; jsonBraceDepth = 0;
jsonBracketDepth = 0; jsonBracketDepth = 0;
inSummaryAccumulation = false;
}; };
let lineIndex = 0; let lineIndex = 0;
@@ -480,6 +490,18 @@ export function parseLogOutput(rawOutput: string): LogEntry[] {
continue; continue;
} }
// If we're in summary accumulation mode, keep accumulating until </summary>
if (inSummaryAccumulation) {
currentContent.push(line);
// Summary is complete when we see closing tag
if (trimmedLine.includes("</summary>")) {
inSummaryAccumulation = false;
// Don't finalize here - let normal flow handle it
}
lineIndex++;
continue;
}
// Detect if this line starts a new entry // Detect if this line starts a new entry
const lineType = detectEntryType(trimmedLine); const lineType = detectEntryType(trimmedLine);
const isNewEntry = const isNewEntry =
@@ -498,8 +520,10 @@ export function parseLogOutput(rawOutput: string): LogEntry[] {
trimmedLine.match(/\[ERROR\]/i) || trimmedLine.match(/\[ERROR\]/i) ||
trimmedLine.match(/\[Status\]/i) || trimmedLine.match(/\[Status\]/i) ||
trimmedLine.toLowerCase().includes("ultrathink preparation") || trimmedLine.toLowerCase().includes("ultrathink preparation") ||
trimmedLine.toLowerCase().includes("thinking level") || trimmedLine.match(/thinking level[:\s]*(low|medium|high|none|\d)/i) ||
// Agent summary sections (markdown headers after tool calls) // Summary tags (preferred format from agent)
trimmedLine.startsWith("<summary>") ||
// Agent summary sections (markdown headers - fallback)
trimmedLine.match(/^##\s+(Summary|Feature|Changes|Implementation)/i) || trimmedLine.match(/^##\s+(Summary|Feature|Changes|Implementation)/i) ||
// Summary introduction lines // Summary introduction lines
trimmedLine.match(/^All tasks completed/i) || trimmedLine.match(/^All tasks completed/i) ||
@@ -526,6 +550,11 @@ export function parseLogOutput(rawOutput: string): LogEntry[] {
}, },
}; };
currentContent.push(trimmedLine); currentContent.push(trimmedLine);
// If this is a <summary> tag, start summary accumulation mode
if (trimmedLine.startsWith("<summary>") && !trimmedLine.includes("</summary>")) {
inSummaryAccumulation = true;
}
} else if (isInputLine && currentEntry) { } else if (isInputLine && currentEntry) {
// Start JSON accumulation mode // Start JSON accumulation mode
currentContent.push(trimmedLine); currentContent.push(trimmedLine);

View File

@@ -1143,7 +1143,22 @@ Implement this feature by:
4. Add or update tests as needed 4. Add or update tests as needed
5. Ensure the code follows existing patterns and conventions 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 <summary> tags like this:
<summary>
## Summary: [Feature Title]
### Changes Implemented
- [List of changes made]
### Files Modified
- [List of files]
### Notes for Developer
- [Any important notes]
</summary>
This helps parse your summary correctly in the output logs.`;
return prompt; return prompt;
} }