diff --git a/v2/src/lib/agent-dispatcher.ts b/v2/src/lib/agent-dispatcher.ts index 9b84dec..5f0a17e 100644 --- a/v2/src/lib/agent-dispatcher.ts +++ b/v2/src/lib/agent-dispatcher.ts @@ -11,15 +11,28 @@ import { appendAgentMessages, updateAgentCost, getAgentSessions, + getAgentSession, createAgentSession, findChildByToolUseId, } from './stores/agents.svelte'; import { addPane, getPanes } from './stores/layout.svelte'; import { notify } from './stores/notifications.svelte'; +import { + saveProjectAgentState, + saveAgentMessages, + type AgentMessageRecord, +} from './adapters/groups-bridge'; let unlistenMsg: (() => void) | null = null; let unlistenExit: (() => void) | null = null; +// Map sessionId -> projectId for persistence routing +const sessionProjectMap = new Map(); + +export function registerSessionProject(sessionId: string, projectId: string): void { + sessionProjectMap.set(sessionId, projectId); +} + // Sidecar liveness — checked by UI components let sidecarAlive = true; @@ -160,6 +173,8 @@ function handleAgentEvent(sessionId: string, event: Record): vo updateAgentStatus(sessionId, 'done'); notify('success', `Agent done — $${cost.totalCostUsd.toFixed(4)}, ${cost.numTurns} turns`); } + // Persist session state for project-scoped sessions + persistSessionForProject(sessionId); break; } } @@ -220,15 +235,60 @@ function spawnSubagentPane(parentSessionId: string, tc: ToolCallContent): void { }); updateAgentStatus(childId, 'running'); - // Create layout pane, auto-grouped under parent's title - const parentPane = getPanes().find(p => p.id === parentSessionId); - const groupName = parentPane?.title ?? `Agent ${parentSessionId.slice(0, 8)}`; - addPane({ - id: childId, - type: 'agent', - title: `Sub: ${label}`, - group: groupName, - }); + // For project-scoped sessions, subagents render in TeamAgentsPanel (no layout pane) + // For non-project sessions (detached mode), create a layout pane + if (!sessionProjectMap.has(parentSessionId)) { + const parentPane = getPanes().find(p => p.id === parentSessionId); + const groupName = parentPane?.title ?? `Agent ${parentSessionId.slice(0, 8)}`; + addPane({ + id: childId, + type: 'agent', + title: `Sub: ${label}`, + group: groupName, + }); + } +} + +/** Persist session state + messages to SQLite for the project that owns this session */ +async function persistSessionForProject(sessionId: string): Promise { + const projectId = sessionProjectMap.get(sessionId); + if (!projectId) return; // Not a project-scoped session + + const session = getAgentSession(sessionId); + if (!session) return; + + try { + // Save agent state + await saveProjectAgentState({ + project_id: projectId, + last_session_id: sessionId, + sdk_session_id: session.sdkSessionId ?? null, + status: session.status, + cost_usd: session.costUsd, + input_tokens: session.inputTokens, + output_tokens: session.outputTokens, + last_prompt: session.prompt, + updated_at: Date.now(), + }); + + // Save messages + const records: AgentMessageRecord[] = session.messages.map((m, i) => ({ + id: i, + session_id: sessionId, + project_id: projectId, + sdk_session_id: session.sdkSessionId ?? null, + message_type: m.type, + content: JSON.stringify(m.content), + parent_id: m.parentId ?? null, + created_at: Date.now(), + })); + + if (records.length > 0) { + await saveAgentMessages(sessionId, projectId, session.sdkSessionId, records); + } + } catch (e) { + console.warn('Failed to persist agent session:', e); + } } export function stopAgentDispatcher(): void { diff --git a/v2/src/lib/components/StatusBar/StatusBar.svelte b/v2/src/lib/components/StatusBar/StatusBar.svelte index 484308d..b69ff3f 100644 --- a/v2/src/lib/components/StatusBar/StatusBar.svelte +++ b/v2/src/lib/components/StatusBar/StatusBar.svelte @@ -1,22 +1,27 @@
- {terminalCount} terminals + {#if activeGroup} + {activeGroup.name} + + {/if} + {projectCount} projects - {agentCount} agents + {agentCount} agents {#if activeAgents > 0} @@ -34,24 +39,24 @@ ${totalCost.toFixed(4)} {/if} - BTerminal v2 + BTerminal v3