feat(v3): implement session continuity, workspace teardown, StatusBar rewrite, subagent routing fix

P6: persistSessionForProject() saves agent state + messages to SQLite on
session complete. registerSessionProject() maps sessionId -> projectId.
ClaudeSession restoreMessagesFromRecords() restores cached messages on mount.

P7: clearAllAgentSessions() clears sessions on group switch. switchGroup()
calls clearAllAgentSessions() + resets terminal tabs.

P10: StatusBar rewritten for workspace store (group name, project count,
agent count, "BTerminal v3"). Subagent routing fixed: project-scoped
sessions skip layout pane creation (render in TeamAgentsPanel instead).
This commit is contained in:
Hibryda 2026-03-07 16:33:27 +01:00
parent 9766a480ed
commit e0056f811f
5 changed files with 156 additions and 31 deletions

View file

@ -1,14 +1,21 @@
<script lang="ts">
import { onMount } from 'svelte';
import type { ProjectConfig } from '../../types/groups';
import {
loadProjectAgentState,
saveProjectAgentState,
loadAgentMessages,
saveAgentMessages,
type ProjectAgentState,
type AgentMessageRecord,
} from '../../adapters/groups-bridge';
import { registerSessionProject } from '../../agent-dispatcher';
import {
createAgentSession,
appendAgentMessages,
updateAgentCost,
updateAgentStatus,
setAgentSdkSessionId,
getAgentSession,
} from '../../stores/agents.svelte';
import type { AgentMessage } from '../../adapters/sdk-messages';
import AgentPane from '../Agent/AgentPane.svelte';
interface Props {
@ -18,11 +25,10 @@
let { project, onsessionid }: Props = $props();
// Per-project session ID (stable across renders, changes with project)
let sessionId = $state(crypto.randomUUID());
let lastState = $state<ProjectAgentState | null>(null);
let resumeSessionId = $state<string | undefined>(undefined);
let loading = $state(true);
let hasRestoredHistory = $state(false);
// Load previous session state when project changes
$effect(() => {
@ -32,14 +38,20 @@
async function loadPreviousState(projectId: string) {
loading = true;
hasRestoredHistory = false;
try {
const state = await loadProjectAgentState(projectId);
lastState = state;
if (state?.sdk_session_id) {
resumeSessionId = state.sdk_session_id;
if (state?.last_session_id) {
sessionId = state.last_session_id;
// Restore cached messages into the agent store
const records = await loadAgentMessages(projectId);
if (records.length > 0) {
restoreMessagesFromRecords(sessionId, state, records);
hasRestoredHistory = true;
}
} else {
resumeSessionId = undefined;
sessionId = crypto.randomUUID();
}
} catch (e) {
@ -47,9 +59,45 @@
sessionId = crypto.randomUUID();
} finally {
loading = false;
// Register session -> project mapping for persistence
registerSessionProject(sessionId, project.id);
onsessionid?.(sessionId);
}
}
function restoreMessagesFromRecords(
sid: string,
state: ProjectAgentState,
records: AgentMessageRecord[],
) {
// Don't re-create if already exists
if (getAgentSession(sid)) return;
createAgentSession(sid, state.last_prompt ?? '');
if (state.sdk_session_id) {
setAgentSdkSessionId(sid, state.sdk_session_id);
}
// Convert records back to AgentMessage format
const messages: AgentMessage[] = records.map(r => ({
id: `restored-${r.id}`,
type: r.message_type as AgentMessage['type'],
content: JSON.parse(r.content),
parentId: r.parent_id ?? undefined,
}));
appendAgentMessages(sid, messages);
updateAgentCost(sid, {
costUsd: state.cost_usd,
inputTokens: state.input_tokens,
outputTokens: state.output_tokens,
numTurns: 0,
durationMs: 0,
});
// Mark as done (it's a restored completed session)
updateAgentStatus(sid, state.status === 'error' ? 'error' : 'done');
}
</script>
<div class="claude-session">
@ -69,6 +117,7 @@
overflow: hidden;
display: flex;
flex-direction: column;
flex: 1;
}
.loading-state {