From 64853dbfde7e00d4d0825420ba8dcb88dbb8cb36 Mon Sep 17 00:00:00 2001 From: Gaurav Ghosh Date: Mon, 23 Feb 2026 11:52:00 -0800 Subject: [PATCH] refactor: Introduce dedicated browser agent configuration with session mode, headless, profile path, and visual model settings. --- docs/cli/settings.md | 9 +++ docs/reference/configuration.md | 21 +++++++ docs/sidebar.json | 2 - packages/cli/src/config/settingsSchema.ts | 60 +++++++++++++++++-- .../cli/src/ui/components/SettingsDialog.tsx | 3 +- .../browser/browserAgentFactory.test.ts | 22 +++---- .../browser/browserAgentInvocation.test.ts | 7 ++- .../src/agents/browser/browserManager.test.ts | 38 ++++++------ .../core/src/agents/browser/browserManager.ts | 9 +-- packages/core/src/config/config.test.ts | 24 ++++---- packages/core/src/config/config.ts | 14 ++--- packages/sdk/src/agent.integration.test.ts | 5 +- schemas/settings.schema.json | 42 +++++++++++-- 13 files changed, 179 insertions(+), 77 deletions(-) diff --git a/docs/cli/settings.md b/docs/cli/settings.md index 5011f55b2c..3b754682bf 100644 --- a/docs/cli/settings.md +++ b/docs/cli/settings.md @@ -87,6 +87,15 @@ they appear in the UI. | Disable Loop Detection | `model.disableLoopDetection` | Disable automatic detection and prevention of infinite loops. | `false` | | Skip Next Speaker Check | `model.skipNextSpeakerCheck` | Skip the next speaker check. | `true` | +### Agents + +| UI Label | Setting | Description | Default | +| -------------------- | ---------------------------- | ---------------------------------------------------------- | -------------- | +| Browser Session Mode | `agents.browser.sessionMode` | Session mode: 'persistent', 'isolated', or 'existing'. | `"persistent"` | +| Browser Headless | `agents.browser.headless` | Run browser in headless mode. | `false` | +| Browser Profile Path | `agents.browser.profilePath` | Path to browser profile directory for session persistence. | `undefined` | +| Browser Visual Model | `agents.browser.visualModel` | Model override for the visual agent. | `undefined` | + ### Context | UI Label | Setting | Description | Default | diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index b9874e017b..b2eea5bec1 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -641,6 +641,27 @@ their corresponding top-level category object in your `settings.json` file. - **Default:** `{}` - **Requires restart:** Yes +- **`agents.browser.sessionMode`** (enum): + - **Description:** Session mode: 'persistent', 'isolated', or 'existing'. + - **Default:** `"persistent"` + - **Values:** `"persistent"`, `"isolated"`, `"existing"` + - **Requires restart:** Yes + +- **`agents.browser.headless`** (boolean): + - **Description:** Run browser in headless mode. + - **Default:** `false` + - **Requires restart:** Yes + +- **`agents.browser.profilePath`** (string): + - **Description:** Path to browser profile directory for session persistence. + - **Default:** `undefined` + - **Requires restart:** Yes + +- **`agents.browser.visualModel`** (string): + - **Description:** Model override for the visual agent. + - **Default:** `undefined` + - **Requires restart:** Yes + #### `context` - **`context.fileName`** (string | string[]): diff --git a/docs/sidebar.json b/docs/sidebar.json index 6f40d047f2..8a4bd7391c 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -196,7 +196,6 @@ { "label": "resources_tab", "items": [ - { { "label": "Resources", "items": [ @@ -216,7 +215,6 @@ { "label": "Uninstall", "slug": "docs/resources/uninstall" } ] } - ] }, { diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 5c95bcbcf9..3a8703ec24 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -964,6 +964,60 @@ const SETTINGS_SCHEMA = { ref: 'AgentOverride', }, }, + browser: { + type: 'object', + label: 'Browser Agent', + category: 'Advanced', + requiresRestart: true, + default: {}, + description: 'Settings specific to the browser agent.', + showInDialog: false, + properties: { + sessionMode: { + type: 'enum', + label: 'Browser Session Mode', + category: 'Advanced', + requiresRestart: true, + default: 'persistent', + description: + "Session mode: 'persistent', 'isolated', or 'existing'.", + showInDialog: true, + options: [ + { value: 'persistent', label: 'Persistent' }, + { value: 'isolated', label: 'Isolated' }, + { value: 'existing', label: 'Existing' }, + ], + }, + headless: { + type: 'boolean', + label: 'Browser Headless', + category: 'Advanced', + requiresRestart: true, + default: false, + description: 'Run browser in headless mode.', + showInDialog: true, + }, + profilePath: { + type: 'string', + label: 'Browser Profile Path', + category: 'Advanced', + requiresRestart: true, + default: undefined as string | undefined, + description: + 'Path to browser profile directory for session persistence.', + showInDialog: true, + }, + visualModel: { + type: 'string', + label: 'Browser Visual Model', + category: 'Advanced', + requiresRestart: true, + default: undefined as string | undefined, + description: 'Model override for the visual agent.', + showInDialog: true, + }, + }, + }, }, }, @@ -2275,12 +2329,6 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record< type: 'boolean', description: 'Whether to enable the agent.', }, - customConfig: { - type: 'object', - description: - 'Agent-specific custom configuration. Each agent defines its own schema.', - additionalProperties: true, - }, }, }, CustomTheme: { diff --git a/packages/cli/src/ui/components/SettingsDialog.tsx b/packages/cli/src/ui/components/SettingsDialog.tsx index e426e9bbe3..4b2c164d3b 100644 --- a/packages/cli/src/ui/components/SettingsDialog.tsx +++ b/packages/cli/src/ui/components/SettingsDialog.tsx @@ -170,7 +170,8 @@ export function SettingsDialog({ updated = setPendingSettingValue(key, value, updated); } else if ( (def?.type === 'number' && typeof value === 'number') || - (def?.type === 'string' && typeof value === 'string') + (def?.type === 'string' && typeof value === 'string') || + (def?.type === 'enum' && typeof value === 'string') ) { updated = setPendingSettingValueAny(key, value, updated); } diff --git a/packages/core/src/agents/browser/browserAgentFactory.test.ts b/packages/core/src/agents/browser/browserAgentFactory.test.ts index 3a128ad4c6..e5e48f13a5 100644 --- a/packages/core/src/agents/browser/browserAgentFactory.test.ts +++ b/packages/core/src/agents/browser/browserAgentFactory.test.ts @@ -73,11 +73,11 @@ describe('browserAgentFactory', () => { overrides: { browser_agent: { enabled: true, - customConfig: { - headless: false, - }, }, }, + browser: { + headless: false, + }, }, }); @@ -160,12 +160,12 @@ describe('browserAgentFactory', () => { overrides: { browser_agent: { enabled: true, - customConfig: { - headless: false, - visualModel: 'gemini-2.5-flash-preview', - }, }, }, + browser: { + headless: false, + visualModel: 'gemini-2.5-flash-preview', + }, }, }); @@ -185,12 +185,12 @@ describe('browserAgentFactory', () => { overrides: { browser_agent: { enabled: true, - customConfig: { - headless: false, - visualModel: 'gemini-2.5-flash-preview', - }, }, }, + browser: { + headless: false, + visualModel: 'gemini-2.5-flash-preview', + }, }, }); diff --git a/packages/core/src/agents/browser/browserAgentInvocation.test.ts b/packages/core/src/agents/browser/browserAgentInvocation.test.ts index fd9b8edaa8..3f2ebad7c4 100644 --- a/packages/core/src/agents/browser/browserAgentInvocation.test.ts +++ b/packages/core/src/agents/browser/browserAgentInvocation.test.ts @@ -32,11 +32,12 @@ describe('BrowserAgentInvocation', () => { overrides: { browser_agent: { enabled: true, - customConfig: { - headless: false, - }, }, }, + browser: { + headless: false, + sessionMode: 'isolated', + }, }, }); diff --git a/packages/core/src/agents/browser/browserManager.test.ts b/packages/core/src/agents/browser/browserManager.test.ts index d7aedebae6..2266983e4d 100644 --- a/packages/core/src/agents/browser/browserManager.test.ts +++ b/packages/core/src/agents/browser/browserManager.test.ts @@ -56,11 +56,11 @@ describe('BrowserManager', () => { overrides: { browser_agent: { enabled: true, - customConfig: { - headless: false, - }, }, }, + browser: { + headless: false, + }, }, }); @@ -165,11 +165,11 @@ describe('BrowserManager', () => { overrides: { browser_agent: { enabled: true, - customConfig: { - headless: true, - }, }, }, + browser: { + headless: true, + }, }, }); @@ -182,17 +182,17 @@ describe('BrowserManager', () => { }); }); - it('should pass chromeProfilePath as --userDataDir when configured', async () => { + it('should pass profilePath as --userDataDir when configured', async () => { const profileConfig = makeFakeConfig({ agents: { overrides: { browser_agent: { enabled: true, - customConfig: { - chromeProfilePath: '/path/to/profile', - }, }, }, + browser: { + profilePath: '/path/to/profile', + }, }, }); @@ -211,11 +211,11 @@ describe('BrowserManager', () => { overrides: { browser_agent: { enabled: true, - customConfig: { - sessionMode: 'isolated', - }, }, }, + browser: { + sessionMode: 'isolated', + }, }, }); @@ -234,11 +234,11 @@ describe('BrowserManager', () => { overrides: { browser_agent: { enabled: true, - customConfig: { - sessionMode: 'existing', - }, }, }, + browser: { + sessionMode: 'existing', + }, }, }); @@ -268,11 +268,11 @@ describe('BrowserManager', () => { overrides: { browser_agent: { enabled: true, - customConfig: { - sessionMode: 'existing', - }, }, }, + browser: { + sessionMode: 'existing', + }, }, }); diff --git a/packages/core/src/agents/browser/browserManager.ts b/packages/core/src/agents/browser/browserManager.ts index 1f44fb8ea2..82062de520 100644 --- a/packages/core/src/agents/browser/browserManager.ts +++ b/packages/core/src/agents/browser/browserManager.ts @@ -259,11 +259,8 @@ export class BrowserManager { if (browserConfig.customConfig.headless) { mcpArgs.push('--headless'); } - if (browserConfig.customConfig.chromeProfilePath) { - mcpArgs.push( - '--userDataDir', - browserConfig.customConfig.chromeProfilePath, - ); + if (browserConfig.customConfig.profilePath) { + mcpArgs.push('--userDataDir', browserConfig.customConfig.profilePath); } debugLogger.log( @@ -343,7 +340,7 @@ export class BrowserManager { `To fix this:\n` + ` 1. Close all Chrome windows using this profile, OR\n` + ` 2. Set sessionMode to "isolated" in settings.json to use a temporary profile, OR\n` + - ` 3. Set chromeProfilePath in settings.json to use a different profile directory`, + ` 3. Set profilePath in settings.json to use a different profile directory`, ); } // existing mode — shouldn't normally hit this, but handle gracefully diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 74329b6263..49e7044de6 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -1333,7 +1333,7 @@ describe('Server Config (config.ts)', () => { expect(browserConfig.model).toBeUndefined(); expect(browserConfig.customConfig.sessionMode).toBe('persistent'); expect(browserConfig.customConfig.headless).toBe(false); - expect(browserConfig.customConfig.chromeProfilePath).toBeUndefined(); + expect(browserConfig.customConfig.profilePath).toBeUndefined(); expect(browserConfig.customConfig.visualModel).toBeUndefined(); }); @@ -1345,14 +1345,14 @@ describe('Server Config (config.ts)', () => { browser_agent: { enabled: true, modelConfig: { model: 'custom-model' }, - customConfig: { - sessionMode: 'existing', - headless: true, - chromeProfilePath: '/path/to/profile', - visualModel: 'custom-visual-model', - }, }, }, + browser: { + sessionMode: 'existing', + headless: true, + profilePath: '/path/to/profile', + visualModel: 'custom-visual-model', + }, }, }; const config = new Config(params); @@ -1362,9 +1362,7 @@ describe('Server Config (config.ts)', () => { expect(browserConfig.model).toBe('custom-model'); expect(browserConfig.customConfig.sessionMode).toBe('existing'); expect(browserConfig.customConfig.headless).toBe(true); - expect(browserConfig.customConfig.chromeProfilePath).toBe( - '/path/to/profile', - ); + expect(browserConfig.customConfig.profilePath).toBe('/path/to/profile'); expect(browserConfig.customConfig.visualModel).toBe( 'custom-visual-model', ); @@ -1377,11 +1375,11 @@ describe('Server Config (config.ts)', () => { overrides: { browser_agent: { enabled: true, - customConfig: { - headless: true, - }, }, }, + browser: { + headless: true, + }, }, }; const config = new Config(params); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index d8ca51e9e6..511642d76b 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -202,16 +202,11 @@ export interface AgentOverride { modelConfig?: ModelConfig; runConfig?: AgentRunConfig; enabled?: boolean; - /** - * Agent-specific custom configuration. - * Each agent defines and documents its own customConfig structure. - * Example: browser_agent uses BrowserAgentCustomConfig for sessionMode, headless, etc. - */ - customConfig?: Record; } export interface AgentSettings { overrides?: Record; + browser?: BrowserAgentCustomConfig; } export interface CustomTheme { @@ -281,7 +276,7 @@ export interface BrowserAgentCustomConfig { /** Run browser in headless mode. Default: false */ headless?: boolean; /** Path to Chrome profile directory for session persistence. */ - chromeProfilePath?: string; + profilePath?: string; /** Model override for the visual agent. */ visualModel?: string; } @@ -2539,15 +2534,14 @@ export class Config { customConfig: BrowserAgentCustomConfig; } { const override = this.getAgentOverride('browser_agent'); - const customConfig = (override?.customConfig ?? - {}) as BrowserAgentCustomConfig; + const customConfig = this.getAgentsSettings()?.browser ?? {}; return { enabled: override?.enabled ?? false, model: override?.modelConfig?.model, customConfig: { sessionMode: customConfig.sessionMode ?? 'persistent', headless: customConfig.headless ?? false, - chromeProfilePath: customConfig.chromeProfilePath, + profilePath: customConfig.profilePath, visualModel: customConfig.visualModel, }, }; diff --git a/packages/sdk/src/agent.integration.test.ts b/packages/sdk/src/agent.integration.test.ts index 064cd9fad7..1de8e52ac7 100644 --- a/packages/sdk/src/agent.integration.test.ts +++ b/packages/sdk/src/agent.integration.test.ts @@ -149,6 +149,9 @@ describe('GeminiCliAgent Integration', () => { throw new Error('Dynamic instruction failure'); }, model: 'gemini-2.0-flash', + fakeResponses: RECORD_MODE + ? undefined + : getGoldenPath('agent-dynamic-instructions'), }); const session = agent.session(); @@ -159,5 +162,5 @@ describe('GeminiCliAgent Integration', () => { // Just consume the stream } }).rejects.toThrow('Dynamic instruction failure'); - }); + }, 30000); }); diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index 0b68680aba..612a26d51d 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -1086,6 +1086,43 @@ "additionalProperties": { "$ref": "#/$defs/AgentOverride" } + }, + "browser": { + "title": "Browser Agent", + "description": "Settings specific to the browser agent.", + "markdownDescription": "Settings specific to the browser agent.\n\n- Category: `Advanced`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "sessionMode": { + "title": "Browser Session Mode", + "description": "Session mode: 'persistent', 'isolated', or 'existing'.", + "markdownDescription": "Session mode: 'persistent', 'isolated', or 'existing'.\n\n- Category: `Advanced`\n- Requires restart: `yes`\n- Default: `persistent`", + "default": "persistent", + "type": "string", + "enum": ["persistent", "isolated", "existing"] + }, + "headless": { + "title": "Browser Headless", + "description": "Run browser in headless mode.", + "markdownDescription": "Run browser in headless mode.\n\n- Category: `Advanced`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "profilePath": { + "title": "Browser Profile Path", + "description": "Path to browser profile directory for session persistence.", + "markdownDescription": "Path to browser profile directory for session persistence.\n\n- Category: `Advanced`\n- Requires restart: `yes`", + "type": "string" + }, + "visualModel": { + "title": "Browser Visual Model", + "description": "Model override for the visual agent.", + "markdownDescription": "Model override for the visual agent.\n\n- Category: `Advanced`\n- Requires restart: `yes`", + "type": "string" + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -2092,11 +2129,6 @@ "enabled": { "type": "boolean", "description": "Whether to enable the agent." - }, - "customConfig": { - "type": "object", - "description": "Agent-specific custom configuration. Each agent defines its own schema.", - "additionalProperties": true } } },