diff --git a/packages/browser-extension/README.md b/packages/browser-extension/README.md index 1211c964..06fb7e3f 100644 --- a/packages/browser-extension/README.md +++ b/packages/browser-extension/README.md @@ -1182,3 +1182,7 @@ Error: Refused to evaluate a string as JavaScript because this document requires --- This CSP section should help both developers and LLMs understand the security constraints when working on extension features, especially those involving dynamic code execution or user-generated content. + +## Known Bugs + +- **PersistentStorageDialog**: Currently broken and commented out in sidepanel.ts. The dialog for requesting persistent storage does not work correctly and needs to be fixed. diff --git a/packages/browser-extension/src/sidepanel.ts b/packages/browser-extension/src/sidepanel.ts index fde6942f..054407ff 100644 --- a/packages/browser-extension/src/sidepanel.ts +++ b/packages/browser-extension/src/sidepanel.ts @@ -10,7 +10,7 @@ import { AppStorage, ChatPanel, ChromeStorageBackend, - PersistentStorageDialog, + // PersistentStorageDialog, // TODO: Fix - currently broken ProviderTransport, ProxyTab, SessionIndexedDBBackend, @@ -71,9 +71,9 @@ let agent: Agent; let chatPanel: ChatPanel; let agentUnsubscribe: (() => void) | undefined; -// Track last navigation for inserting navigation messages -let lastSubmittedUrl: string | undefined; -let lastSubmittedTabIndex: number | undefined; +// Track current active tab for real-time navigation updates +let currentTabUrl: string | undefined; +let currentTabIndex: number | undefined; // ============================================================================ // HELPERS @@ -204,7 +204,7 @@ const renderApp = () => { loadSession(sessionId); }, (deletedSessionId) => { - // If the deleted session is the current one, start a new session + // Only reload if the current session was deleted if (deletedSessionId === currentSessionId) { newSession(); } @@ -294,6 +294,41 @@ const renderApp = () => { render(appHtml, document.body); }; +// ============================================================================ +// TAB NAVIGATION TRACKING +// ============================================================================ + +// Listen for tab updates to insert navigation messages in real-time +chrome.tabs.onUpdated.addListener((_tabId, changeInfo, tab) => { + // Only care about URL changes on the active tab + if (changeInfo.url && tab.active && tab.url) { + handleTabNavigation(tab.url, tab.title || "Untitled", tab.favIconUrl, tab.index); + } +}); + +// Listen for tab activation (user switches tabs) +chrome.tabs.onActivated.addListener(async (activeInfo) => { + const tab = await chrome.tabs.get(activeInfo.tabId); + if (tab.url) { + handleTabNavigation(tab.url, tab.title || "Untitled", tab.favIconUrl, tab.index); + } +}); + +function handleTabNavigation(url: string, title: string, favicon?: string, tabIndex?: number) { + // Update current tab tracking + const urlChanged = currentTabUrl !== url; + const tabChanged = currentTabIndex !== tabIndex; + + currentTabUrl = url; + currentTabIndex = tabIndex; + + // Only insert navigation message if something changed and we have an agent + if ((urlChanged || tabChanged) && agent) { + const navMessage = createNavigationMessage(url, title, favicon, tabIndex); + agent.appendMessage(navMessage); + } +} + // ============================================================================ // INIT // ============================================================================ @@ -308,10 +343,11 @@ async function initApp() { document.body, ); + // TODO: Fix PersistentStorageDialog - currently broken // Request persistent storage - if (storage.sessions) { - await PersistentStorageDialog.request(); - } + // if (storage.sessions) { + // await PersistentStorageDialog.request(); + // } // Create ChatPanel chatPanel = new ChatPanel(); @@ -322,21 +358,33 @@ async function initApp() { chatPanel.onBeforeSend = async () => { // Get current tab info const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); - if (!tab || !tab.url) return; + if (!tab?.url) return; - // Check if navigation changed since last submit - if (lastSubmittedUrl !== tab.url || lastSubmittedTabIndex !== tab.index) { - // Insert navigation message + // Find last navigation message in messages (reverse loop) + const messages = agent.state.messages; + let lastNavMessage: ReturnType | undefined; + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i].role === "navigation") { + lastNavMessage = messages[i] as ReturnType; + break; + } + } + + // Only insert if URL or tab changed + if (lastNavMessage?.url !== tab.url || lastNavMessage?.tabIndex !== tab.index) { const navMessage = createNavigationMessage(tab.url, tab.title || "Untitled", tab.favIconUrl, tab.index); agent.appendMessage(navMessage); - - // Update tracking - lastSubmittedUrl = tab.url; - lastSubmittedTabIndex = tab.index; } }; chatPanel.additionalTools = [browserJavaScriptTool]; + // Initialize current tab state + const [currentTab] = await chrome.tabs.query({ active: true, currentWindow: true }); + if (currentTab?.url) { + currentTabUrl = currentTab.url; + currentTabIndex = currentTab.index; + } + // Check for session in URL const urlParams = new URLSearchParams(window.location.search); let sessionIdFromUrl = urlParams.get("session"); diff --git a/packages/web-ui/README.md b/packages/web-ui/README.md index 73a1684e..00676dd1 100644 --- a/packages/web-ui/README.md +++ b/packages/web-ui/README.md @@ -322,6 +322,10 @@ setAppStorage(storage); See [src/index.ts](src/index.ts) for the full public API. +## Known Bugs + +- **PersistentStorageDialog**: Currently broken and commented out in examples. The dialog for requesting persistent storage does not work correctly and needs to be fixed. + ## License MIT diff --git a/packages/web-ui/example/src/main.ts b/packages/web-ui/example/src/main.ts index 1e795a0f..982821c4 100644 --- a/packages/web-ui/example/src/main.ts +++ b/packages/web-ui/example/src/main.ts @@ -9,7 +9,7 @@ import { type AppMessage, AppStorage, ChatPanel, - PersistentStorageDialog, + // PersistentStorageDialog, // TODO: Fix - currently broken ProviderTransport, ProxyTab, SessionIndexedDBBackend, @@ -192,7 +192,7 @@ const renderApp = () => { await loadSession(sessionId); }, (deletedSessionId) => { - // If the deleted session is the current one, start a new session + // Only reload if the current session was deleted if (deletedSessionId === currentSessionId) { newSession(); } @@ -311,10 +311,11 @@ async function initApp() { app, ); + // TODO: Fix PersistentStorageDialog - currently broken // Request persistent storage - if (storage.sessions) { - await PersistentStorageDialog.request(); - } + // if (storage.sessions) { + // await PersistentStorageDialog.request(); + // } // Create ChatPanel chatPanel = new ChatPanel(); diff --git a/packages/web-ui/src/dialogs/SessionListDialog.ts b/packages/web-ui/src/dialogs/SessionListDialog.ts index 1fa17735..c17f834d 100644 --- a/packages/web-ui/src/dialogs/SessionListDialog.ts +++ b/packages/web-ui/src/dialogs/SessionListDialog.ts @@ -12,6 +12,8 @@ export class SessionListDialog extends DialogBase { private onSelectCallback?: (sessionId: string) => void; private onDeleteCallback?: (sessionId: string) => void; + private deletedSessions = new Set(); + private closedViaSelection = false; protected modalWidth = "min(600px, 90vw)"; protected modalHeight = "min(700px, 90vh)"; @@ -57,16 +59,26 @@ export class SessionListDialog extends DialogBase { await storage.sessions.deleteSession(sessionId); await this.loadSessions(); - // Notify callback that session was deleted - if (this.onDeleteCallback) { - this.onDeleteCallback(sessionId); - } + // Track deleted session + this.deletedSessions.add(sessionId); } catch (err) { console.error("Failed to delete session:", err); } } + override close() { + super.close(); + + // Only notify about deleted sessions if dialog wasn't closed via selection + if (!this.closedViaSelection && this.onDeleteCallback && this.deletedSessions.size > 0) { + for (const sessionId of this.deletedSessions) { + this.onDeleteCallback(sessionId); + } + } + } + private handleSelect(sessionId: string) { + this.closedViaSelection = true; if (this.onSelectCallback) { this.onSelectCallback(sessionId); }