From 466671eed483f1bdac13f817dcd5ef7df401ab82 Mon Sep 17 00:00:00 2001 From: cynthialong0-0 <82900738+cynthialong0-0@users.noreply.github.com> Date: Tue, 24 Mar 2026 14:40:48 -0700 Subject: [PATCH] feat(browser): add maxActionsPerTask for browser agent setting (#23216) --- docs/reference/configuration.md | 5 ++++ packages/cli/src/config/settingsSchema.ts | 10 ++++++++ .../agents/browser/browserAgentDefinition.ts | 1 + .../src/agents/browser/browserManager.test.ts | 24 +++++++++++++++++++ .../core/src/agents/browser/browserManager.ts | 16 +++++++++++++ packages/core/src/config/config.test.ts | 16 +++++++++++++ packages/core/src/config/config.ts | 3 +++ schemas/settings.schema.json | 7 ++++++ 8 files changed, 82 insertions(+) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 89f7502502..f8382ee28c 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -1215,6 +1215,11 @@ their corresponding top-level category object in your `settings.json` file. - **Description:** Disable user input on browser window during automation. - **Default:** `true` +- **`agents.browser.maxActionsPerTask`** (number): + - **Description:** The maximum number of tool calls allowed per browser task. + Enforcement is hard: the agent will be terminated when the limit is reached. + - **Default:** `100` + - **`agents.browser.confirmSensitiveActions`** (boolean): - **Description:** Require manual confirmation for sensitive browser actions (e.g., fill_form, evaluate_script). diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 0d0672a227..c0f2395110 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -1208,6 +1208,16 @@ const SETTINGS_SCHEMA = { 'Disable user input on browser window during automation.', showInDialog: false, }, + maxActionsPerTask: { + type: 'number', + label: 'Max Actions Per Task', + category: 'Advanced', + requiresRestart: false, + default: 100, + description: + 'The maximum number of tool calls allowed per browser task. Enforcement is hard: the agent will be terminated when the limit is reached.', + showInDialog: false, + }, confirmSensitiveActions: { type: 'boolean', label: 'Confirm Sensitive Actions', diff --git a/packages/core/src/agents/browser/browserAgentDefinition.ts b/packages/core/src/agents/browser/browserAgentDefinition.ts index 064d66dfbc..b04b2a3ede 100644 --- a/packages/core/src/agents/browser/browserAgentDefinition.ts +++ b/packages/core/src/agents/browser/browserAgentDefinition.ts @@ -112,6 +112,7 @@ Some errors are unrecoverable and retrying will never help. When you see ANY of - "Could not connect to Chrome" or "Failed to connect to Chrome" or "Timed out connecting to Chrome" — Include the full error message with its remediation steps in your summary verbatim. Do NOT paraphrase or omit instructions. - "Browser closed" or "Target closed" or "Session closed" — The browser process has terminated. Include the error and tell the user to try again. - "net::ERR_" network errors on the SAME URL after 2 retries — the site is unreachable. Report the URL and error. +- "reached maximum action limit" — You have performed too many actions in this task. Stop immediately and report this limit to the user. - Any error that appears IDENTICALLY 3+ times in a row — it will not resolve by retrying. Do NOT keep retrying terminal errors. Report them with actionable remediation steps and exit immediately. diff --git a/packages/core/src/agents/browser/browserManager.test.ts b/packages/core/src/agents/browser/browserManager.test.ts index 36652bbb64..303c07288d 100644 --- a/packages/core/src/agents/browser/browserManager.test.ts +++ b/packages/core/src/agents/browser/browserManager.test.ts @@ -697,4 +697,28 @@ describe('BrowserManager', () => { expect(injectAutomationOverlay).not.toHaveBeenCalled(); }); }); + + describe('Rate limiting', () => { + it('should terminate task when maxActionsPerTask is reached', async () => { + const limitedConfig = makeFakeConfig({ + agents: { + browser: { + maxActionsPerTask: 3, + }, + }, + }); + const manager = new BrowserManager(limitedConfig); + + // First 3 calls should succeed + await manager.callTool('take_snapshot', {}); + await manager.callTool('take_snapshot', { some: 'args' }); + await manager.callTool('take_snapshot', { other: 'args' }); + await manager.callTool('take_snapshot', { other: 'new args' }); + + // 4th call should throw + await expect(manager.callTool('take_snapshot', {})).rejects.toThrow( + /maximum action limit \(3\)/, + ); + }); + }); }); diff --git a/packages/core/src/agents/browser/browserManager.ts b/packages/core/src/agents/browser/browserManager.ts index c5fc6c5053..cc059feea3 100644 --- a/packages/core/src/agents/browser/browserManager.ts +++ b/packages/core/src/agents/browser/browserManager.ts @@ -97,6 +97,10 @@ export class BrowserManager { private mcpTransport: StdioClientTransport | undefined; private discoveredTools: McpTool[] = []; + /** State for action rate limiting */ + private actionCounter = 0; + private readonly maxActionsPerTask: number; + /** * Whether to inject the automation overlay. * Always false in headless mode (no visible window to decorate). @@ -108,6 +112,8 @@ export class BrowserManager { const browserConfig = config.getBrowserAgentConfig(); this.shouldInjectOverlay = !browserConfig?.customConfig?.headless; this.shouldDisableInput = config.shouldDisableBrowserUserInput(); + this.maxActionsPerTask = + browserConfig?.customConfig.maxActionsPerTask ?? 100; } /** @@ -151,6 +157,16 @@ export class BrowserManager { throw signal.reason ?? new Error('Operation cancelled'); } + // Hard enforcement of per-action rate limit + if (this.actionCounter > this.maxActionsPerTask) { + const error = new Error( + `Browser agent reached maximum action limit (${this.maxActionsPerTask}). ` + + `Task terminated to prevent runaway execution. To config the limit, use maxActionsPerTask in the settings.`, + ); + throw error; + } + this.actionCounter++; + const errorMessage = this.checkNavigationRestrictions(toolName, args); if (errorMessage) { return { diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index f8247f8377..99688eead5 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -1474,6 +1474,22 @@ describe('Server Config (config.ts)', () => { expect(browserConfig.customConfig.visualModel).toBe( 'custom-visual-model', ); + expect(browserConfig.customConfig.maxActionsPerTask).toBe(100); // default + }); + + it('should return custom maxActionsPerTask', () => { + const params: ConfigParameters = { + ...baseParams, + agents: { + browser: { + maxActionsPerTask: 50, + }, + }, + }; + const config = new Config(params); + const browserConfig = config.getBrowserAgentConfig(); + + expect(browserConfig.customConfig.maxActionsPerTask).toBe(50); }); it('should apply defaults for partial custom config', () => { diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index f4f186ff8f..795df747cb 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -331,6 +331,8 @@ export interface BrowserAgentCustomConfig { allowedDomains?: string[]; /** Disable user input on the browser window during automation. Default: true in non-headless mode */ disableUserInput?: boolean; + /** Maximum number of actions (tool calls) allowed per task. Default: 100 */ + maxActionsPerTask?: number; /** Whether to confirm sensitive actions (e.g., fill_form, evaluate_script). */ confirmSensitiveActions?: boolean; /** Whether to block file uploads. */ @@ -3194,6 +3196,7 @@ export class Config implements McpContext, AgentLoopContext { visualModel: customConfig.visualModel, allowedDomains: customConfig.allowedDomains, disableUserInput: customConfig.disableUserInput, + maxActionsPerTask: customConfig.maxActionsPerTask ?? 100, confirmSensitiveActions: customConfig.confirmSensitiveActions, blockFileUploads: customConfig.blockFileUploads, }, diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index 287d2b3f76..93bd8fc895 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -2142,6 +2142,13 @@ "default": true, "type": "boolean" }, + "maxActionsPerTask": { + "title": "Max Actions Per Task", + "description": "The maximum number of tool calls allowed per browser task. Enforcement is hard: the agent will be terminated when the limit is reached.", + "markdownDescription": "The maximum number of tool calls allowed per browser task. Enforcement is hard: the agent will be terminated when the limit is reached.\n\n- Category: `Advanced`\n- Requires restart: `no`\n- Default: `100`", + "default": 100, + "type": "number" + }, "confirmSensitiveActions": { "title": "Confirm Sensitive Actions", "description": "Require manual confirmation for sensitive browser actions (e.g., fill_form, evaluate_script).",