diff --git a/src/background/index.ts b/src/background/index.ts index 359c4fd..30012c2 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -386,15 +386,19 @@ function reportToolDone( } /** Sendet den aktuellen Target-Tab ans Panel und überträgt den Glow auf den neuen Tab. */ -async function notifyTargetTab(): Promise { - const targetTabId = getTargetTabId(); +async function notifyTargetTab(sessionId?: string): Promise { + // sessionId mitstempeln: ohne sie zeigte ein frisch geöffneter Chat den + // Arbeits-Tab eines ANDEREN, parallel laufenden Chats an — Ereignisse ohne + // sessionId passieren den Filter im Panel ungehindert. + const sid = sessionId ?? activeSessionId; + const targetTabId = getTargetTabId(sid); if (targetTabId === null) { - sendToPanel({ type: 'target_tab', tabId: null }); + sendToPanel({ type: 'target_tab', tabId: null, sessionId: sid }); return; } try { const tab = await chrome.tabs.get(targetTabId); - sendToPanel({ type: 'target_tab', tabId: targetTabId, title: tab.title, url: tab.url }); + sendToPanel({ type: 'target_tab', tabId: targetTabId, title: tab.title, url: tab.url, sessionId: sid }); // Nur während eines echten Laufs anzeigen. if (agentRunActive) notifyGlow(true); // Tab in den Vordergrund bringen wenn focusWorkingTab aktiv @@ -403,7 +407,7 @@ async function notifyTargetTab(): Promise { await chrome.tabs.update(targetTabId, { active: true }).catch(() => {}); } } catch { - sendToPanel({ type: 'target_tab', tabId: null }); + sendToPanel({ type: 'target_tab', tabId: null, sessionId: sid }); } } @@ -434,7 +438,7 @@ async function runTool(name: string, input: Record, sessionId?: } const tabId = await currentTabId(sessionId); - const { allowed, verdict } = await gate(tabId, name, input, (v) => askUser(v, name)); + const { allowed, verdict } = await gate(tabId, name, input, (v) => askUser(v, name, sessionId)); if (!allowed) { return { @@ -464,11 +468,14 @@ async function runTool(name: string, input: Record, sessionId?: } /** Fragt den Nutzer. Ohne offenes Panel wird gehandelt — Autonomie hat Vorrang. */ -function askUser(verdict: RiskVerdict, toolName: string): Promise { +function askUser(verdict: RiskVerdict, toolName: string, sessionId?: string): Promise { if (!panelPort) return Promise.resolve(true); const requestId = crypto.randomUUID(); - sendToPanel({ type: 'risk_request', requestId, name: toolName, verdict, timeoutMs: RISK_TIMEOUT_MS }); + sendToPanel({ + type: 'risk_request', requestId, name: toolName, verdict, + timeoutMs: RISK_TIMEOUT_MS, sessionId: sessionId ?? activeSessionId, + }); return new Promise((resolve) => { const timer = setTimeout(() => { @@ -505,7 +512,7 @@ async function getActiveTab(sessionId?: string): Promise { const [candidate] = await chrome.tabs.query({ active: true, currentWindow: true }); if (candidate?.id != null && await tabOwner(candidate.id, sessionId) === 'own') { setTargetTabId(candidate.id, sessionId); - notifyTargetTab(); + notifyTargetTab(sessionId); return candidate; } @@ -516,14 +523,14 @@ async function getActiveTab(sessionId?: string): Promise { if (!hasOwnGroup && candidate?.id != null && await tabOwner(candidate.id, sessionId) === 'user') { setTargetTabId(candidate.id, sessionId); - notifyTargetTab(); + notifyTargetTab(sessionId); return candidate; } const fresh = await chrome.tabs.create({ url: 'about:blank', active: false }); if (!fresh.id) throw new ToolError('NO_ACTIVE_TAB', 'Kein Tab verfügbar', false); setTargetTabId(fresh.id, sessionId); - notifyTargetTab(); + notifyTargetTab(sessionId); return fresh; } @@ -970,11 +977,11 @@ async function dispatchTool(name: string, input: Record, sessio if (input.newTab) { const newTab = await chrome.tabs.create({ url, active: false }); setTargetTabId(newTab.id!); - notifyTargetTab(); + notifyTargetTab(sessionId); return { tabId: newTab.id, url, loading: true, hint: 'Seite lädt — browser_wait oder screenshot nach kurzer Pause.', _method: 'synthetic' }; } await chrome.tabs.update(tabId!, { url }); - notifyTargetTab(); + notifyTargetTab(sessionId); return { tabId, url, loading: true, hint: 'Seite lädt — browser_wait oder screenshot nach kurzer Pause.', _method: 'synthetic' }; } @@ -1019,7 +1026,7 @@ async function dispatchTool(name: string, input: Record, sessio // sessionId mitgeben: sonst landet der neue Tab in der Gruppe der gerade // SICHTBAREN Konversation statt in der des aufrufenden Chats. setTargetTabId(newTab.id!, sessionId); - notifyTargetTab(); + notifyTargetTab(sessionId); return { tabId: newTab.id, url, redirectedFrom: url !== requestedUrl ? requestedUrl : undefined, _method: 'synthetic' }; } @@ -1037,7 +1044,7 @@ async function dispatchTool(name: string, input: Record, sessio await chrome.windows.update(t.windowId!, { focused: true }); } setTargetTabId(tid, sessionId); - notifyTargetTab(); + notifyTargetTab(sessionId); return { ok: true, tabId: tid, _method: 'synthetic' }; } @@ -1912,7 +1919,7 @@ chrome.runtime.onConnect.addListener((port) => { } else { // Der Broker-Kanal kennt keine Bilder — lieber sagen als still schlucken. if (msg.images?.length) { - sendToPanel({ type: 'log', level: 'warn', + sendToPanel({ type: 'log', level: 'warn', sessionId, text: `${msg.images.length} Bild(er) ignoriert: Bildanhänge gehen nur im Antrieb "direct".` }); } // Aktuelle Tab-URL mitschicken — der Broker baut daraus den Kontext (z.B. Mercedes-Wissen). @@ -1929,12 +1936,22 @@ chrome.runtime.onConnect.addListener((port) => { } case 'sync': { // Nachliefern statt neu erzeugen: der Puffer ist die Wahrheit. + // ABER nur die Ereignisse DIESER Konversation: der Puffer ist global, + // ein frisch geöffneter Chat bekam sonst den kompletten Verlauf aller + // parallel laufenden Chats nachgespielt ("es fühlt sich wie eine an"). + // Ereignisse ohne sessionId sind global (connection_status u.ä.) und + // bleiben drin. const from = typeof msg.from === 'number' ? msg.from : 0; - const missed = eventBuffer.filter(e => e.seq > from); + const sid = (msg as any).sessionId || activeSessionId; + const missed = eventBuffer.filter(e => { + if (e.seq <= from) return false; + const evSid = (e.msg as any).sessionId; + return !evSid || evSid === sid; + }); if (panelPort) { try { panelPort.postMessage({ - type: 'sync_start', count: missed.length, running: isAgentRunning(), + type: 'sync_start', count: missed.length, running: isAgentRunning(sid), latest: eventSeq, startedAt: runStartedAt || undefined, }); // Direkt posten, nicht ueber sendToPanel — sonst landet alles erneut im Puffer. @@ -1962,17 +1979,17 @@ chrome.runtime.onConnect.addListener((port) => { case 'set_config': { const next = await patchConfig(msg.patch); if (msg.patch.brokerUrl) { ws?.close(); setTimeout(connect, 300); } - sendToPanel({ type: 'state', config: next, connected: ws?.readyState === WebSocket.OPEN, running: isAgentRunning(), version: EXTENSION_VERSION, runningSessions: getRunningSessions() }); + sendToPanel({ type: 'state', config: next, connected: ws?.readyState === WebSocket.OPEN, running: isAgentRunning(activeSessionId), version: EXTENSION_VERSION, sessionId: activeSessionId, runningSessions: getRunningSessions() }); break; } case 'get_state': { const cfg = await loadConfig(); - sendToPanel({ type: 'state', config: cfg, connected: ws?.readyState === WebSocket.OPEN, running: isAgentRunning(), version: EXTENSION_VERSION, runningSessions: getRunningSessions() }); + sendToPanel({ type: 'state', config: cfg, connected: ws?.readyState === WebSocket.OPEN, running: isAgentRunning(activeSessionId), version: EXTENSION_VERSION, sessionId: activeSessionId, runningSessions: getRunningSessions() }); break; } case 'run_tool': { const callId = crypto.randomUUID(); - sendToPanel({ type: 'tool_executing', callId, name: msg.name, input: msg.input }); + sendToPanel({ type: 'tool_executing', callId, name: msg.name, input: msg.input, sessionId: activeSessionId }); const started = performance.now(); try { const result = await runTool(msg.name, msg.input); @@ -1996,7 +2013,7 @@ chrome.runtime.onConnect.addListener((port) => { const sid = (msg as any).sessionId; if (sid) { activeSessionId = sid; - notifyTargetTab(); + notifyTargetTab(sid); sendToPanel({ type: 'state', config: await loadConfig(), diff --git a/src/sidepanel/app.ts b/src/sidepanel/app.ts index a15ea4f..d32022d 100644 --- a/src/sidepanel/app.ts +++ b/src/sidepanel/app.ts @@ -212,7 +212,7 @@ function openPort(): void { } port = p; // Alles nachfordern, was waehrend geschlossenem Panel aufgelaufen ist. - try { p.postMessage({ type: 'sync', from: lastSeq }); } catch { /* Reconnect greift */ } + try { p.postMessage({ type: 'sync', from: lastSeq, sessionId: activeConvId }); } catch { /* Reconnect greift */ } p.onMessage.addListener((msg) => { // Sequenznummer merken: nach einem Abriss wird genau ab hier nachgeliefert. @@ -2263,7 +2263,7 @@ function showRunningBanner(startedAt?: number): void { bar.remove(); // Alles ab Null nachfordern: der Puffer im Worker hat den vollen Lauf. lastSeq = 0; - try { port?.postMessage({ type: 'sync', from: 0 }); } catch { /* egal */ } + try { port?.postMessage({ type: 'sync', from: 0, sessionId: activeConvId }); } catch { /* egal */ } }); bar.append(btn); appendToChat(bar);