// Claude Desktop — App-State import { writable, derived } from 'svelte/store'; // Typen export interface Agent { id: string; type: 'main' | 'explore' | 'plan' | 'bash' | 'code' | 'test' | 'review'; status: 'active' | 'waiting' | 'idle' | 'stopped'; task: string; startedAt: Date; toolCalls: ToolCall[]; // Subagent-Hierarchie parentAgentId?: string; // undefined = Main Agent depth: number; // 0 = Main, 1 = direkter Subagent, etc. model?: string; // Welches Modell nutzt dieser Agent } export interface ToolCall { id: string; agentId: string; tool: string; args: Record; status: 'running' | 'completed' | 'failed'; startedAt: Date; completedAt?: Date; result?: unknown; } export interface Message { id: string; role: 'user' | 'assistant' | 'system'; content: string; timestamp: Date; agentId?: string; model?: string; } export interface Permission { id: string; pattern: string; type: 'session' | 'permanent'; action: 'allow' | 'deny'; createdAt: Date; } // Stores export const agents = writable([]); export const toolCalls = writable([]); export const messages = writable([]); export const permissions = writable([]); // UI-State export const isProcessing = writable(false); export const currentInput = writable(''); export const selectedAgentId = writable(null); export const currentModel = writable(''); export const currentSessionId = writable(null); // Session-Statistiken (kumuliert) export const sessionStats = writable({ totalTokensIn: 0, totalTokensOut: 0, totalCost: 0, messageCount: 0, }); // Abgeleitete Stores export const activeAgents = derived(agents, ($agents) => $agents.filter((a) => a.status === 'active') ); export const recentToolCalls = derived(toolCalls, ($toolCalls) => $toolCalls.slice(-50).reverse() ); export const agentCount = derived(agents, ($agents) => ({ total: $agents.length, active: $agents.filter((a) => a.status === 'active').length, waiting: $agents.filter((a) => a.status === 'waiting').length, idle: $agents.filter((a) => a.status === 'idle').length, mainAgents: $agents.filter((a) => !a.parentAgentId).length, subAgents: $agents.filter((a) => a.parentAgentId).length, })); // Agent-Baum Typen und Builder (muss vor agentTree Store sein) export interface AgentTreeNode { agent: Agent; children: AgentTreeNode[]; } export function buildAgentTree(agentsList: Agent[]): AgentTreeNode[] { // Nur Root-Agents (ohne Parent) const roots = agentsList.filter((a) => !a.parentAgentId); function buildNode(agent: Agent): AgentTreeNode { const children = agentsList .filter((a) => a.parentAgentId === agent.id) .map(buildNode); return { agent, children }; } return roots.map(buildNode); } // Agent-Baum als reaktiver Store export const agentTree = derived(agents, ($agents) => buildAgentTree($agents)); // Aktionen export function addMessage(role: Message['role'], content: string, agentId?: string) { messages.update((msgs) => [ ...msgs, { id: crypto.randomUUID(), role, content, timestamp: new Date(), agentId } ]); } export interface AddAgentOptions { id?: string; parentAgentId?: string; model?: string; } export function addAgent(type: Agent['type'], task: string, options?: AddAgentOptions): string { const id = options?.id || crypto.randomUUID(); const parentAgentId = options?.parentAgentId; // Tiefe berechnen: Parent-Tiefe + 1 (oder 0 wenn kein Parent) let depth = 0; if (parentAgentId) { agents.subscribe((ags) => { const parent = ags.find((a) => a.id === parentAgentId); if (parent) { depth = parent.depth + 1; } })(); } agents.update((ags) => [ ...ags, { id, type, status: 'active', task, startedAt: new Date(), toolCalls: [], parentAgentId, depth, model: options?.model, } ]); return id; } // Subagent hinzufügen (Kurzform) export function addSubAgent( parentId: string, type: Agent['type'], task: string, options?: Omit ): string { return addAgent(type, task, { ...options, parentAgentId: parentId }); } // Alle Kinder eines Agents finden export function getChildAgents(parentId: string, agentsList: Agent[]): Agent[] { return agentsList.filter((a) => a.parentAgentId === parentId); } export function updateAgentStatus(id: string, status: Agent['status']) { agents.update((ags) => ags.map((a) => (a.id === id ? { ...a, status } : a)) ); } export function addToolCall(agentId: string, tool: string, args: Record): string { const id = crypto.randomUUID(); const call: ToolCall = { id, agentId, tool, args, status: 'running', startedAt: new Date() }; toolCalls.update((calls) => [...calls, call]); // Auch im Agent speichern agents.update((ags) => ags.map((a) => a.id === agentId ? { ...a, toolCalls: [...a.toolCalls, call] } : a ) ); return id; } export function completeToolCall(id: string, result: unknown, failed = false) { toolCalls.update((calls) => calls.map((c) => c.id === id ? { ...c, status: failed ? 'failed' : 'completed', completedAt: new Date(), result } : c ) ); } export function clearAll() { agents.set([]); toolCalls.set([]); messages.set([]); isProcessing.set(false); } // DB-Nachricht Format (für Tauri) export interface DbMessage { id: string; session_id: string; role: string; content: string; model: string | null; agent_id: string | null; // Agent der die Nachricht erzeugt hat timestamp: string; } // Konvertierung: Store → DB export function messageToDb(msg: Message, sessionId: string): DbMessage { return { id: msg.id, session_id: sessionId, role: msg.role, content: msg.content, model: msg.model || null, agent_id: msg.agentId || null, timestamp: msg.timestamp.toISOString(), }; } // Konvertierung: DB → Store export function dbToMessage(db: DbMessage): Message { return { id: db.id, role: db.role as Message['role'], content: db.content, model: db.model || undefined, agentId: db.agent_id || undefined, timestamp: new Date(db.timestamp), }; } // Nachrichten aus DB in Store laden export function setMessagesFromDb(dbMessages: DbMessage[]) { messages.set(dbMessages.map(dbToMessage)); } // ============ System-Monitor ============ export type MonitorEventType = 'api' | 'hook' | 'tool' | 'mcp' | 'agent' | 'error' | 'debug'; export interface MonitorEvent { id: string; timestamp: Date; type: MonitorEventType; summary: string; // Einzeiler für Kompakt-Ansicht details: Record; // Vollständige Daten sessionId?: string; agentId?: string; durationMs?: number; error?: string; } // Farbcodierung für Event-Typen export const monitorEventColors: Record = { api: '🔵', hook: '🟢', tool: '🟡', mcp: '🟣', agent: '🟠', error: '🔴', debug: '⚪', }; // Monitor Store — Ringbuffer mit max 1000 Events const MAX_MONITOR_EVENTS = 1000; export const monitorEvents = writable([]); // Filter für Monitor-Ansicht export const monitorFilter = writable('all'); export const monitorAutoScroll = writable(true); export const selectedMonitorEventId = writable(null); // Gefilterte Events export const filteredMonitorEvents = derived( [monitorEvents, monitorFilter], ([$events, $filter]) => { if ($filter === 'all') return $events; return $events.filter((e) => e.type === $filter); } ); // Monitor-Statistiken export const monitorStats = derived(monitorEvents, ($events) => { const last100 = $events.slice(-100); const apiEvents = last100.filter((e) => e.type === 'api'); const errorEvents = last100.filter((e) => e.type === 'error'); const avgLatency = apiEvents.length > 0 ? apiEvents.reduce((sum, e) => sum + (e.durationMs || 0), 0) / apiEvents.length : 0; return { totalEvents: $events.length, apiCalls: apiEvents.length, errors: errorEvents.length, avgLatencyMs: Math.round(avgLatency), }; }); // Monitor-Event hinzufügen export function addMonitorEvent( type: MonitorEventType, summary: string, details: Record = {}, options?: Partial> ) { const event: MonitorEvent = { id: crypto.randomUUID(), timestamp: new Date(), type, summary, details, ...options, }; monitorEvents.update((events) => { const updated = [...events, event]; // Ringbuffer: Alte Events entfernen wenn zu viele if (updated.length > MAX_MONITOR_EVENTS) { return updated.slice(-MAX_MONITOR_EVENTS); } return updated; }); return event.id; } // Monitor leeren export function clearMonitorEvents() { monitorEvents.set([]); } // Sensitive Daten maskieren export function maskSensitive(data: string): string { return data .replace(/password[=:]\s*\S+/gi, 'password=***') .replace(/api[_-]?key[=:]\s*\S+/gi, 'api_key=***') .replace(/bearer\s+\S+/gi, 'Bearer ***') .replace(/sk-[a-zA-Z0-9]+/g, 'sk-***') .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, '***@***.***'); }