mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 20:23:36 +00:00
feat: add thorough verification process and enhance agent output modal
- Introduced a new markdown file outlining a mandatory 3-pass verification process for code completion, focusing on correctness, edge cases, and maintainability. - Updated the AgentInfoPanel to display a todo list for non-backlog features, ensuring users can see the agent's current tasks. - Enhanced the AgentOutputModal to support a summary view, extracting and displaying summary content from raw log output. - Improved the log parser to extract summaries from various formats, enhancing the overall user experience and information accessibility.
This commit is contained in:
@@ -39,55 +39,118 @@ export function formatModelName(model: string): string {
|
||||
return model.split('-').slice(1, 3).join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to extract a balanced JSON object from a string starting at a given position
|
||||
*/
|
||||
function extractJsonObject(str: string, startIdx: number): string | null {
|
||||
if (str[startIdx] !== '{') return null;
|
||||
|
||||
let depth = 0;
|
||||
let inString = false;
|
||||
let escapeNext = false;
|
||||
|
||||
for (let i = startIdx; i < str.length; i++) {
|
||||
const char = str[i];
|
||||
|
||||
if (escapeNext) {
|
||||
escapeNext = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '\\' && inString) {
|
||||
escapeNext = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '"' && !escapeNext) {
|
||||
inString = !inString;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inString) continue;
|
||||
|
||||
if (char === '{') depth++;
|
||||
else if (char === '}') {
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
return str.slice(startIdx, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts todos from the context content
|
||||
* Looks for TodoWrite tool calls in the format:
|
||||
* TodoWrite: [{"content": "...", "status": "..."}]
|
||||
* 🔧 Tool: TodoWrite
|
||||
* Input: {"todos": [{"content": "...", "status": "..."}]}
|
||||
*/
|
||||
function extractTodos(content: string): AgentTaskInfo['todos'] {
|
||||
const todos: AgentTaskInfo['todos'] = [];
|
||||
|
||||
// Look for TodoWrite tool inputs
|
||||
const todoMatches = content.matchAll(
|
||||
/TodoWrite.*?(?:"todos"\s*:\s*)?(\[[\s\S]*?\](?=\s*(?:\}|$|🔧|📋|⚡|✅|❌)))/g
|
||||
);
|
||||
// Find all occurrences of TodoWrite tool calls
|
||||
const todoWriteMarker = '🔧 Tool: TodoWrite';
|
||||
let searchStart = 0;
|
||||
|
||||
for (const match of todoMatches) {
|
||||
try {
|
||||
// Try to find JSON array in the match
|
||||
const jsonStr = match[1] || match[0];
|
||||
const arrayMatch = jsonStr.match(/\[[\s\S]*?\]/);
|
||||
if (arrayMatch) {
|
||||
const parsed = JSON.parse(arrayMatch[0]);
|
||||
if (Array.isArray(parsed)) {
|
||||
for (const item of parsed) {
|
||||
while (true) {
|
||||
const markerIdx = content.indexOf(todoWriteMarker, searchStart);
|
||||
if (markerIdx === -1) break;
|
||||
|
||||
// Look for "Input:" after the marker
|
||||
const inputIdx = content.indexOf('Input:', markerIdx);
|
||||
if (inputIdx === -1 || inputIdx > markerIdx + 100) {
|
||||
searchStart = markerIdx + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the start of the JSON object
|
||||
const jsonStart = content.indexOf('{', inputIdx);
|
||||
if (jsonStart === -1) {
|
||||
searchStart = markerIdx + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract the complete JSON object
|
||||
const jsonStr = extractJsonObject(content, jsonStart);
|
||||
if (jsonStr) {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonStr) as {
|
||||
todos?: Array<{ content: string; status: string }>;
|
||||
};
|
||||
if (parsed.todos && Array.isArray(parsed.todos)) {
|
||||
// Clear previous todos - we want the latest state
|
||||
todos.length = 0;
|
||||
for (const item of parsed.todos) {
|
||||
if (item.content && item.status) {
|
||||
// Check if this todo already exists (avoid duplicates)
|
||||
if (!todos.some((t) => t.content === item.content)) {
|
||||
todos.push({
|
||||
content: item.content,
|
||||
status: item.status,
|
||||
});
|
||||
}
|
||||
todos.push({
|
||||
content: item.content,
|
||||
status: item.status as 'pending' | 'in_progress' | 'completed',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
|
||||
searchStart = markerIdx + 1;
|
||||
}
|
||||
|
||||
// Also try to extract from markdown task lists
|
||||
const markdownTodos = content.matchAll(/- \[([ xX])\] (.+)/g);
|
||||
for (const match of markdownTodos) {
|
||||
const isCompleted = match[1].toLowerCase() === 'x';
|
||||
const content = match[2].trim();
|
||||
if (!todos.some((t) => t.content === content)) {
|
||||
todos.push({
|
||||
content,
|
||||
status: isCompleted ? 'completed' : 'pending',
|
||||
});
|
||||
// Also try to extract from markdown task lists as fallback
|
||||
if (todos.length === 0) {
|
||||
const markdownTodos = content.matchAll(/- \[([ xX])\] (.+)/g);
|
||||
for (const match of markdownTodos) {
|
||||
const isCompleted = match[1].toLowerCase() === 'x';
|
||||
const todoContent = match[2].trim();
|
||||
if (!todos.some((t) => t.content === todoContent)) {
|
||||
todos.push({
|
||||
content: todoContent,
|
||||
status: isCompleted ? 'completed' : 'pending',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -664,6 +664,53 @@ function mergeConsecutiveEntries(entries: LogEntry[]): LogEntry[] {
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts summary content from raw log output
|
||||
* Returns the summary text if found, or null if no summary exists
|
||||
*/
|
||||
export function extractSummary(rawOutput: string): string | null {
|
||||
if (!rawOutput || !rawOutput.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to find <summary> tags first (preferred format)
|
||||
const summaryTagMatch = rawOutput.match(/<summary>([\s\S]*?)<\/summary>/);
|
||||
if (summaryTagMatch) {
|
||||
return summaryTagMatch[1].trim();
|
||||
}
|
||||
|
||||
// Try to find markdown ## Summary section
|
||||
const summaryHeaderMatch = rawOutput.match(/^##\s+Summary\s*\n([\s\S]*?)(?=\n##\s+|$)/m);
|
||||
if (summaryHeaderMatch) {
|
||||
return summaryHeaderMatch[1].trim();
|
||||
}
|
||||
|
||||
// Try other summary formats (Feature, Changes, Implementation)
|
||||
const otherHeaderMatch = rawOutput.match(
|
||||
/^##\s+(Feature|Changes|Implementation)\s*\n([\s\S]*?)(?=\n##\s+|$)/m
|
||||
);
|
||||
if (otherHeaderMatch) {
|
||||
return `## ${otherHeaderMatch[1]}\n${otherHeaderMatch[2].trim()}`;
|
||||
}
|
||||
|
||||
// Try to find summary introduction lines
|
||||
const introMatch = rawOutput.match(
|
||||
/(^|\n)(All tasks completed[\s\S]*?)(?=\n🔧|\n📋|\n⚡|\n❌|$)/
|
||||
);
|
||||
if (introMatch) {
|
||||
return introMatch[2].trim();
|
||||
}
|
||||
|
||||
const completionMatch = rawOutput.match(
|
||||
/(^|\n)((I've|I have) (successfully |now )?(completed|finished|implemented)[\s\S]*?)(?=\n🔧|\n📋|\n⚡|\n❌|$)/
|
||||
);
|
||||
if (completionMatch) {
|
||||
return completionMatch[2].trim();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the color classes for a log entry type
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user