Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Agent",
"version": "50.1.0.2",
"version": "50.1.1.0",
"description": "Agent",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs1zULZz5eE0U8SEjr/R++dlx6WKFj7GbpnBiE1n17gaylMWDlw6uuBJNjcRrSGwOt53Z3PKf2T3g5DtNES8q6rQc11P/y8J8GKhKuqGrtRJyk5iXzcKJk4CHz6leFSMt8CsZY0r0b7wCZ5QuhomTHGQpNWNS0c13xfVqWt4dncfIRj7fMzfTkicq7Mqqx+JcdprLkiVfETvdkMwwEWmSNwQ6nCDzLtTbyyMiGUEBSJs+WlP1fO7LIX0sHesFVxfPhCZ2K4F1biwenbRL+YYD60ogpVppop2ee/W3D211IN1zYxgnhycFv3m8TrzG+MD/IZgcu13u0bHRn3V7IGW1iwIDAQAB",
"permissions": [
Expand Down
10 changes: 10 additions & 0 deletions src/lib/agent/BrowserAgent.prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ Tab Control:
- tab_open(url?): Open new tab
- tab_focus(tabId): Switch to specific tab
- tab_close(tabId): Close tab
- create_tab_group(name, color?, tabIds?): Create a new tab group with a name and color
- list_tab_groups(): List all existing tab groups with their IDs, names, and tab counts
- add_tab_to_group(tabId, groupId): Add a specific tab to an existing tab group

Data Operations:
- extract(format, task): Extract structured data matching JSON schema
Expand Down Expand Up @@ -313,6 +316,8 @@ Example: Use "Use MCP to search Gmail for unread emails" instead of "Navigate to
# EXAMPLES OF EFFECTIVE (GOOD) ACTIONS

- Use BrowserOS info tool to retrieve agent details
- Use list_tab_groups() first, then create_tab_group("Work", "blue") for a single category, and add_tab_to_group() for each tab belonging to that category. Then repeat for other categories.
- Use create_tab_group("Social Media", "pink") followed by multiple add_tab_to_group() calls to organize related tabs
- Use MCP to search Gmail for unread emails
- Use MCP to get today's Google Calendar events
- Use MCP to read data from a specific Google Sheet
Expand All @@ -328,6 +333,7 @@ Example: Use "Use MCP to search Gmail for unread emails" instead of "Navigate to

- Click element [123] (do not reference node IDs directly; executor agent determines this)
- Type into nodeId 456 (do not reference node IDs directly; executor agent determines this)
- Navigate to chrome://tab-groups/ or chrome://tabs to organize tabs (use the tools instead)
- Add Farmhouse Pepperoni Pizza to the cart when the button is hidden in the screenshot (instead, scroll down, check updated screenshot and then propose the action)
- Navigate to a generic site (e.g., "Go to a pizza website") without specifying the actual URL

Expand Down Expand Up @@ -370,6 +376,10 @@ export function getToolDescriptions(isLimitedContextMode: boolean = false): stri
- tab_open: Open new browser tabs
- tab_focus: Switch between tabs
- tab_close: Close browser tabs
- create_tab_group: Create a new tab group with name and color
- list_tab_groups: List all existing tab groups
- add_tab_to_group: Add a specific tab to an existing tab group
- get_selected_tabs_tool: Get information about currently selected tabs
- extract: Extract data from web pages
- celebration: Show confetti animation
- human_input: Request human assistance
Expand Down
9 changes: 8 additions & 1 deletion src/lib/agent/BrowserAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ import {
GrepElementsTool,
CelebrationTool,
GroupTabsTool,
CreateTabGroupTool,
ListTabGroupsTool,
AddTabToGroupTool,
BrowserOSInfoTool,
GetSelectedTabsTool,
DateTool,
Expand Down Expand Up @@ -279,6 +282,9 @@ export class BrowserAgent {
this.toolManager.register(TabFocusTool(this.executionContext));
this.toolManager.register(TabCloseTool(this.executionContext));
this.toolManager.register(GroupTabsTool(this.executionContext)); // Group tabs together
this.toolManager.register(CreateTabGroupTool(this.executionContext)); // Create new tab group
this.toolManager.register(ListTabGroupsTool(this.executionContext)); // List all tab groups
this.toolManager.register(AddTabToGroupTool(this.executionContext)); // Add tab to existing group
this.toolManager.register(GetSelectedTabsTool(this.executionContext)); // Get selected tabs

// Utility tools
Expand All @@ -300,7 +306,8 @@ export class BrowserAgent {
this.toolManager.register(DoneTool(this.executionContext));

// Populate tool descriptions after all tools are registered
this.toolDescriptions = getToolDescriptions(this.executionContext.isLimitedContextMode());
// Use ToolManager's dynamic descriptions which include all registered tools
this.toolDescriptions = this.toolManager.getDescriptions();

Logging.log(
"BrowserAgent",
Expand Down
65 changes: 65 additions & 0 deletions src/lib/tools/AddTabToGroupTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { z } from "zod"
import { DynamicStructuredTool } from "@langchain/core/tools"
import { ExecutionContext } from "@/lib/runtime/ExecutionContext"
import { toolSuccess, toolError, type ToolOutput } from "@/lib/tools/ToolInterface"
import { PubSub } from "@/lib/pubsub"

// Input schema for adding a tab to a group
export const AddTabToGroupInputSchema = z.object({
tabId: z.number().describe("The ID of the tab to add to a group"),
groupId: z.number().describe("The ID of the existing group to add the tab to")
})

export type AddTabToGroupInput = z.infer<typeof AddTabToGroupInputSchema>

export class AddTabToGroupToolImpl {
constructor(private executionContext: ExecutionContext) {}

async execute(input: AddTabToGroupInput): Promise<ToolOutput> {
try {
this.executionContext.getPubSub().publishMessage(
PubSub.createMessage(`Adding tab ${input.tabId} to group ${input.groupId}`, 'thinking')
)

// Validate the tab exists
const tab = await chrome.tabs.get(input.tabId)
if (!tab) {
return toolError(`Tab with ID ${input.tabId} not found`)
}

// Validate the group exists
const groups = await chrome.tabGroups.query({})
const targetGroup = groups.find(g => g.id === input.groupId)
if (!targetGroup) {
return toolError(`Group with ID ${input.groupId} not found`)
}

// Add the tab to the group
await chrome.tabs.group({
groupId: input.groupId,
tabIds: [input.tabId]
})

const groupName = targetGroup.title || `Group ${input.groupId}`
return toolSuccess(`Added tab "${tab.title}" to group "${groupName}"`)

} catch (error) {
return toolError(`Failed to add tab to group: ${error instanceof Error ? error.message : String(error)}`)
}
}
}

// LangChain wrapper factory function
export function AddTabToGroupTool(executionContext: ExecutionContext): DynamicStructuredTool {
const tool = new AddTabToGroupToolImpl(executionContext)

return new DynamicStructuredTool({
name: "add_tab_to_group",
description: "Add a specific tab to an existing tab group. Use this to move a tab into a group. Requires the tab ID and the group ID.",
schema: AddTabToGroupInputSchema,
func: async (args): Promise<string> => {
const result = await tool.execute(args)
return JSON.stringify(result)
}
})
}
91 changes: 91 additions & 0 deletions src/lib/tools/CreateTabGroupTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { z } from "zod"
import { DynamicStructuredTool } from "@langchain/core/tools"
import { ExecutionContext } from "@/lib/runtime/ExecutionContext"
import { toolSuccess, toolError, type ToolOutput } from "@/lib/tools/ToolInterface"
import { PubSub } from "@/lib/pubsub"

// Constants
const VALID_COLORS = ["grey", "blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange"] as const
const DEFAULT_COLOR = "blue"

// Input schema for creating a tab group
export const CreateTabGroupInputSchema = z.object({
name: z.string().min(1).max(50).describe("Name for the new tab group"),
color: z.enum(VALID_COLORS).default(DEFAULT_COLOR).describe("Color for the tab group (grey, blue, red, yellow, green, pink, purple, cyan, orange)"),
tabIds: z.array(z.number()).min(1).optional().describe("Optional: Tab IDs to add to the group immediately")
})

export type CreateTabGroupInput = z.infer<typeof CreateTabGroupInputSchema>

export class CreateTabGroupToolImpl {
constructor(private executionContext: ExecutionContext) {}

async execute(input: CreateTabGroupInput): Promise<ToolOutput> {
try {
this.executionContext.getPubSub().publishMessage(
PubSub.createMessage(`Creating tab group "${input.name}"`, 'thinking')
)

// If no tabIds provided, we need at least one tab to create a group
// Chrome requires at least one tab to create a group
let tabIdsToGroup = input.tabIds || []

if (tabIdsToGroup.length === 0) {
// Get current tab as default
const currentTab = await chrome.tabs.getCurrent()
if (currentTab?.id) {
tabIdsToGroup = [currentTab.id]
} else {
// Get any tab from current window
const tabs = await chrome.tabs.query({ currentWindow: true })
if (tabs.length > 0 && tabs[0].id) {
tabIdsToGroup = [tabs[0].id]
} else {
return toolError("Cannot create tab group: No tabs available")
}
}
}

// Validate tab IDs exist
const tabs = await chrome.tabs.query({})
const existingTabIds = new Set(tabs.map(t => t.id))
const validTabIds = tabIdsToGroup.filter(id => existingTabIds.has(id))

if (validTabIds.length === 0) {
return toolError(`No valid tabs found with IDs: ${tabIdsToGroup.join(", ")}`)
}

// Create the group
const groupId = await chrome.tabs.group({ tabIds: validTabIds })

// Update group properties
await chrome.tabGroups.update(groupId, {
title: input.name,
color: input.color
})

const tabCount = validTabIds.length
const tabText = tabCount === 1 ? "tab" : "tabs"

return toolSuccess(`Created tab group "${input.name}" (ID: ${groupId}) with ${tabCount} ${tabText}`)

} catch (error) {
return toolError(`Failed to create tab group: ${error instanceof Error ? error.message : String(error)}`)
}
}
}

// LangChain wrapper factory function
export function CreateTabGroupTool(executionContext: ExecutionContext): DynamicStructuredTool {
const tool = new CreateTabGroupToolImpl(executionContext)

return new DynamicStructuredTool({
name: "create_tab_group",
description: "Create a new tab group with a name and color. Optionally add specific tabs to it immediately. Returns the group ID.",
schema: CreateTabGroupInputSchema,
func: async (args): Promise<string> => {
const result = await tool.execute(args)
return JSON.stringify(result)
}
})
}
77 changes: 77 additions & 0 deletions src/lib/tools/ListTabGroupsTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { z } from "zod"
import { DynamicStructuredTool } from "@langchain/core/tools"
import { ExecutionContext } from "@/lib/runtime/ExecutionContext"
import { toolSuccess, type ToolOutput } from "@/lib/tools/ToolInterface"
import { PubSub } from "@/lib/pubsub"

// Input schema (empty for list operation)
export const ListTabGroupsInputSchema = z.object({})

export type ListTabGroupsInput = z.infer<typeof ListTabGroupsInputSchema>

interface TabGroupInfo {
id: number
title: string
color: string
collapsed: boolean
windowId: number
}

export class ListTabGroupsToolImpl {
constructor(private executionContext: ExecutionContext) {}

async execute(input: ListTabGroupsInput): Promise<ToolOutput> {
try {
this.executionContext.getPubSub().publishMessage(
PubSub.createMessage("Listing tab groups...", 'thinking')
)

// Get all tab groups
const groups = await chrome.tabGroups.query({})

if (groups.length === 0) {
return toolSuccess("No tab groups found")
}

// Format group information
const groupsInfo: TabGroupInfo[] = groups.map(group => ({
id: group.id,
title: group.title || `Unnamed Group ${group.id}`,
color: group.color,
collapsed: group.collapsed,
windowId: group.windowId
}))

// Get tab count for each group
const groupsWithCounts = await Promise.all(
groupsInfo.map(async (group) => {
const tabsInGroup = await chrome.tabs.query({ groupId: group.id })
return {
...group,
tabCount: tabsInGroup.length
}
})
)

return toolSuccess(JSON.stringify(groupsWithCounts, null, 2))

} catch (error) {
return toolSuccess("[]") // Return empty array on error
}
}
}

// LangChain wrapper factory function
export function ListTabGroupsTool(executionContext: ExecutionContext): DynamicStructuredTool {
const tool = new ListTabGroupsToolImpl(executionContext)

return new DynamicStructuredTool({
name: "list_tab_groups",
description: "List all existing tab groups with their IDs, names, colors, and tab counts. Use this to find group IDs before adding tabs to groups.",
schema: ListTabGroupsInputSchema,
func: async (args): Promise<string> => {
const result = await tool.execute(args)
return JSON.stringify(result)
}
})
}
3 changes: 3 additions & 0 deletions src/lib/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,8 @@ export * from './Planner'
export * from './MCPTool'
export * from './GetSelectedTabsTool'
export * from './GroupTabsTool'
export * from './CreateTabGroupTool'
export * from './ListTabGroupsTool'
export * from './AddTabToGroupTool'
export * from './BrowserOSInfoTool'
export * from './DateTool'