fix: Glow-Bug + focusWorkingTab-Option

Glow-Fix (3 Ursachen behoben):
- notifyGlow: schickt jetzt an targetTabId statt an den aktiven Tab des
  Nutzers. Vorher: Agent arbeitet in Tab B, Glow geht an Tab A (den der
  Nutzer gerade sieht) — Tab B leuchtet nie.
- executeForBroker: notifyGlow(true) beim ersten Tool-Call, nicht erst
  bei 'round'. Vorher: targetTabId war bei 'round' noch null, currentTabId()
  gab undefined zurück, Glow wurde nie gesendet.
- notifyTargetTab: Glow auf neuen Tab übertragen wenn Agent Tab wechselt.

focusWorkingTab (neue Config-Option):
- ExtensionConfig.focusWorkingTab: boolean (Default false)
- notifyTargetTab: bringt Tab in Vordergrund wenn focusWorkingTab=true
- Checkbox 'Tab live mitverfolgen' in Einstellungen > Anzeige & Sicherheit
- false = Agent arbeitet im Hintergrund (bisheriges Verhalten)
- true = Tab wird bei jedem Wechsel sichtbar
This commit is contained in:
2026-08-10 10:02:37 +02:00
parent 160e2c884d
commit 8d03f36b0e
4 changed files with 34 additions and 2 deletions
+16 -2
View File
@@ -183,6 +183,10 @@ async function executeForBroker(callId: string, name: string, input: Record<stri
const start = performance.now(); const start = performance.now();
let method: InteractionMethod = 'synthetic'; let method: InteractionMethod = 'synthetic';
// Glow beim ersten Tool-Call einschalten — zu diesem Zeitpunkt ist targetTabId
// noch null (wird erst in getActiveTab gesetzt), daher aktiven Tab nehmen.
notifyGlow(true);
try { try {
sendToPanel({ type: 'tool_executing', callId, name, input }); sendToPanel({ type: 'tool_executing', callId, name, input });
const result = await runTool(name, input); const result = await runTool(name, input);
@@ -214,7 +218,7 @@ function reportToolDone(
sendToPanel({ type: 'tool_done', callId, name, result, error, durationMs, method }); sendToPanel({ type: 'tool_done', callId, name, result, error, durationMs, method });
} }
/** Sendet den aktuellen Target-Tab ans Panel. */ /** Sendet den aktuellen Target-Tab ans Panel und überträgt den Glow auf den neuen Tab. */
async function notifyTargetTab(): Promise<void> { async function notifyTargetTab(): Promise<void> {
if (targetTabId === null) { if (targetTabId === null) {
sendToPanel({ type: 'target_tab', tabId: null }); sendToPanel({ type: 'target_tab', tabId: null });
@@ -223,6 +227,13 @@ async function notifyTargetTab(): Promise<void> {
try { try {
const tab = await chrome.tabs.get(targetTabId); 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 });
// Glow auf den neuen Arbeits-Tab übertragen (falls Agent gerade läuft)
notifyGlow(true);
// Tab in den Vordergrund bringen wenn focusWorkingTab aktiv
const cfg = await loadConfig();
if (cfg.focusWorkingTab) {
await chrome.tabs.update(targetTabId, { active: true }).catch(() => {});
}
} catch { } catch {
sendToPanel({ type: 'target_tab', tabId: null }); sendToPanel({ type: 'target_tab', tabId: null });
} }
@@ -724,7 +735,10 @@ function notifyCursor(tabId: number, x: number, y: number, click?: 'left' | 'rig
async function notifyGlow(on: boolean): Promise<void> { async function notifyGlow(on: boolean): Promise<void> {
const cfg = await loadConfig(); const cfg = await loadConfig();
if (cfg.pageOverlay !== 'glow' && cfg.pageOverlay !== 'both') return; if (cfg.pageOverlay !== 'glow' && cfg.pageOverlay !== 'both') return;
const id = await currentTabId().catch(() => undefined);
// Glow an den Arbeits-Tab schicken, nicht an den aktiven Tab des Nutzers.
// targetTabId ist null wenn noch kein Tool gelaufen ist — dann aktiven Tab nehmen.
const id = targetTabId ?? await currentTabId().catch(() => undefined);
if (typeof id !== 'number') return; if (typeof id !== 'number') return;
chrome.tabs.sendMessage(id, { type: 'NEXUS_GLOW', on }).catch(() => {}); chrome.tabs.sendMessage(id, { type: 'NEXUS_GLOW', on }).catch(() => {});
} }
+8
View File
@@ -159,6 +159,13 @@ export interface ExtensionConfig {
* zurückholen. Dieser Riegel greift unabhängig vom Risikomodus. * zurückholen. Dieser Riegel greift unabhängig vom Risikomodus.
*/ */
blockMailSend: boolean; blockMailSend: boolean;
/**
* Arbeits-Tab in den Vordergrund bringen wenn der Agent ihn wechselt.
* false (Standard) = Agent arbeitet im Hintergrund, Nutzer kann anderen Tab nutzen.
* true = Tab wird bei jedem Wechsel sichtbar — gut zum Live-Zuschauen.
*/
focusWorkingTab: boolean;
} }
export const DEFAULT_SYSTEM_PROMPT = `Du steuerst einen echten Browser wie ein Mensch: mit Mauszeiger und Tastatur. export const DEFAULT_SYSTEM_PROMPT = `Du steuerst einen echten Browser wie ein Mensch: mit Mauszeiger und Tastatur.
@@ -328,6 +335,7 @@ export const DEFAULT_CONFIG: ExtensionConfig = {
memoryToken: '', memoryToken: '',
memoryEnabled: false, memoryEnabled: false,
blockMailSend: true, blockMailSend: true,
focusWorkingTab: false,
}; };
const STORAGE_KEY = 'nexus_ext_config'; const STORAGE_KEY = 'nexus_ext_config';
+6
View File
@@ -88,6 +88,7 @@ const paceMsInput = need<HTMLInputElement>('paceMs');
const showCursorInput = need<HTMLInputElement>('showCursor'); const showCursorInput = need<HTMLInputElement>('showCursor');
const pageOverlaySelect = need<HTMLSelectElement>('pageOverlay'); const pageOverlaySelect = need<HTMLSelectElement>('pageOverlay');
const blockMailSendInput = need<HTMLInputElement>('blockMailSend'); const blockMailSendInput = need<HTMLInputElement>('blockMailSend');
const focusWorkingTabInput = need<HTMLInputElement>('focusWorkingTab');
const providerSelect = need<HTMLSelectElement>('provider'); const providerSelect = need<HTMLSelectElement>('provider');
const baseUrlInput = need<HTMLInputElement>('baseUrl'); const baseUrlInput = need<HTMLInputElement>('baseUrl');
const apiKeyInput = need<HTMLInputElement>('apiKey'); const apiKeyInput = need<HTMLInputElement>('apiKey');
@@ -379,6 +380,7 @@ function applyConfigToFields(cfg: ExtensionConfig): void {
setFieldValue(paceMsInput, String(cfg.paceMs)); setFieldValue(paceMsInput, String(cfg.paceMs));
if (document.activeElement !== showCursorInput) showCursorInput.checked = cfg.showCursor; if (document.activeElement !== showCursorInput) showCursorInput.checked = cfg.showCursor;
if (document.activeElement !== blockMailSendInput) blockMailSendInput.checked = cfg.blockMailSend; if (document.activeElement !== blockMailSendInput) blockMailSendInput.checked = cfg.blockMailSend;
if (document.activeElement !== focusWorkingTabInput) focusWorkingTabInput.checked = cfg.focusWorkingTab ?? false;
setFieldValue(providerSelect, cfg.provider); setFieldValue(providerSelect, cfg.provider);
setFieldValue(baseUrlInput, cfg.baseUrl); setFieldValue(baseUrlInput, cfg.baseUrl);
@@ -685,6 +687,10 @@ function wireSettings(): void {
patchConfigValue({ showCursor: showCursorInput.checked }); patchConfigValue({ showCursor: showCursorInput.checked });
}); });
focusWorkingTabInput.addEventListener('change', () => {
patchConfigValue({ focusWorkingTab: focusWorkingTabInput.checked });
});
blockMailSendInput.addEventListener('change', () => { blockMailSendInput.addEventListener('change', () => {
patchConfigValue({ blockMailSend: blockMailSendInput.checked }); patchConfigValue({ blockMailSend: blockMailSendInput.checked });
if (!blockMailSendInput.checked) { if (!blockMailSendInput.checked) {
+4
View File
@@ -1099,6 +1099,10 @@
<input type="checkbox" id="showCursor"> <input type="checkbox" id="showCursor">
<label for="showCursor">Mauszeiger sichtbar einblenden</label> <label for="showCursor">Mauszeiger sichtbar einblenden</label>
</div> </div>
<div class="row check">
<input type="checkbox" id="focusWorkingTab">
<label for="focusWorkingTab" title="Tab bei jedem Wechsel in den Vordergrund bringen — gut zum Live-Zuschauen. Aus = Agent arbeitet im Hintergrund.">Tab live mitverfolgen (Vordergrund)</label>
</div>
<div class="row check"> <div class="row check">
<input type="checkbox" id="blockMailSend"> <input type="checkbox" id="blockMailSend">
<label for="blockMailSend" title="Klicks auf Senden-Schaltflächen in Mail-Oberflächen werden abgelehnt. Der Agent legt nur Entwürfe an.">Senden in Mails blockieren (nur Entwürfe)</label> <label for="blockMailSend" title="Klicks auf Senden-Schaltflächen in Mail-Oberflächen werden abgelehnt. Der Agent legt nur Entwürfe an.">Senden in Mails blockieren (nur Entwürfe)</label>