Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions apps/backend/agents/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,20 +395,18 @@ async def run_agent_session(

# Extract meaningful tool input for display
if inp:
if "pattern" in inp:
tool_input_display = f"pattern: {inp['pattern']}"
elif "file_path" in inp:
fp = inp["file_path"]
if len(fp) > 50:
fp = "..." + fp[-47:]
tool_input_display = fp
if "file_path" in inp:
tool_input_display = inp["file_path"]
elif "command" in inp:
cmd = inp["command"]
if len(cmd) > 50:
cmd = cmd[:47] + "..."
tool_input_display = cmd
tool_input_display = inp["command"]
elif "pattern" in inp:
tool_input_display = f"/{inp['pattern']}/"
elif "path" in inp:
tool_input_display = inp["path"]
elif "url" in inp:
tool_input_display = inp["url"]
elif "query" in inp:
tool_input_display = f'"{inp["query"]}"'

debug(
"session",
Expand Down
28 changes: 24 additions & 4 deletions apps/backend/qa/fixer.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,34 @@ async def run_qa_fixer_session(
if inp:
if "file_path" in inp:
fp = inp["file_path"]
if len(fp) > 50:
fp = "..." + fp[-47:]
if len(fp) > 80:
fp = "..." + fp[-77:]
tool_input_display = fp
elif "command" in inp:
cmd = inp["command"]
if len(cmd) > 50:
cmd = cmd[:47] + "..."
if len(cmd) > 80:
cmd = cmd[:77] + "..."
tool_input_display = cmd
elif "pattern" in inp:
pat = inp["pattern"]
if len(pat) > 60:
pat = pat[:57] + "..."
tool_input_display = f"/{pat}/"
elif "path" in inp:
p = inp["path"]
if len(p) > 80:
p = "..." + p[-77:]
tool_input_display = p
elif "url" in inp:
url = inp["url"]
if len(url) > 80:
url = url[:77] + "..."
tool_input_display = url
elif "query" in inp:
q = inp["query"]
if len(q) > 60:
q = q[:57] + "..."
tool_input_display = f'"{q}"'
Comment on lines 165 to +195
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | πŸ”΅ Trivial

🧩 Analysis chain

🏁 Script executed:

# First, let's check if these files exist and examine fixer.py around the mentioned lines
echo "=== Checking file_path handling in fixer.py (around line 170) ==="
sed -n '165,180p' apps/backend/qa/fixer.py | cat -n

echo -e "\n=== Checking file_path handling in reviewer.py (around line 228) ==="
sed -n '220,235p' apps/backend/qa/reviewer.py | cat -n

echo -e "\n=== Checking file_path handling in session.py (around line 400) ==="
sed -n '395,410p' apps/backend/agents/session.py | cat -n

Repository: AndyMik90/Auto-Claude

Length of output: 3025


🏁 Script executed:

# Search more broadly for all file_path handling with context
echo "=== All file_path assignments in fixer.py ==="
rg -n "file_path" apps/backend/qa/fixer.py -B 2 -A 2

echo -e "\n=== All file_path assignments in reviewer.py ==="
rg -n "file_path" apps/backend/qa/reviewer.py -B 2 -A 2

echo -e "\n=== All file_path assignments in session.py ==="
rg -n "file_path" apps/backend/agents/session.py -B 2 -A 2

Repository: AndyMik90/Auto-Claude

Length of output: 1195


Align file_path truncation behavior across agent modules.

fixer.py truncates file_path to 80 characters when displaying tool inputs, while reviewer.py and session.py show file_path verbatim. This inconsistency means the same tool input displays differently depending on which agent is running, creating a confusing user experience. Standardize the truncation approach across all three modules.

πŸ€– Prompt for AI Agents
In `@apps/backend/qa/fixer.py` around lines 167 - 197, The fixer module currently
truncates "file_path" before assigning tool_input_display, causing inconsistency
with reviewer.py and session.py; update the handling in the block that inspects
inp (the "file_path" branch) to stop truncating and instead set
tool_input_display = inp["file_path"] (i.e., show the full path verbatim) so the
behavior matches the other agent modules; keep the existing truncation rules for
other keys (e.g., "command", "pattern", "url", "query") unchanged.


debug(
"qa_fixer",
Expand Down
15 changes: 10 additions & 5 deletions apps/backend/qa/reviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,17 @@ async def run_qa_agent_session(
# Extract tool input for display
if inp:
if "file_path" in inp:
fp = inp["file_path"]
if len(fp) > 50:
fp = "..." + fp[-47:]
tool_input_display = fp
tool_input_display = inp["file_path"]
elif "command" in inp:
tool_input_display = inp["command"]
elif "pattern" in inp:
tool_input_display = f"pattern: {inp['pattern']}"
tool_input_display = f"/{inp['pattern']}/"
elif "path" in inp:
tool_input_display = inp["path"]
elif "url" in inp:
tool_input_display = inp["url"]
elif "query" in inp:
tool_input_display = f'"{inp["query"]}"'

debug(
"qa_reviewer",
Expand Down
6 changes: 5 additions & 1 deletion apps/backend/task_logger/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,11 @@ def tool_start(
)

if print_to_console:
print(f"\n[Tool: {tool_name}]", flush=True)
# Include input for live status display
if display_input:
print(f"\n[Tool: {tool_name}] {display_input}", flush=True)
else:
print(f"\n[Tool: {tool_name}]", flush=True)

def tool_end(
self,
Expand Down
28 changes: 28 additions & 0 deletions apps/frontend/src/main/agent/agent-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,34 @@ export class AgentEvents {
return { phase: 'failed', message: log.trim().substring(0, 200) };
}


// Live tool action detection - update message without changing phase
// Works in all phases to show real-time tool activity
const toolMatch = log.match(/\[(?:Tool|Fixer Tool|QA Tool):\s*(\w+)\]\s*(.*)?/i);
if (toolMatch) {
const toolName = toolMatch[1];
const details = toolMatch[2]?.trim().replace(/\r$/, '') || '';

// Build message with action verb and optional details
const toolVerbs: Record<string, string> = {
'Read': 'Reading',
'Edit': 'Editing',
'Write': 'Writing',
'Glob': 'Searching files',
'Grep': 'Searching',
'Bash': 'Running',
'Task': 'Running subagent',
'WebFetch': 'Fetching',
'WebSearch': 'Searching web'
};
const verb = toolVerbs[toolName] || ('Using ' + toolName);



const message = details ? (verb + ' ' + details) : (verb + '...');
return { phase: currentPhase, message };
}

return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ export function registerAgenteventsHandlers(
taskProjectId
);


const phaseToStatus: Record<string, TaskStatus | null> = {
idle: null,
planning: "in_progress",
Expand Down
78 changes: 76 additions & 2 deletions apps/frontend/src/renderer/components/TaskCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef, useCallback, memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Play, Square, Clock, Zap, Target, Shield, Gauge, Palette, FileCode, Bug, Wrench, Loader2, AlertTriangle, RotateCcw, Archive, GitPullRequest, MoreVertical } from 'lucide-react';
import { Play, Square, Clock, Zap, Target, Shield, Gauge, Palette, FileCode, Bug, Wrench, Loader2, AlertTriangle, RotateCcw, Archive, GitPullRequest, MoreVertical, FileText, Search, FolderSearch, Terminal, Pencil, Globe } from 'lucide-react';
import { Card, CardContent } from './ui/card';
import { Badge } from './ui/badge';
import { Button } from './ui/button';
Expand Down Expand Up @@ -44,6 +44,50 @@ const CategoryIcon: Record<TaskCategory, typeof Zap> = {
testing: FileCode
};

// Data-driven tool styles for live action status
// Order matters: more specific patterns must come before general ones (first match wins)
const TOOL_STYLES: Array<{
patterns: string[];
icon: typeof FileText;
color: string;
translationKey: string;
}> = [
{ patterns: ['reading'], icon: FileText, color: 'text-blue-500 bg-blue-500/10', translationKey: 'toolActions.reading' },
{ patterns: ['searching files', 'globbing'], icon: FolderSearch, color: 'text-amber-500 bg-amber-500/10', translationKey: 'toolActions.searchingFiles' },
{ patterns: ['searching web'], icon: Globe, color: 'text-indigo-500 bg-indigo-500/10', translationKey: 'toolActions.searchingWeb' },
{ patterns: ['searching'], icon: Search, color: 'text-green-500 bg-green-500/10', translationKey: 'toolActions.searching' },
{ patterns: ['fetching'], icon: Globe, color: 'text-indigo-500 bg-indigo-500/10', translationKey: 'toolActions.fetching' },
{ patterns: ['editing'], icon: Pencil, color: 'text-purple-500 bg-purple-500/10', translationKey: 'toolActions.editing' },
{ patterns: ['writing'], icon: FileCode, color: 'text-cyan-500 bg-cyan-500/10', translationKey: 'toolActions.writing' },
{ patterns: ['running', 'executing'], icon: Terminal, color: 'text-orange-500 bg-orange-500/10', translationKey: 'toolActions.running' },
{ patterns: ['using'], icon: Wrench, color: 'text-slate-500 bg-slate-500/10', translationKey: 'toolActions.using' },
];

// Helper to detect tool type from execution message and return styling with translation key
function getToolStyleFromMessage(message: string): {
icon: typeof FileText;
color: string;
translationKey: string;
details: string;
} | null {
const lowerMessage = message.toLowerCase();
const match = TOOL_STYLES.find(style =>
style.patterns.some(pattern => lowerMessage.startsWith(pattern))
);
if (!match) return null;

// Extract details by finding which pattern matched and removing it
const matchedPattern = match.patterns.find(p => lowerMessage.startsWith(p)) || '';
const details = message.slice(matchedPattern.length).trim() || '...';

return {
icon: match.icon,
color: match.color,
translationKey: match.translationKey,
details
};
}

interface TaskCardProps {
task: Task;
onClick: () => void;
Expand All @@ -70,6 +114,7 @@ function taskCardPropsAreEqual(prevProps: TaskCardProps, nextProps: TaskCardProp
prevTask.reviewReason === nextTask.reviewReason &&
prevTask.executionProgress?.phase === nextTask.executionProgress?.phase &&
prevTask.executionProgress?.phaseProgress === nextTask.executionProgress?.phaseProgress &&
prevTask.executionProgress?.message === nextTask.executionProgress?.message &&
prevTask.subtasks.length === nextTask.subtasks.length &&
prevTask.metadata?.category === nextTask.metadata?.category &&
prevTask.metadata?.complexity === nextTask.metadata?.complexity &&
Expand Down Expand Up @@ -104,7 +149,7 @@ export const TaskCard = memo(function TaskCard({ task, onClick, onStatusChange }
interval: null
});

const isRunning = task.status === 'in_progress';
const isRunning = task.status === 'in_progress' || task.status === 'ai_review';
const executionPhase = task.executionProgress?.phase;
const hasActiveExecution = executionPhase && executionPhase !== 'idle' && executionPhase !== 'complete' && executionPhase !== 'failed';

Expand Down Expand Up @@ -137,6 +182,22 @@ export const TaskCard = memo(function TaskCard({ task, onClick, onStatusChange }
));
}, [task.status, onStatusChange, t]);

// Memoize live action status to avoid recreating on every render
const liveActionStatus = useMemo(() => {
const message = task.executionProgress?.message;
const phase = task.executionProgress?.phase;
// Don't show live status for terminal phases (complete/failed)
if (!message || phase === 'complete' || phase === 'failed') return null;
const toolStyle = getToolStyleFromMessage(message);
return {
icon: toolStyle?.icon ?? Loader2,
colorClass: toolStyle?.color ?? 'text-muted-foreground bg-muted/50',
translationKey: toolStyle?.translationKey ?? 'toolActions.default',
details: toolStyle?.details ?? message,
message, // Keep original for tooltip
};
}, [task.executionProgress?.message, task.executionProgress?.phase]);

// Memoized stuck check function to avoid recreating on every render
const performStuckCheck = useCallback(() => {
// IMPORTANT: If the execution phase is 'complete' or 'failed', the task is NOT stuck.
Expand Down Expand Up @@ -480,6 +541,19 @@ export const TaskCard = memo(function TaskCard({ task, onClick, onStatusChange }
</div>
)}

{/* Live action status - shows current tool activity */}
{isRunning && !isStuck && liveActionStatus && (
<div
className={cn("mt-2 flex items-start gap-1.5 rounded-md px-2 py-1.5 text-xs", liveActionStatus.colorClass)}
title={liveActionStatus.message}
>
<liveActionStatus.icon className="h-3.5 w-3.5 shrink-0 animate-pulse mt-0.5" />
<span className="line-clamp-3 break-all leading-tight">
{t(liveActionStatus.translationKey, { details: liveActionStatus.details })}
</span>
</div>
)}

{/* Footer */}
<div className="mt-4 flex items-center justify-between">
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
Expand Down
10 changes: 10 additions & 0 deletions apps/frontend/src/renderer/hooks/useIpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ function queueUpdate(taskId: string, update: BatchedUpdate): void {
}
}

// Live tool action messages bypass batching - apply immediately for real-time feedback
// These are short-lived status updates (Reading file..., Editing..., etc.)
if (update.progress?.message && storeActionsRef) {
const isToolActionMessage = /^(Reading|Editing|Writing|Searching|Running|Using)/.test(update.progress.message);
if (isToolActionMessage) {
storeActionsRef.updateExecutionProgress(taskId, update.progress);
return;
}
}

// For logs, accumulate rather than replace
let mergedLogs = existing.logs;
if (update.logs) {
Expand Down
4 changes: 4 additions & 0 deletions apps/frontend/src/renderer/stores/task-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,10 @@ export const useTaskStore = create<TaskState>((set, get) => ({
// This prevents unnecessary re-renders from the memo comparator
const phaseChanged = progress.phase && progress.phase !== existingProgress.phase;

// DEBUG: Log message updates
if (progress.message) {
console.log('[Store] Updating executionProgress.message:', { taskId, message: progress.message, phase: progress.phase });
}
return {
...t,
executionProgress: {
Expand Down
12 changes: 12 additions & 0 deletions apps/frontend/src/shared/i18n/locales/en/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -240,5 +240,17 @@
},
"subtasks": {
"untitled": "Untitled subtask"
},
"toolActions": {
"reading": "Reading {{details}}",
"searchingFiles": "Searching files {{details}}",
"searchingWeb": "Searching web {{details}}",
"searching": "Searching {{details}}",
"fetching": "Fetching {{details}}",
"editing": "Editing {{details}}",
"writing": "Writing {{details}}",
"running": "Running {{details}}",
"using": "Using {{details}}",
"default": "{{details}}"
}
}
12 changes: 12 additions & 0 deletions apps/frontend/src/shared/i18n/locales/fr/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -240,5 +240,17 @@
},
"subtasks": {
"untitled": "Sous-tΓ’che sans titre"
},
"toolActions": {
"reading": "Lecture {{details}}",
"searchingFiles": "Recherche fichiers {{details}}",
"searchingWeb": "Recherche web {{details}}",
"searching": "Recherche {{details}}",
"fetching": "TΓ©lΓ©chargement {{details}}",
"editing": "Γ‰dition {{details}}",
"writing": "Γ‰criture {{details}}",
"running": "ExΓ©cution {{details}}",
"using": "Utilisation {{details}}",
"default": "{{details}}"
}
}
Loading