diff --git a/apps/app/src/components/ui/log-viewer.tsx b/apps/app/src/components/ui/log-viewer.tsx
index d962a4fc..a926e2d9 100644
--- a/apps/app/src/components/ui/log-viewer.tsx
+++ b/apps/app/src/components/ui/log-viewer.tsx
@@ -22,6 +22,9 @@ import {
Layers,
X,
Filter,
+ Circle,
+ Play,
+ Loader2,
} from "lucide-react";
import { cn } from "@/lib/utils";
import {
@@ -111,6 +114,112 @@ const getToolCategoryColor = (category: ToolCategory | undefined): string => {
}
};
+/**
+ * Interface for parsed todo items from TodoWrite tool
+ */
+interface TodoItem {
+ content: string;
+ status: "pending" | "in_progress" | "completed";
+ activeForm?: string;
+}
+
+/**
+ * Parses TodoWrite JSON content and extracts todo items
+ */
+function parseTodoContent(content: string): TodoItem[] | null {
+ try {
+ // Find the JSON object in the content
+ const jsonMatch = content.match(/\{[\s\S]*"todos"[\s\S]*\}/);
+ if (!jsonMatch) return null;
+
+ const parsed = JSON.parse(jsonMatch[0]) as { todos?: TodoItem[] };
+ if (!parsed.todos || !Array.isArray(parsed.todos)) return null;
+
+ return parsed.todos;
+ } catch {
+ return null;
+ }
+}
+
+/**
+ * Renders a list of todo items with status icons and colors
+ */
+function TodoListRenderer({ todos }: { todos: TodoItem[] }) {
+ const getStatusIcon = (status: TodoItem["status"]) => {
+ switch (status) {
+ case "completed":
+ return ;
+ case "in_progress":
+ return ;
+ case "pending":
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const getStatusColor = (status: TodoItem["status"]) => {
+ switch (status) {
+ case "completed":
+ return "text-emerald-300 line-through opacity-70";
+ case "in_progress":
+ return "text-amber-300";
+ case "pending":
+ return "text-zinc-400";
+ default:
+ return "text-zinc-400";
+ }
+ };
+
+ const getStatusBadge = (status: TodoItem["status"]) => {
+ switch (status) {
+ case "completed":
+ return (
+
+ Done
+
+ );
+ case "in_progress":
+ return (
+
+ In Progress
+
+ );
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+ {todos.map((todo, index) => (
+
+
{getStatusIcon(todo.status)}
+
+
+ {todo.content}
+
+ {todo.status === "in_progress" && todo.activeForm && (
+
+ {todo.activeForm}
+
+ )}
+
+ {getStatusBadge(todo.status)}
+
+ ))}
+
+ );
+}
+
interface LogEntryItemProps {
entry: LogEntry;
isExpanded: boolean;
@@ -126,6 +235,13 @@ function LogEntryItem({ entry, isExpanded, onToggle }: LogEntryItemProps) {
const toolCategory = entry.metadata?.toolCategory;
const toolCategoryColors = isToolCall ? getToolCategoryColor(toolCategory) : "";
+ // Check if this is a TodoWrite entry and parse the todos
+ const isTodoWrite = entry.metadata?.toolName === "TodoWrite";
+ const parsedTodos = useMemo(() => {
+ if (!isTodoWrite) return null;
+ return parseTodoContent(entry.content);
+ }, [isTodoWrite, entry.content]);
+
// Get the appropriate icon based on entry type and tool category
const icon = isToolCall ? getToolCategoryIcon(toolCategory) : getLogIcon(entry.type);
@@ -256,26 +372,31 @@ function LogEntryItem({ entry, isExpanded, onToggle }: LogEntryItemProps) {
className="px-4 pb-3 pt-1"
data-testid={`log-entry-content-${entry.id}`}
>
-
- {formattedContent.map((part, index) => (
-
- {part.type === "json" ? (
-
- {part.content}
-
- ) : (
-
- {part.content}
-
- )}
-
- ))}
-
+ {/* Render TodoWrite entries with special formatting */}
+ {parsedTodos ? (
+
+ ) : (
+
+ {formattedContent.map((part, index) => (
+
+ {part.type === "json" ? (
+
+ {part.content}
+
+ ) : (
+
+ {part.content}
+
+ )}
+
+ ))}
+
+ )}
)}
diff --git a/apps/server/src/services/auto-mode-service.ts b/apps/server/src/services/auto-mode-service.ts
index 40c434c0..1ce206c9 100644
--- a/apps/server/src/services/auto-mode-service.ts
+++ b/apps/server/src/services/auto-mode-service.ts
@@ -549,13 +549,15 @@ Address the follow-up instructions above. Review the previous work and make the
}
// Use fullPrompt (already built above) with model and all images
+ // Pass previousContext so the history is preserved in the output file
await this.runAgent(
workDir,
featureId,
fullPrompt,
abortController,
allImagePaths.length > 0 ? allImagePaths : imagePaths,
- model
+ model,
+ previousContext || undefined
);
// Mark as waiting_approval for user review
@@ -1169,7 +1171,8 @@ This helps parse your summary correctly in the output logs.`;
prompt: string,
abortController: AbortController,
imagePaths?: string[],
- model?: string
+ model?: string,
+ previousContent?: string
): Promise {
// CI/CD Mock Mode: Return early with mock response when AUTOMAKER_MOCK_AGENT is set
// This prevents actual API calls during automated testing
@@ -1271,7 +1274,10 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
// Execute via provider
const stream = provider.executeQuery(options);
- let responseText = "";
+ // Initialize with previous content if this is a follow-up, with a separator
+ let responseText = previousContent
+ ? `${previousContent}\n\n---\n\n## Follow-up Session\n\n`
+ : "";
// Agent output goes to .automaker directory
// Note: We use the original projectPath here (from config), not workDir
// because workDir might be a worktree path