diff --git a/scripts/claude-bridge.js b/scripts/claude-bridge.js index 13fb0ec..7d929e0 100644 --- a/scripts/claude-bridge.js +++ b/scripts/claude-bridge.js @@ -19,6 +19,9 @@ let activeAbort = null; let currentAgentId = null; let currentModel = process.env.CLAUDE_MODEL || 'opus'; +// Sticky Context (Schicht 1) — wird bei JEDEM API-Call injiziert +let stickyContext = ''; + // Subagent-Tracking // Map: toolUseId → { agentId, parentId, type, task, depth } const activeSubagents = new Map(); @@ -116,10 +119,13 @@ function summarizeToolInput(tool, input) { // ============ Claude Agent SDK ============ -async function sendMessage(message, requestId, model = null) { +async function sendMessage(message, requestId, model = null, contextOverride = null) { // Modell für diese Anfrage (Parameter > State > Default) const useModel = model || currentModel; + // Context für diese Anfrage (Parameter > State) + const useContext = contextOverride || stickyContext; + currentAgentId = randomUUID(); activeAbort = new AbortController(); @@ -135,24 +141,32 @@ async function sendMessage(message, requestId, model = null) { agentId: currentAgentId, model: useModel, task: message.substring(0, 100), + contextTokens: useContext ? Math.ceil(useContext.length / 4) : 0, }); // Monitor: API-Request - sendMonitorEvent('api', `→ ${useModel}`, { + const contextInfo = useContext ? ` +${Math.ceil(useContext.length / 4)} ctx` : ''; + sendMonitorEvent('api', `→ ${useModel}${contextInfo}`, { model: useModel, promptLength: message.length, + contextLength: useContext?.length || 0, maxTurns: 25, }); sendResponse(requestId, { agentId: currentAgentId, status: 'gestartet', model: useModel }); + // Nachricht mit Context kombinieren + const fullPrompt = useContext + ? `${useContext}\n\n---\n\n${message}` + : message; + const startTime = Date.now(); let fullText = ''; let usedModel = useModel; try { const conversation = query({ - prompt: message, + prompt: fullPrompt, options: { model: useModel, maxTurns: 25, @@ -328,8 +342,32 @@ function handleCommand(msg) { sendError(msg.id, 'Keine Nachricht angegeben'); return; } - // Modell kann pro Anfrage überschrieben werden - sendMessage(msg.message, msg.id, msg.model); + // Modell und Context können pro Anfrage überschrieben werden + sendMessage(msg.message, msg.id, msg.model, msg.context); + break; + + case 'set-context': + // Sticky Context setzen (wird bei allen folgenden Nachrichten verwendet) + stickyContext = msg.context || ''; + const ctxTokens = stickyContext ? Math.ceil(stickyContext.length / 4) : 0; + sendResponse(msg.id, { status: 'Context gesetzt', tokens: ctxTokens }); + sendMonitorEvent('hook', `Sticky Context gesetzt (~${ctxTokens} Token)`, { + contextLength: stickyContext.length, + estimatedTokens: ctxTokens, + }); + break; + + case 'get-context': + sendResponse(msg.id, { + context: stickyContext, + tokens: stickyContext ? Math.ceil(stickyContext.length / 4) : 0, + }); + break; + + case 'clear-context': + stickyContext = ''; + sendResponse(msg.id, { status: 'Context gelöscht' }); + sendMonitorEvent('hook', 'Sticky Context gelöscht', {}); break; case 'stop': diff --git a/src-tauri/src/claude.rs b/src-tauri/src/claude.rs index 93793fa..66125ca 100644 --- a/src-tauri/src/claude.rs +++ b/src-tauri/src/claude.rs @@ -7,6 +7,7 @@ use std::process::{Command, Stdio}; use std::sync::{Arc, Mutex}; use tauri::{AppHandle, Emitter, Manager}; +use crate::context; use crate::db; /// Status eines Agents @@ -258,6 +259,11 @@ 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) +} + +/// Befehl an Bridge senden mit optionalem Context +fn send_to_bridge_with_context(app: &AppHandle, command: &str, message: &str, context: Option) -> Result { let state = app.state::>>(); let mut state = state.lock().unwrap(); @@ -271,6 +277,25 @@ fn send_to_bridge(app: &AppHandle, command: &str, message: &str) -> Result { + let mut payload = serde_json::json!({ + "command": command, + "id": request_id, + "message": message + }); + // Context hinzufügen wenn vorhanden + if let Some(ctx) = context { + if !ctx.is_empty() { + payload["context"] = serde_json::Value::String(ctx); + } + } + payload + }, + "set-context" | "clear-context" => serde_json::json!({ + "command": command, + "id": request_id, + "context": message + }), _ => serde_json::json!({ "command": command, "id": request_id, @@ -307,12 +332,70 @@ pub async fn send_message(app: AppHandle, message: String) -> Result Option { + use crate::context; + + // Versuche Context zu laden + if let Some(db_state) = app.try_state::>>() { + let db = db_state.lock().ok()?; + + // Context-Tabellen erstellen falls nicht vorhanden + let _ = db.create_context_tables(); + + // Sticky Context laden + let entries = db.load_sticky_context().ok()?; + + if entries.is_empty() { + return None; + } + + let mut sticky = context::StickyContext::default(); + + for (key, value, _priority) in entries { + match key.as_str() { + "user_info" => sticky.user_info = Some(value), + k if k.starts_with("cred:") => { + if let Ok(cred) = serde_json::from_str::(&value) { + sticky.active_credentials.push(cred); + } + } + k if k.starts_with("project:") => { + if let Ok(proj) = serde_json::from_str::(&value) { + sticky.current_project = Some(proj); + } + } + k if k.starts_with("rule:") => { + sticky.critical_rules.push(value); + } + _ => {} + } + } + + let rendered = sticky.render(); + if rendered.is_empty() { + None + } else { + Some(rendered) + } + } else { + None + } +} + /// Alle Agents stoppen #[tauri::command] pub async fn stop_all_agents(app: AppHandle) -> Result<(), String> { diff --git a/src/lib/components/DiffView.svelte b/src/lib/components/DiffView.svelte new file mode 100644 index 0000000..1a2a718 --- /dev/null +++ b/src/lib/components/DiffView.svelte @@ -0,0 +1,242 @@ + + +
+ {#if filename} +
+ {filename} + {#if language} + {language} + {/if} + + +{stats.added} + -{stats.removed} + +
+ {/if} + +
+ {#each diffLines as line, idx (idx)} +
+ {line.lineNo.old ?? ''} + {line.lineNo.new ?? ''} + + {#if line.type === 'added'}+{:else if line.type === 'removed'}-{:else}{/if} + + {line.text || '\u00A0'} +
+ {/each} +
+
+ + diff --git a/src/lib/components/FilePreview.svelte b/src/lib/components/FilePreview.svelte new file mode 100644 index 0000000..2e122ed --- /dev/null +++ b/src/lib/components/FilePreview.svelte @@ -0,0 +1,266 @@ + + +
+
+ {getFileIcon(filename)} + {filename} + {language} + {lines.length} Zeilen +
+ + +
+
+ + {#if !isCollapsed} +
+
+ {#each displayLines as _, idx} + {startLine + idx} + {/each} +
+
{displayLines.join('\n')}
+
+ + {#if truncated} +
+ ... {lines.length - maxLines} weitere Zeilen nicht angezeigt +
+ {/if} + {/if} +
+ +