Spaces:
Running
Running
Henri Bonamy
commited on
Commit
·
4485208
1
Parent(s):
29d492e
plan tool bottom right
Browse files
agent/tools/jobs_tool.py
CHANGED
|
@@ -367,17 +367,48 @@ class HfJobsTool:
|
|
| 367 |
|
| 368 |
for _ in range(max_retries):
|
| 369 |
try:
|
| 370 |
-
#
|
| 371 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
|
| 373 |
-
|
| 374 |
-
|
| 375 |
print("\t" + log_line)
|
| 376 |
if self.log_callback:
|
| 377 |
await self.log_callback(log_line)
|
| 378 |
all_logs.append(log_line)
|
| 379 |
|
| 380 |
-
# If we get here, streaming completed normally
|
|
|
|
|
|
|
| 381 |
break
|
| 382 |
|
| 383 |
except (
|
|
|
|
| 367 |
|
| 368 |
for _ in range(max_retries):
|
| 369 |
try:
|
| 370 |
+
# Use a queue to bridge sync generator to async consumer
|
| 371 |
+
queue = asyncio.Queue()
|
| 372 |
+
loop = asyncio.get_running_loop()
|
| 373 |
+
|
| 374 |
+
def log_producer():
|
| 375 |
+
try:
|
| 376 |
+
# fetch_job_logs is a blocking sync generator
|
| 377 |
+
logs_gen = self.api.fetch_job_logs(job_id=job_id, namespace=namespace)
|
| 378 |
+
for line in logs_gen:
|
| 379 |
+
# Push line to queue thread-safely
|
| 380 |
+
loop.call_soon_threadsafe(queue.put_nowait, line)
|
| 381 |
+
# Signal EOF
|
| 382 |
+
loop.call_soon_threadsafe(queue.put_nowait, None)
|
| 383 |
+
except Exception as e:
|
| 384 |
+
# Signal error
|
| 385 |
+
loop.call_soon_threadsafe(queue.put_nowait, e)
|
| 386 |
+
|
| 387 |
+
# Start producer in a background thread so it doesn't block the event loop
|
| 388 |
+
producer_future = loop.run_in_executor(None, log_producer)
|
| 389 |
+
|
| 390 |
+
# Consume logs from the queue as they arrive
|
| 391 |
+
while True:
|
| 392 |
+
item = await queue.get()
|
| 393 |
+
|
| 394 |
+
# EOF sentinel
|
| 395 |
+
if item is None:
|
| 396 |
+
break
|
| 397 |
+
|
| 398 |
+
# Error occurred in producer
|
| 399 |
+
if isinstance(item, Exception):
|
| 400 |
+
raise item
|
| 401 |
|
| 402 |
+
# Process log line
|
| 403 |
+
log_line = item
|
| 404 |
print("\t" + log_line)
|
| 405 |
if self.log_callback:
|
| 406 |
await self.log_callback(log_line)
|
| 407 |
all_logs.append(log_line)
|
| 408 |
|
| 409 |
+
# If we get here, streaming completed normally (EOF received)
|
| 410 |
+
# Wait for thread to cleanup (should be done)
|
| 411 |
+
await producer_future
|
| 412 |
break
|
| 413 |
|
| 414 |
except (
|
agent/tools/plan_tool.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
from typing import Any, Dict, List
|
| 2 |
|
|
|
|
| 3 |
from agent.utils.terminal_display import format_plan_tool_output
|
| 4 |
|
| 5 |
from .types import ToolResult
|
|
@@ -11,8 +12,8 @@ _current_plan: List[Dict[str, str]] = []
|
|
| 11 |
class PlanTool:
|
| 12 |
"""Tool for managing a list of todos with status tracking."""
|
| 13 |
|
| 14 |
-
def __init__(self):
|
| 15 |
-
|
| 16 |
|
| 17 |
async def execute(self, params: Dict[str, Any]) -> ToolResult:
|
| 18 |
"""
|
|
@@ -56,6 +57,15 @@ class PlanTool:
|
|
| 56 |
# Store the raw todos structure in memory
|
| 57 |
_current_plan = todos
|
| 58 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
# Format only for display using terminal_display utility
|
| 60 |
formatted_output = format_plan_tool_output(todos)
|
| 61 |
|
|
@@ -120,7 +130,9 @@ PLAN_TOOL_SPEC = {
|
|
| 120 |
}
|
| 121 |
|
| 122 |
|
| 123 |
-
async def plan_tool_handler(
|
| 124 |
-
|
|
|
|
|
|
|
| 125 |
result = await tool.execute(arguments)
|
| 126 |
return result["formatted"], not result.get("isError", False)
|
|
|
|
| 1 |
from typing import Any, Dict, List
|
| 2 |
|
| 3 |
+
from agent.core.session import Event
|
| 4 |
from agent.utils.terminal_display import format_plan_tool_output
|
| 5 |
|
| 6 |
from .types import ToolResult
|
|
|
|
| 12 |
class PlanTool:
|
| 13 |
"""Tool for managing a list of todos with status tracking."""
|
| 14 |
|
| 15 |
+
def __init__(self, session: Any = None):
|
| 16 |
+
self.session = session
|
| 17 |
|
| 18 |
async def execute(self, params: Dict[str, Any]) -> ToolResult:
|
| 19 |
"""
|
|
|
|
| 57 |
# Store the raw todos structure in memory
|
| 58 |
_current_plan = todos
|
| 59 |
|
| 60 |
+
# Emit plan update event if session is available
|
| 61 |
+
if self.session:
|
| 62 |
+
await self.session.send_event(
|
| 63 |
+
Event(
|
| 64 |
+
event_type="plan_update",
|
| 65 |
+
data={"plan": todos},
|
| 66 |
+
)
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
# Format only for display using terminal_display utility
|
| 70 |
formatted_output = format_plan_tool_output(todos)
|
| 71 |
|
|
|
|
| 130 |
}
|
| 131 |
|
| 132 |
|
| 133 |
+
async def plan_tool_handler(
|
| 134 |
+
arguments: Dict[str, Any], session: Any = None
|
| 135 |
+
) -> tuple[str, bool]:
|
| 136 |
+
tool = PlanTool(session=session)
|
| 137 |
result = await tool.execute(arguments)
|
| 138 |
return result["formatted"], not result.get("isError", False)
|
frontend/src/components/CodePanel/CodePanel.tsx
CHANGED
|
@@ -1,12 +1,15 @@
|
|
| 1 |
-
import { Box, Typography, IconButton } from '@mui/material';
|
| 2 |
import CloseIcon from '@mui/icons-material/Close';
|
|
|
|
|
|
|
|
|
|
| 3 |
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
| 4 |
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
| 5 |
import { useAgentStore } from '@/store/agentStore';
|
| 6 |
import { useLayoutStore } from '@/store/layoutStore';
|
| 7 |
|
| 8 |
export default function CodePanel() {
|
| 9 |
-
const { panelContent } = useAgentStore();
|
| 10 |
const { setRightPanelOpen } = useLayoutStore();
|
| 11 |
|
| 12 |
return (
|
|
@@ -28,64 +31,107 @@ export default function CodePanel() {
|
|
| 28 |
</IconButton>
|
| 29 |
</Box>
|
| 30 |
|
| 31 |
-
{
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
height: '100%',
|
| 50 |
-
overflow: 'auto',
|
| 51 |
-
}}
|
| 52 |
-
>
|
| 53 |
-
{panelContent.content ? (
|
| 54 |
-
panelContent.language === 'python' ? (
|
| 55 |
-
<SyntaxHighlighter
|
| 56 |
-
language="python"
|
| 57 |
-
style={vscDarkPlus}
|
| 58 |
-
customStyle={{
|
| 59 |
-
margin: 0,
|
| 60 |
-
padding: 0,
|
| 61 |
-
background: 'transparent',
|
| 62 |
fontSize: '13px',
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
) : (
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
</Typography>
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
</Box>
|
| 90 |
)}
|
| 91 |
</Box>
|
|
|
|
| 1 |
+
import { Box, Typography, IconButton, Checkbox } from '@mui/material';
|
| 2 |
import CloseIcon from '@mui/icons-material/Close';
|
| 3 |
+
import RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked';
|
| 4 |
+
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
| 5 |
+
import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline';
|
| 6 |
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
| 7 |
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
| 8 |
import { useAgentStore } from '@/store/agentStore';
|
| 9 |
import { useLayoutStore } from '@/store/layoutStore';
|
| 10 |
|
| 11 |
export default function CodePanel() {
|
| 12 |
+
const { panelContent, plan } = useAgentStore();
|
| 13 |
const { setRightPanelOpen } = useLayoutStore();
|
| 14 |
|
| 15 |
return (
|
|
|
|
| 31 |
</IconButton>
|
| 32 |
</Box>
|
| 33 |
|
| 34 |
+
{/* Main Content Area */}
|
| 35 |
+
<Box sx={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
|
| 36 |
+
{!panelContent ? (
|
| 37 |
+
<Box sx={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', p: 4 }}>
|
| 38 |
+
<Typography variant="body2" color="text.secondary" sx={{ opacity: 0.5 }}>
|
| 39 |
+
NO DATA LOADED
|
| 40 |
+
</Typography>
|
| 41 |
+
</Box>
|
| 42 |
+
) : (
|
| 43 |
+
<Box sx={{ flex: 1, overflow: 'auto', p: 2 }}>
|
| 44 |
+
<Box
|
| 45 |
+
className="code-panel"
|
| 46 |
+
sx={{
|
| 47 |
+
background: '#0A0B0C',
|
| 48 |
+
borderRadius: 'var(--radius-md)',
|
| 49 |
+
padding: '18px',
|
| 50 |
+
border: '1px solid rgba(255,255,255,0.03)',
|
| 51 |
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", monospace',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
fontSize: '13px',
|
| 53 |
+
lineHeight: 1.55,
|
| 54 |
+
height: '100%',
|
| 55 |
+
overflow: 'auto',
|
| 56 |
+
}}
|
| 57 |
+
>
|
| 58 |
+
{panelContent.content ? (
|
| 59 |
+
panelContent.language === 'python' ? (
|
| 60 |
+
<SyntaxHighlighter
|
| 61 |
+
language="python"
|
| 62 |
+
style={vscDarkPlus}
|
| 63 |
+
customStyle={{
|
| 64 |
+
margin: 0,
|
| 65 |
+
padding: 0,
|
| 66 |
+
background: 'transparent',
|
| 67 |
+
fontSize: '13px',
|
| 68 |
+
fontFamily: 'inherit',
|
| 69 |
+
}}
|
| 70 |
+
wrapLines={true}
|
| 71 |
+
wrapLongLines={true}
|
| 72 |
+
>
|
| 73 |
+
{panelContent.content}
|
| 74 |
+
</SyntaxHighlighter>
|
| 75 |
+
) : (
|
| 76 |
+
<Box component="pre" sx={{
|
| 77 |
+
m: 0,
|
| 78 |
+
fontFamily: 'inherit',
|
| 79 |
+
color: 'var(--text)',
|
| 80 |
+
whiteSpace: 'pre-wrap',
|
| 81 |
+
wordBreak: 'break-all'
|
| 82 |
+
}}>
|
| 83 |
+
<code>{panelContent.content}</code>
|
| 84 |
+
</Box>
|
| 85 |
+
)
|
| 86 |
) : (
|
| 87 |
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', opacity: 0.5 }}>
|
| 88 |
+
<Typography variant="caption">
|
| 89 |
+
NO CONTENT TO DISPLAY
|
| 90 |
+
</Typography>
|
| 91 |
+
</Box>
|
| 92 |
+
)}
|
| 93 |
+
</Box>
|
| 94 |
+
</Box>
|
| 95 |
+
)}
|
| 96 |
+
</Box>
|
| 97 |
+
|
| 98 |
+
{/* Plan Display at Bottom */}
|
| 99 |
+
{plan && plan.length > 0 && (
|
| 100 |
+
<Box sx={{
|
| 101 |
+
borderTop: '1px solid rgba(255,255,255,0.03)',
|
| 102 |
+
bgcolor: 'rgba(0,0,0,0.2)',
|
| 103 |
+
maxHeight: '30%',
|
| 104 |
+
display: 'flex',
|
| 105 |
+
flexDirection: 'column'
|
| 106 |
+
}}>
|
| 107 |
+
<Box sx={{ p: 1.5, borderBottom: '1px solid rgba(255,255,255,0.03)', display: 'flex', alignItems: 'center', gap: 1 }}>
|
| 108 |
+
<Typography variant="caption" sx={{ fontWeight: 600, color: 'var(--muted-text)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
|
| 109 |
+
CURRENT PLAN
|
| 110 |
</Typography>
|
| 111 |
+
</Box>
|
| 112 |
+
<Box sx={{ p: 2, overflow: 'auto', display: 'flex', flexDirection: 'column', gap: 1 }}>
|
| 113 |
+
{plan.map((item) => (
|
| 114 |
+
<Box key={item.id} sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.5 }}>
|
| 115 |
+
<Box sx={{ mt: 0.2 }}>
|
| 116 |
+
{item.status === 'completed' && <CheckCircleIcon sx={{ fontSize: 16, color: 'var(--accent-green)' }} />}
|
| 117 |
+
{item.status === 'in_progress' && <PlayCircleOutlineIcon sx={{ fontSize: 16, color: 'var(--accent-yellow)' }} />}
|
| 118 |
+
{item.status === 'pending' && <RadioButtonUncheckedIcon sx={{ fontSize: 16, color: 'var(--muted-text)', opacity: 0.5 }} />}
|
| 119 |
+
</Box>
|
| 120 |
+
<Typography
|
| 121 |
+
variant="body2"
|
| 122 |
+
sx={{
|
| 123 |
+
fontSize: '13px',
|
| 124 |
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, monospace',
|
| 125 |
+
color: item.status === 'completed' ? 'var(--muted-text)' : 'var(--text)',
|
| 126 |
+
textDecoration: item.status === 'completed' ? 'line-through' : 'none',
|
| 127 |
+
opacity: item.status === 'pending' ? 0.7 : 1
|
| 128 |
+
}}
|
| 129 |
+
>
|
| 130 |
+
{item.content}
|
| 131 |
+
</Typography>
|
| 132 |
+
</Box>
|
| 133 |
+
))}
|
| 134 |
+
</Box>
|
| 135 |
</Box>
|
| 136 |
)}
|
| 137 |
</Box>
|
frontend/src/hooks/useAgentWebSocket.ts
CHANGED
|
@@ -32,6 +32,7 @@ export function useAgentWebSocket({
|
|
| 32 |
addTraceLog,
|
| 33 |
clearTraceLogs,
|
| 34 |
setPanelContent,
|
|
|
|
| 35 |
traceLogs,
|
| 36 |
} = useAgentStore();
|
| 37 |
|
|
@@ -148,6 +149,15 @@ export function useAgentWebSocket({
|
|
| 148 |
break;
|
| 149 |
}
|
| 150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
case 'approval_required': {
|
| 152 |
const tools = event.data?.tools as Array<{
|
| 153 |
tool: string;
|
|
|
|
| 32 |
addTraceLog,
|
| 33 |
clearTraceLogs,
|
| 34 |
setPanelContent,
|
| 35 |
+
setPlan,
|
| 36 |
traceLogs,
|
| 37 |
} = useAgentStore();
|
| 38 |
|
|
|
|
| 149 |
break;
|
| 150 |
}
|
| 151 |
|
| 152 |
+
case 'plan_update': {
|
| 153 |
+
const plan = (event.data?.plan as any[]) || [];
|
| 154 |
+
setPlan(plan);
|
| 155 |
+
if (!useLayoutStore.getState().isRightPanelOpen) {
|
| 156 |
+
setRightPanelOpen(true);
|
| 157 |
+
}
|
| 158 |
+
break;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
case 'approval_required': {
|
| 162 |
const tools = event.data?.tools as Array<{
|
| 163 |
tool: string;
|
frontend/src/store/agentStore.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
| 1 |
import { create } from 'zustand';
|
| 2 |
import type { Message, ApprovalBatch, User, TraceLog } from '@/types/agent';
|
| 3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
interface AgentStore {
|
| 5 |
// State per session (keyed by session ID)
|
| 6 |
messagesBySession: Record<string, Message[]>;
|
|
@@ -11,6 +17,7 @@ interface AgentStore {
|
|
| 11 |
error: string | null;
|
| 12 |
traceLogs: TraceLog[];
|
| 13 |
panelContent: { title: string; content: string; language?: string; parameters?: any } | null;
|
|
|
|
| 14 |
|
| 15 |
// Actions
|
| 16 |
addMessage: (sessionId: string, message: Message) => void;
|
|
@@ -24,6 +31,7 @@ interface AgentStore {
|
|
| 24 |
addTraceLog: (log: TraceLog) => void;
|
| 25 |
clearTraceLogs: () => void;
|
| 26 |
setPanelContent: (content: { title: string; content: string; language?: string; parameters?: any } | null) => void;
|
|
|
|
| 27 |
}
|
| 28 |
|
| 29 |
export const useAgentStore = create<AgentStore>((set, get) => ({
|
|
@@ -35,6 +43,7 @@ export const useAgentStore = create<AgentStore>((set, get) => ({
|
|
| 35 |
error: null,
|
| 36 |
traceLogs: [],
|
| 37 |
panelContent: null,
|
|
|
|
| 38 |
|
| 39 |
addMessage: (sessionId: string, message: Message) => {
|
| 40 |
set((state) => {
|
|
@@ -94,4 +103,8 @@ export const useAgentStore = create<AgentStore>((set, get) => ({
|
|
| 94 |
setPanelContent: (content) => {
|
| 95 |
set({ panelContent: content });
|
| 96 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
}));
|
|
|
|
| 1 |
import { create } from 'zustand';
|
| 2 |
import type { Message, ApprovalBatch, User, TraceLog } from '@/types/agent';
|
| 3 |
|
| 4 |
+
export interface PlanItem {
|
| 5 |
+
id: string;
|
| 6 |
+
content: string;
|
| 7 |
+
status: 'pending' | 'in_progress' | 'completed';
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
interface AgentStore {
|
| 11 |
// State per session (keyed by session ID)
|
| 12 |
messagesBySession: Record<string, Message[]>;
|
|
|
|
| 17 |
error: string | null;
|
| 18 |
traceLogs: TraceLog[];
|
| 19 |
panelContent: { title: string; content: string; language?: string; parameters?: any } | null;
|
| 20 |
+
plan: PlanItem[];
|
| 21 |
|
| 22 |
// Actions
|
| 23 |
addMessage: (sessionId: string, message: Message) => void;
|
|
|
|
| 31 |
addTraceLog: (log: TraceLog) => void;
|
| 32 |
clearTraceLogs: () => void;
|
| 33 |
setPanelContent: (content: { title: string; content: string; language?: string; parameters?: any } | null) => void;
|
| 34 |
+
setPlan: (plan: PlanItem[]) => void;
|
| 35 |
}
|
| 36 |
|
| 37 |
export const useAgentStore = create<AgentStore>((set, get) => ({
|
|
|
|
| 43 |
error: null,
|
| 44 |
traceLogs: [],
|
| 45 |
panelContent: null,
|
| 46 |
+
plan: [],
|
| 47 |
|
| 48 |
addMessage: (sessionId: string, message: Message) => {
|
| 49 |
set((state) => {
|
|
|
|
| 103 |
setPanelContent: (content) => {
|
| 104 |
set({ panelContent: content });
|
| 105 |
},
|
| 106 |
+
|
| 107 |
+
setPlan: (plan: PlanItem[]) => {
|
| 108 |
+
set({ plan });
|
| 109 |
+
},
|
| 110 |
}));
|
frontend/src/types/events.ts
CHANGED
|
@@ -15,7 +15,8 @@ export type EventType =
|
|
| 15 |
| 'error'
|
| 16 |
| 'shutdown'
|
| 17 |
| 'interrupted'
|
| 18 |
-
| 'undo_complete'
|
|
|
|
| 19 |
|
| 20 |
export interface AgentEvent {
|
| 21 |
event_type: EventType;
|
|
@@ -50,6 +51,10 @@ export interface ToolLogEventData {
|
|
| 50 |
log: string;
|
| 51 |
}
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
export interface ApprovalRequiredEventData {
|
| 54 |
tools: ApprovalToolItem[];
|
| 55 |
count: number;
|
|
|
|
| 15 |
| 'error'
|
| 16 |
| 'shutdown'
|
| 17 |
| 'interrupted'
|
| 18 |
+
| 'undo_complete'
|
| 19 |
+
| 'plan_update';
|
| 20 |
|
| 21 |
export interface AgentEvent {
|
| 22 |
event_type: EventType;
|
|
|
|
| 51 |
log: string;
|
| 52 |
}
|
| 53 |
|
| 54 |
+
export interface PlanUpdateEventData {
|
| 55 |
+
plan: Array<{ id: string; content: string; status: 'pending' | 'in_progress' | 'completed' }>;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
export interface ApprovalRequiredEventData {
|
| 59 |
tools: ApprovalToolItem[];
|
| 60 |
count: number;
|