feat(agents): enhance execution control with stop functionality and improved database handling

- Refactor database path handling in agents.rs to initialize paths earlier and make them available to all async tasks
- Add immediate session ID updates to the database when session IDs are extracted
- Implement stop/cancel functionality for running agent sessions in both AgentRunOutputViewer and AgentRunView
- Improve loading states and event listener management in AgentRunOutputViewer
- Add stop button UI controls with proper styling and positioning
- Enhance error handling and logging throughout the agent execution flow
This commit is contained in:
Mufeed VH
2025-07-04 20:27:12 +05:30
parent 85dce56e04
commit 19cf623d64
3 changed files with 259 additions and 62 deletions

View File

@@ -3,11 +3,12 @@ import { motion } from "framer-motion";
import {
ArrowLeft,
Copy,
ChevronDown,
ChevronDown,
Clock,
Hash,
DollarSign,
Bot
Bot,
StopCircle
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
@@ -115,14 +116,16 @@ export const AgentRunView: React.FC<AgentRunViewProps> = ({
const handleCopyAsMarkdown = async () => {
if (!run) return;
let markdown = `# Agent Execution: ${run.agent_name}\n\n`;
let markdown = `# Agent Run: ${run.agent_name}\n\n`;
markdown += `**Task:** ${run.task}\n`;
markdown += `**Model:** ${run.model === 'opus' ? 'Claude 4 Opus' : 'Claude 4 Sonnet'}\n`;
markdown += `**Date:** ${formatISOTimestamp(run.created_at)}\n`;
if (run.metrics?.duration_ms) markdown += `**Duration:** ${(run.metrics.duration_ms / 1000).toFixed(2)}s\n`;
if (run.metrics?.total_tokens) markdown += `**Total Tokens:** ${run.metrics.total_tokens}\n`;
if (run.metrics?.cost_usd) markdown += `**Cost:** $${run.metrics.cost_usd.toFixed(4)} USD\n`;
markdown += `\n---\n\n`;
markdown += `**Model:** ${run.model}\n`;
markdown += `**Status:** ${run.status}\n`;
if (run.metrics) {
markdown += `**Tokens:** ${run.metrics.total_tokens || 'N/A'}\n`;
markdown += `**Cost:** $${run.metrics.cost_usd?.toFixed(4) || 'N/A'}\n`;
}
markdown += `**Date:** ${new Date(run.created_at).toISOString()}\n\n`;
markdown += `---\n\n`;
for (const msg of messages) {
if (msg.type === "system" && msg.subtype === "init") {
@@ -170,6 +173,50 @@ export const AgentRunView: React.FC<AgentRunViewProps> = ({
setCopyPopoverOpen(false);
};
const handleStop = async () => {
if (!runId) {
console.error('[AgentRunView] No run ID available to stop');
return;
}
try {
// Call the API to kill the agent session
const success = await api.killAgentSession(runId);
if (success) {
console.log(`[AgentRunView] Successfully stopped agent session ${runId}`);
// Update the run status locally
if (run) {
setRun({ ...run, status: 'cancelled' });
}
// Add a message indicating execution was stopped
const stopMessage: ClaudeStreamMessage = {
type: "result",
subtype: "error",
is_error: true,
result: "Execution stopped by user",
duration_ms: 0,
usage: {
input_tokens: 0,
output_tokens: 0
}
};
setMessages(prev => [...prev, stopMessage]);
// Reload the run data after a short delay
setTimeout(() => {
loadRun();
}, 1000);
} else {
console.warn(`[AgentRunView] Failed to stop agent session ${runId} - it may have already finished`);
}
} catch (err) {
console.error('[AgentRunView] Failed to stop agent:', err);
}
};
const renderIcon = (iconName: string) => {
const Icon = AGENT_ICONS[iconName as keyof typeof AGENT_ICONS] || Bot;
return <Icon className="h-5 w-5" />;
@@ -220,42 +267,56 @@ export const AgentRunView: React.FC<AgentRunViewProps> = ({
</div>
</div>
<Popover
trigger={
<div className="flex items-center gap-2">
{run?.status === 'running' && (
<Button
variant="ghost"
size="sm"
className="flex items-center gap-2"
variant="ghost"
onClick={handleStop}
className="text-destructive hover:text-destructive"
>
<Copy className="h-4 w-4" />
Copy Output
<ChevronDown className="h-3 w-3" />
<StopCircle className="h-4 w-4 mr-1" />
Stop
</Button>
}
content={
<div className="w-44 p-1">
)}
<Popover
trigger={
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
onClick={handleCopyAsJsonl}
className="flex items-center gap-2"
>
Copy as JSONL
<Copy className="h-4 w-4" />
Copy Output
<ChevronDown className="h-3 w-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
onClick={handleCopyAsMarkdown}
>
Copy as Markdown
</Button>
</div>
}
open={copyPopoverOpen}
onOpenChange={setCopyPopoverOpen}
align="end"
/>
}
content={
<div className="w-44 p-1">
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
onClick={handleCopyAsJsonl}
>
Copy as JSONL
</Button>
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
onClick={handleCopyAsMarkdown}
>
Copy as Markdown
</Button>
</div>
}
open={copyPopoverOpen}
onOpenChange={setCopyPopoverOpen}
align="end"
/>
</div>
</motion.div>
{/* Run Details */}