Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions apps/agent/entrypoints/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ export default defineBackground(() => {

scheduledJobRuns()

// Track which tabs belong to each agent conversation for side panel management
const agentTabSets = new Map<string, Set<number>>()

chrome.tabs.onRemoved.addListener((tabId) => {
for (const [conversationId, tabSet] of agentTabSets) {
tabSet.delete(tabId)
// Prune empty entries to prevent unbounded growth
if (tabSet.size === 0) agentTabSets.delete(conversationId)
}
})

chrome.action.onClicked.addListener(async (tab) => {
if (tab.id) {
await toggleSidePanel(tab.id)
Expand Down Expand Up @@ -91,6 +102,29 @@ export default defineBackground(() => {
timestamp: Date.now(),
})
}

// Open side panel on tabs the agent interacts with
if (
message?.type === 'open-sidepanel-on-tab' &&
message?.tabId &&
message?.conversationId
) {
const { tabId, conversationId } = message
let tabSet = agentTabSets.get(conversationId)
if (!tabSet) {
tabSet = new Set()
agentTabSets.set(conversationId, tabSet)
}
if (!tabSet.has(tabId)) {
tabSet.add(tabId)
openSidePanel(tabId).catch(() => {})
}
}

// Clean up tab tracking when conversation resets
if (message?.type === 'clear-agent-tabs' && message?.conversationId) {
agentTabSets.delete(message.conversationId)
}
})

sessionStorage.watch(async (newSession) => {
Expand Down
7 changes: 7 additions & 0 deletions apps/agent/entrypoints/sidepanel/index/useChatSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,13 @@ export const useChatSession = (options?: ChatSessionOptions) => {
const resetConversation = () => {
track(CONVERSATION_RESET_EVENT, { message_count: messages.length })
stop()
// Clear agent tab tracking in background before generating new conversationId
chrome.runtime
.sendMessage({
type: 'clear-agent-tabs',
conversationId: conversationIdRef.current,
})
.catch(() => {})
setConversationId(crypto.randomUUID())
setMessages([])
setTextToAction(new Map())
Expand Down
18 changes: 18 additions & 0 deletions apps/agent/entrypoints/sidepanel/index/useNotifyActiveTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const useNotifyActiveTab = ({
conversationId: string
}) => {
const lastTabIdRef = useRef<number | null>(null)
const knownTabsRef = useRef<Set<number>>(new Set())

const lastMessage = messages?.[messages.length - 1]

Expand All @@ -39,6 +40,11 @@ export const useNotifyActiveTab = ({
const hasToolCalls = !!latestTool
const toolTabId = extractTabId(latestTool as ToolUIPart | null)

// biome-ignore lint/correctness/useExhaustiveDependencies: intentionally reset when conversationId changes
useEffect(() => {
knownTabsRef.current = new Set()
}, [conversationId])

useEffect(() => {
const isStreaming = status === 'streaming'
const previousTabId = lastTabIdRef.current
Expand Down Expand Up @@ -82,6 +88,18 @@ export const useNotifyActiveTab = ({

if (cancelled || !targetTabId) return

// Open side panel on tabs the agent hasn't seen yet
if (!knownTabsRef.current.has(targetTabId)) {
knownTabsRef.current.add(targetTabId)
chrome.runtime
.sendMessage({
type: 'open-sidepanel-on-tab',
tabId: targetTabId,
conversationId,
})
.catch(() => {})
}

if (previousTabId && previousTabId !== targetTabId) {
const deactivateMessage: GlowMessage = {
conversationId,
Expand Down
Loading