fix: sessionId durchreichen bei target_tab/sync/risk_request

Ereignisse ohne sessionId passierten den Panel-Filter ungehindert, wodurch
ein frisch geoeffneter Chat den Arbeits-Tab oder Verlauf eines parallel
laufenden Chats angezeigt bekam.
This commit is contained in:
2026-09-01 09:40:00 +02:00
parent 02d3d11523
commit ef180164ea
2 changed files with 41 additions and 24 deletions
+39 -22
View File
@@ -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<void> {
const targetTabId = getTargetTabId();
async function notifyTargetTab(sessionId?: string): Promise<void> {
// 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<void> {
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<string, unknown>, 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<string, unknown>, sessionId?:
}
/** Fragt den Nutzer. Ohne offenes Panel wird gehandelt — Autonomie hat Vorrang. */
function askUser(verdict: RiskVerdict, toolName: string): Promise<boolean> {
function askUser(verdict: RiskVerdict, toolName: string, sessionId?: string): Promise<boolean> {
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<boolean>((resolve) => {
const timer = setTimeout(() => {
@@ -505,7 +512,7 @@ async function getActiveTab(sessionId?: string): Promise<chrome.tabs.Tab> {
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<chrome.tabs.Tab> {
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<string, unknown>, 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<string, unknown>, 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<string, unknown>, 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(),
+2 -2
View File
@@ -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);