From be65dee04a3ed76e69571b39379a0ed5f8f15c1a Mon Sep 17 00:00:00 2001 From: Eddy Date: Tue, 14 Apr 2026 14:30:28 +0200 Subject: [PATCH] =?UTF-8?q?Claude-Session-ID=20f=C3=BCr=20SDK-Fortsetzung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - events.ts: Session-ID aus claude-result speichern via set_claude_session_id - claude.rs: load_claude_session_id() lädt ID der aktiven Session - claude.rs: send_to_bridge_full() mit resumeSessionId Parameter - claude-bridge.js: sendMessage() akzeptiert resumeSessionId - Bridge nutzt sessionId in query() Optionen für SDK-Fortsetzung Ermöglicht nahtlose Konversations-Fortsetzung auf SDK-Ebene. Co-Authored-By: Claude Opus 4.5 --- scripts/claude-bridge.js | 36 +++++++++++++++++++++++++----------- src-tauri/src/claude.rs | 38 ++++++++++++++++++++++++++++++++++++-- src/lib/stores/events.ts | 18 +++++++++++++++++- 3 files changed, 78 insertions(+), 14 deletions(-) diff --git a/scripts/claude-bridge.js b/scripts/claude-bridge.js index 7d929e0..72b5a95 100644 --- a/scripts/claude-bridge.js +++ b/scripts/claude-bridge.js @@ -119,7 +119,7 @@ function summarizeToolInput(tool, input) { // ============ Claude Agent SDK ============ -async function sendMessage(message, requestId, model = null, contextOverride = null) { +async function sendMessage(message, requestId, model = null, contextOverride = null, resumeSessionId = null) { // Modell für diese Anfrage (Parameter > State > Default) const useModel = model || currentModel; @@ -129,31 +129,37 @@ async function sendMessage(message, requestId, model = null, contextOverride = n currentAgentId = randomUUID(); activeAbort = new AbortController(); + const isResuming = !!resumeSessionId; + sendEvent('agent-started', { id: currentAgentId, type: 'Main', task: message.substring(0, 100), model: useModel, + resuming: isResuming, }); // Monitor: Agent gestartet - sendMonitorEvent('agent', `Main Agent gestartet (${useModel})`, { + const resumeInfo = isResuming ? ' (Fortsetzung)' : ''; + sendMonitorEvent('agent', `Main Agent gestartet (${useModel})${resumeInfo}`, { agentId: currentAgentId, model: useModel, task: message.substring(0, 100), contextTokens: useContext ? Math.ceil(useContext.length / 4) : 0, + resumeSessionId: resumeSessionId || null, }); // Monitor: API-Request const contextInfo = useContext ? ` +${Math.ceil(useContext.length / 4)} ctx` : ''; - sendMonitorEvent('api', `→ ${useModel}${contextInfo}`, { + sendMonitorEvent('api', `→ ${useModel}${contextInfo}${resumeInfo}`, { model: useModel, promptLength: message.length, contextLength: useContext?.length || 0, maxTurns: 25, + resumeSessionId: resumeSessionId || null, }); - sendResponse(requestId, { agentId: currentAgentId, status: 'gestartet', model: useModel }); + sendResponse(requestId, { agentId: currentAgentId, status: 'gestartet', model: useModel, resuming: isResuming }); // Nachricht mit Context kombinieren const fullPrompt = useContext @@ -165,13 +171,21 @@ async function sendMessage(message, requestId, model = null, contextOverride = n let usedModel = useModel; try { + // Query-Optionen zusammenstellen + const queryOptions = { + model: useModel, + maxTurns: 25, + abortController: activeAbort, + }; + + // Session-ID für Fortsetzung hinzufügen wenn vorhanden + if (resumeSessionId) { + queryOptions.sessionId = resumeSessionId; + } + const conversation = query({ prompt: fullPrompt, - options: { - model: useModel, - maxTurns: 25, - abortController: activeAbort, - }, + options: queryOptions, }); for await (const event of conversation) { @@ -342,8 +356,8 @@ function handleCommand(msg) { sendError(msg.id, 'Keine Nachricht angegeben'); return; } - // Modell und Context können pro Anfrage überschrieben werden - sendMessage(msg.message, msg.id, msg.model, msg.context); + // Modell, Context und Resume-Session-ID können pro Anfrage überschrieben werden + sendMessage(msg.message, msg.id, msg.model, msg.context, msg.resumeSessionId); break; case 'set-context': diff --git a/src-tauri/src/claude.rs b/src-tauri/src/claude.rs index 66125ca..1342807 100644 --- a/src-tauri/src/claude.rs +++ b/src-tauri/src/claude.rs @@ -259,11 +259,22 @@ fn handle_bridge_message(app: &AppHandle, msg: BridgeMessage) { /// Befehl an Bridge senden fn send_to_bridge(app: &AppHandle, command: &str, message: &str) -> Result { - send_to_bridge_with_context(app, command, message, None) + send_to_bridge_full(app, command, message, None, None) } /// Befehl an Bridge senden mit optionalem Context fn send_to_bridge_with_context(app: &AppHandle, command: &str, message: &str, context: Option) -> Result { + send_to_bridge_full(app, command, message, context, None) +} + +/// Befehl an Bridge senden mit Context und Resume-Session-ID +fn send_to_bridge_full( + app: &AppHandle, + command: &str, + message: &str, + context: Option, + resume_session_id: Option, +) -> Result { let state = app.state::>>(); let mut state = state.lock().unwrap(); @@ -289,6 +300,12 @@ fn send_to_bridge_with_context(app: &AppHandle, command: &str, message: &str, co payload["context"] = serde_json::Value::String(ctx); } } + // Resume-Session-ID hinzufügen wenn vorhanden + if let Some(sid) = resume_session_id { + if !sid.is_empty() { + payload["resumeSessionId"] = serde_json::Value::String(sid); + } + } payload }, "set-context" | "clear-context" => serde_json::json!({ @@ -339,12 +356,29 @@ pub async fn send_message(app: AppHandle, message: String) -> Result Option { + if let Some(db_state) = app.try_state::>>() { + let db = db_state.lock().ok()?; + if let Ok(Some(session)) = db.get_active_session() { + return session.claude_session_id; + } + } + None +} + /// Sticky Context aus DB laden und als Prompt-Text rendern fn load_sticky_context_for_prompt(app: &AppHandle) -> Option { use crate::context; diff --git a/src/lib/stores/events.ts b/src/lib/stores/events.ts index 7243687..8b1f31f 100644 --- a/src/lib/stores/events.ts +++ b/src/lib/stores/events.ts @@ -235,7 +235,7 @@ export async function initEventListeners(): Promise { listeners.push( await listen('claude-result', async (event) => { const { cost, tokens, session_id, model } = event.payload; - console.log('📊 Ergebnis:', { cost: cost ? `$${cost.toFixed(4)}` : '-', tokens, model }); + console.log('📊 Ergebnis:', { cost: cost ? `$${cost.toFixed(4)}` : '-', tokens, model, session_id }); // Modell an die Streaming-Nachricht anhängen und speichern if (streamingMessageId) { @@ -261,6 +261,22 @@ export async function initEventListeners(): Promise { currentModel.set(model); } + // Claude Session-ID speichern für Fortsetzung + if (session_id) { + const appSessionId = get(currentSessionId); + if (appSessionId) { + try { + await invoke('set_claude_session_id', { + sessionId: appSessionId, + claudeSessionId: session_id, + }); + console.log('🔗 Claude Session-ID gespeichert:', session_id); + } catch (err) { + console.warn('Claude Session-ID konnte nicht gespeichert werden:', err); + } + } + } + // Session-Statistiken aktualisieren if (tokens || cost) { sessionStats.update((s) => ({