Skip to content

Commit 70f5131

Browse files
authored
chore: yo browser less context (#1284)
* docs: add specs for yo browesr context manager * feat(yo-browser): improve skill description and add yo_browser_cdp_send to offload whitelist * refactor(yobrowser): remove skill gating and make CDP tools always available in agent mode * refactor(yobrowser): add CDP method schema validation with strict enums - Add enum-based validation for cdp_send method (11 common CDP methods) - Add detailed union schemas for each method's parameters with examples - Add normalizeCdpParams method to handle both object and JSON string inputs - Prevent method typos and provide better type safety for CDP interactions * fix(yobrowser): add strict tab ID validation in CDP send handler * chore: update deps
1 parent 2e420fa commit 70f5131

25 files changed

Lines changed: 625 additions & 1294 deletions

File tree

docs/architecture/tool-system.md

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ graph TB
2929
3030
AgentToolMgr[AgentToolManager]
3131
FsHandler[AgentFileSystemHandler]
32-
Browser[Yo Browser Tools]
32+
YoBrowser[Yo Browser CDP]
3333
end
3434
3535
subgraph "外部服务"
@@ -48,10 +48,10 @@ graph TB
4848
McpClient --> MCPServers
4949
5050
AgentToolMgr --> FsHandler
51-
AgentToolMgr --> Browser
51+
AgentToolMgr --> YoBrowser
5252
5353
FsHandler --> Files
54-
Browser --> Web
54+
YoBrowser --> Web
5555
5656
classDef router fill:#e3f2fd
5757
classDef mcp fill:#fff3e0
@@ -60,7 +60,7 @@ graph TB
6060
6161
class ToolP,Mapper router
6262
class McpP,ServerMgr,ToolMgr,McpClient mcp
63-
class AgentToolMgr,FsHandler,Browser agent
63+
class AgentToolMgr,FsHandler,YoBrowser agent
6464
class MCPServers,Files,Web external
6565
```
6666

@@ -622,23 +622,20 @@ class AgentFileSystemHandler {
622622
3. **边界检查**:防止 `../` 越界访问
623623
4. **正则验证**`grep_search``text_replace` 使用 `validateRegexPattern` 防 ReDoS
624624

625-
### Browser 工具
625+
### YoBrowser CDP 工具
626626

627-
```typescript
628-
// 通过 Yo Browser Presenter 调用
629-
async callBrowserTool(toolName: string, args: any): Promise<string> {
630-
switch (toolName) {
631-
case 'browser_navigate':
632-
return await this.yoBrowserPresenter.navigate(args.url)
633-
case 'browser_scrape':
634-
return await this.yoBrowserPresenter.scrape(args.url)
635-
case 'browser_screenshot':
636-
return await this.yoBrowserPresenter.screenshot(args.url)
637-
default:
638-
throw new Error(`未知的 Browser 工具: ${toolName}`)
639-
}
640-
}
641-
```
627+
YoBrowser 提供基于 Chrome DevTools Protocol (CDP) 的最小工具集,在 agent 模式下直接可用。
628+
629+
**可用工具**
630+
- `yo_browser_tab_list` - 列出所有浏览器 tabs
631+
- `yo_browser_tab_new` - 创建新 tab
632+
- `yo_browser_tab_activate` - 激活指定 tab
633+
- `yo_browser_tab_close` - 关闭 tab
634+
- `yo_browser_cdp_send` - 发送 CDP 命令
635+
636+
**安全约束**
637+
- `local://` URL 禁止 CDP attach(在 `BrowserTab.ensureSession()` 中检查)
638+
- 所有 CDP 命令通过 `webContents.debugger.sendCommand()` 执行
642639

643640
## 🔐 权限系统
644641

docs/archives/workspace-agent-refactoring-summary.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ graph TB
196196

197197
- MCP 工具:保持原始命名
198198
- Agent FileSystem 工具:不加前缀(`read_file` 等)
199-
- Yo Browser:保留 `browser_` 前缀
199+
- Yo Browser:使用 `yo_browser_` 前缀
200200

201201
### 工具路由机制
202202

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# YoBrowser Optimization:实施方案(Plan)
2+
3+
## 现状盘点(基于代码)
4+
5+
- Renderer:`src/renderer/src/components/workspace/WorkspaceView.vue``agent` 模式下渲染 `WorkspaceBrowserTabs`,但不关心是否存在 tabs。
6+
- Renderer:`src/renderer/src/stores/yoBrowser.ts` 已维护 tabs 与 `tabCount`(由 IPC 事件更新)。
7+
- Main:YoBrowser 通过 `YoBrowserToolHandler` + `YoBrowserToolDefinitions` 暴露 `yo_browser_*` 工具,当前有 skill gating 逻辑(需要激活 `yo-browser-cdp` skill)。
8+
- Agent loop:`src/main/presenter/agentPresenter/loop/toolCallProcessor.ts``TOOLS_REQUIRING_OFFLOAD` 包含 `yo_browser_cdp_send`
9+
10+
## 总体设计
11+
12+
1) UI:Browser Tabs 分区只在 `tabCount > 0` 时出现。
13+
2) YoBrowser 工具直接注入:agent 模式下直接提供 `yo_browser_*` 工具,不依赖 skills 体系。
14+
3) 工具实现保持 CDP 方式:`yo_browser_cdp_send` + tab 管理,参数 schema 按 CDP 定义。
15+
16+
> 约束:不做任何 system prompt / browser context 缩减。
17+
18+
---
19+
20+
## 1) UI:Browser Tabs 分区仅在 `tabCount > 0` 时渲染
21+
22+
- 修改 `WorkspaceView.vue`
23+
- 引入 `useYoBrowserStore()`
24+
-`showBrowserTabs` 改为:`chatMode.currentMode.value === 'agent' && yoBrowserStore.tabCount > 0`
25+
26+
说明:
27+
- `yoBrowserStore.tabCount` 已存在且由 tabs 数组计算。
28+
- tabs 更新依赖现有 `YO_BROWSER_EVENTS.*`(TAB_CREATED/TAB_CLOSED/TAB_COUNT_CHANGED 等),无需新增事件。
29+
30+
---
31+
32+
## 2) YoBrowser 工具直接注入(agent 模式,不依赖 skills)
33+
34+
### 2.1 移除 tool definitions 的 skill gating
35+
36+
- `src/main/presenter/browser/YoBrowserToolHandler.ts`
37+
- 删除 `getActiveSkills()` 方法或不再使用。
38+
- `getToolDefinitions()` 直接返回 `getYoBrowserToolDefinitions()`(不再受 `activeSkills` 控制)。
39+
40+
### 2.2 同步更新 AgentToolManager 注入逻辑
41+
42+
- `src/main/presenter/agentPresenter/acp/agentToolManager.ts`
43+
- `getAllToolDefinitions()` 中,在 agent 模式下直接追加 `yoBrowserPresenter.toolHandler.getToolDefinitions()`(不再传递/依赖 conversationId 做 gating)。
44+
- `callTool()` 中,`toolName.startsWith('yo_browser_')` 分支保持不变(继续路由到 YoBrowser handler)。
45+
46+
### 2.3 移除 skill 文档与残留引用
47+
48+
- 删除 `resources/skills/yo-browser-cdp/` 整个目录。
49+
- `docs/architecture/tool-system.md`
50+
- 删除或改写“YoBrowser CDP 工具仅在 `yo-browser-cdp` skill 激活时可用”的描述。
51+
- 改为:“YoBrowser CDP 工具在 agent 模式下直接可用”。
52+
- 全局搜索 `yo-browser-cdp` / `allowedTools` / `skill gated`,确保没有残留引用(代码、文档、测试)。
53+
54+
---
55+
56+
## 3) 工具实现:CDP 方式 + 参数定义(保持现状)
57+
58+
### 3.1 工具集合(无需改动)
59+
60+
- `yo_browser_tab_list`
61+
- `yo_browser_tab_new`
62+
- `yo_browser_tab_activate`
63+
- `yo_browser_tab_close`
64+
- `yo_browser_cdp_send`
65+
66+
### 3.2 参数 schema(保持现状,无需改动)
67+
68+
- `src/main/presenter/browser/YoBrowserToolDefinitions.ts`
69+
- `cdp_send` 参数:`{ tabId?: string, method: string, params?: object }`
70+
- 其他 tab 管理工具参数保持不变。
71+
72+
### 3.3 安全边界(保持现状)
73+
74+
- `src/main/presenter/browser/BrowserTab.ensureSession()`
75+
- 检查 `currentUrl.startsWith('local://')`,若为真则抛出错误(禁止 CDP attach)。
76+
77+
---
78+
79+
## 不在本计划内
80+
81+
- system prompt / browser context 的缩减或重写。
82+
- 任何对 YoBrowser UI 行为(窗口位置/大小等)的调整。
83+
- skills 体系(YoBrowser 不再使用 skills 来控制工具可见性)。
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# YoBrowser Optimization(UI + CDP 工具)
2+
3+
## 背景
4+
5+
当前 YoBrowser 在 Workspace 侧边栏存在 UI 问题:
6+
- `src/renderer/src/components/workspace/WorkspaceView.vue``agent` 模式下总会渲染 `WorkspaceBrowserTabs` 分区,即便没有任何 tab,也会出现一块空区域。
7+
8+
## 目标(Goals)
9+
10+
1. **UI**:只有存在 YoBrowser tabs 时,Workspace 侧边栏才显示 Browser Tabs 分区。
11+
2. **Agent 工具直接注入**:YoBrowser 工具(`yo_browser_*`)在 agent 模式下直接可用,无需激活任何 skill。
12+
13+
## 非目标(Non-Goals)
14+
15+
- 不调整 YoBrowser window 的 UI、尺寸、布局、位置策略。
16+
- 不修改 `BrowserContextBuilder.buildSystemPrompt` 的注入策略(不做减少/压缩/裁剪)。
17+
- 不改造其他 agent 工具(filesystem/bash/mcp 等)。
18+
- 不使用 skills 系统来控制 YoBrowser 工具的可见性。
19+
20+
## 用户故事(User Stories)
21+
22+
- 作为用户,我不希望在没有任何浏览器 tab 的情况下,Workspace 侧边栏仍出现空的 Browser Tabs 分区。
23+
- 作为 agent 用户,我希望 YoBrowser 自动化能力以 CDP 为核心,工具在 agent 模式下直接可用。
24+
25+
## 约束与假设(Constraints & Assumptions)
26+
27+
- YoBrowser 现有实现已经基于 Electron Debugger/CDP(`CDPManager`, `BrowserTab.ensureSession()`)。
28+
- 安全边界:`local://` URL 禁止绑定 CDP(`BrowserTab` 现有逻辑已做限制)。
29+
30+
## 验收标准(Acceptance Criteria)
31+
32+
### A. UI:Workspace Browser Tabs 展示逻辑
33+
34+
- [ ] `src/renderer/src/components/workspace/WorkspaceView.vue` 仅在 `chatMode === 'agent' && yoBrowserStore.tabCount > 0` 时渲染 `WorkspaceBrowserTabs`
35+
- [ ]`tabCount === 0` 时,不显示 Browser Tabs 分区(不保留空白区域)。
36+
37+
### B. 工具:YoBrowser CDP 工具直接注入(agent 模式)
38+
39+
- [ ] agent tool definitions 中包含 `yo_browser_*` 工具(agent 模式下直接可用)。
40+
- [ ] agent 的 tool call 路由正确处理 `yo_browser_*` 工具(`toolName.startsWith('yo_browser_')`)。
41+
- [ ] 不依赖 skills 系统(不检查 `activeSkills`)。
42+
43+
### C. 工具实现:CDP 方式 + 合适的参数定义
44+
45+
- [ ] 工具集合:
46+
- `yo_browser_tab_list`:列出 tabs 与 active tab。
47+
- `yo_browser_tab_new`:创建新 tab(可选 url)。
48+
- `yo_browser_tab_activate`:激活 tab。
49+
- `yo_browser_tab_close`:关闭 tab。
50+
- `yo_browser_cdp_send`:向指定/当前 tab 的 CDP session 发送 `{ method, params }`
51+
- [ ] 参数 schema 符合 CDP 使用方式(method、params 等)。
52+
- [ ] 保留安全边界:`local://` 禁止 CDP attach。
53+
54+
### D. Prompt/Context
55+
56+
- [ ] `BrowserContextBuilder.buildSystemPrompt` 的注入保持现状(不做减少/压缩/裁剪)。
57+
58+
### E. 兼容性
59+
60+
- [ ] 不涉及数据迁移。
61+
- [ ] 现有 YoBrowser UI/窗口/Tab 生命周期保持可用。
62+
63+
## Open Questions
64+
65+
无。
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# YoBrowser Optimization:任务拆分(Tasks)
2+
3+
## Phase 1:UI(Workspace 侧边栏)
4+
5+
1. 调整调整 Browser Tabs 分区显示条件
6+
- 文件:`src/renderer/src/components/workspace/WorkspaceView.vue`
7+
- 改动:`WorkspaceBrowserTabs` 仅在 `chatMode === 'agent' && yoBrowserStore.tabCount > 0` 时渲染。
8+
- 验收:无 tabs 时不出现分区;有 tabs 时出现并能点击切换。
9+
10+
2.(可选)补 renderer 单测
11+
- 文件:`test/renderer/**`(按现有测试组织落位)
12+
- 用例:tabCount=0/1 下的条件渲染。
13+
14+
---
15+
16+
## Phase 2:移除 YoBrowser skill gating
17+
18+
3. 移除 YoBrowser tool definitions 的 skill gating
19+
- 文件:`src/main/presenter/browser/YoBrowserToolHandler.ts`
20+
- 改动:删除 `getActiveSkills()` 方法或不再使用;`getToolDefinitions()` 直接返回 `getYoBrowserToolDefinitions()`
21+
- 验收:不再依赖 `activeSkills`
22+
23+
4. 调整 AgentToolManager 注入逻辑(不再依赖 conversationId 做 gating)
24+
- 文件:`src/main/presenter/agentPresenter/acp/agentToolManager.ts`
25+
- 改动:`getAllToolDefinitions()` 中,agent 模式下直接追加 `yoBrowserPresenter.toolHandler.getToolDefinitions()`(可不传 conversationId)。
26+
- 验收:tool definitions 包含 `yo_browser_*`
27+
28+
5. 删除 skill 文档与残留引用
29+
- 删除 `resources/skills/yo-browser-cdp/` 整个目录。
30+
- 文件:`docs/architecture/tool-system.md`(以及搜索到的其他文档)
31+
- 改动:删除或改写“仅在 `yo-browser-cdp` skill 激活时可用”的描述;改为“agent 模式下直接可用”。
32+
- 全局搜索:确认没有残留的 `yo-browser-cdp` / `skill gated` 引用。
33+
34+
---
35+
36+
## Phase 3:验证工具实现(保持 CDP 方式)
37+
38+
6. 验证工具参数定义
39+
- 文件:`src/main/presenter/browser/YoBrowserToolDefinitions.ts`
40+
- 验收:`yo_browser_cdp_send` 参数为 `{ tabId?: string, method: string, params?: object }`
41+
42+
7. 验证安全边界
43+
- 文件:`src/main/presenter/browser/BrowserTab.ts`
44+
- 验收:`ensureSession()` 中有 `local://` URL 检查。
45+
46+
8.(可选)补 main 单测
47+
- 验证:
48+
- agent 模式下 tool definitions 包含 `yo_browser_*`
49+
- `callTool()` 正确路由到 YoBrowser handler。
50+
51+
---
52+
53+
## Phase 4:验收与质量门禁
54+
55+
9. 手工验收
56+
- Agent 模式下:无 tabs 时 Workspace 不显示 Browser Tabs;创建 tab 后显示。
57+
- Agent 模式下:不激活任何 skill,`yo_browser_*` 工具直接可用。
58+
59+
10. 质量门禁
60+
- `pnpm run format && pnpm run lint && pnpm run typecheck`
61+
- `pnpm test`

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
"chokidar": "^5.0.0",
7777
"compare-versions": "^6.1.1",
7878
"cross-spawn": "^7.0.6",
79-
"diff": "^7.0.0",
79+
"diff": "^8.0.3",
8080
"electron-log": "^5.4.3",
8181
"electron-store": "^8.2.0",
8282
"electron-updater": "^6.6.2",

src/main/presenter/agentPresenter/acp/agentToolManager.ts

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { IConfigPresenter, IYoBrowserPresenter, MCPToolDefinition } from '@shared/presenter'
1+
import type { IConfigPresenter, MCPToolDefinition } from '@shared/presenter'
22
import { zodToJsonSchema } from 'zod-to-json-schema'
33
import { z } from 'zod'
44
import fs from 'fs'
@@ -52,14 +52,12 @@ export interface AgentToolCallResult {
5252
}
5353

5454
interface AgentToolManagerOptions {
55-
yoBrowserPresenter: IYoBrowserPresenter
5655
agentWorkspacePath: string | null
5756
configPresenter: IConfigPresenter
5857
commandPermissionHandler?: CommandPermissionService
5958
}
6059

6160
export class AgentToolManager {
62-
private readonly yoBrowserPresenter: IYoBrowserPresenter
6361
private agentWorkspacePath: string | null
6462
private fileSystemHandler: AgentFileSystemHandler | null = null
6563
private bashHandler: AgentBashHandler | null = null
@@ -232,7 +230,6 @@ export class AgentToolManager {
232230
}
233231

234232
constructor(options: AgentToolManagerOptions) {
235-
this.yoBrowserPresenter = options.yoBrowserPresenter
236233
this.agentWorkspacePath = options.agentWorkspacePath
237234
this.configPresenter = options.configPresenter
238235
this.commandPermissionHandler = options.commandPermissionHandler
@@ -275,17 +272,7 @@ export class AgentToolManager {
275272
this.agentWorkspacePath = effectiveWorkspacePath
276273
}
277274

278-
// 1. Yo Browser tools (agent mode only)
279-
if (isAgentMode) {
280-
try {
281-
const yoDefs = await this.yoBrowserPresenter.getToolDefinitions(context.supportsVision)
282-
defs.push(...yoDefs)
283-
} catch (error) {
284-
logger.warn('[AgentToolManager] Failed to load Yo Browser tool definitions', { error })
285-
}
286-
}
287-
288-
// 2. FileSystem tools (agent mode only)
275+
// 1. FileSystem tools (agent mode only)
289276
if (isAgentMode && this.fileSystemHandler) {
290277
const fsDefs = this.getFileSystemToolDefinitions()
291278
defs.push(...fsDefs)
@@ -324,6 +311,15 @@ export class AgentToolManager {
324311
}
325312
}
326313

314+
// 5. YoBrowser CDP tools (agent mode only)
315+
if (isAgentMode) {
316+
try {
317+
defs.push(...presenter.yoBrowserPresenter.toolHandler.getToolDefinitions())
318+
} catch (error) {
319+
logger.warn('[AgentToolManager] Failed to load YoBrowser tools', { error })
320+
}
321+
}
322+
327323
return defs
328324
}
329325

@@ -335,17 +331,6 @@ export class AgentToolManager {
335331
args: Record<string, unknown>,
336332
conversationId?: string
337333
): Promise<AgentToolCallResult | string> {
338-
// Route to Yo Browser tools
339-
if (toolName.startsWith('browser_')) {
340-
const response = await this.yoBrowserPresenter.callTool(
341-
toolName,
342-
args as Record<string, unknown>
343-
)
344-
return {
345-
content: typeof response === 'string' ? response : JSON.stringify(response)
346-
}
347-
}
348-
349334
// Route to FileSystem tools
350335
if (this.isFileSystemTool(toolName)) {
351336
if (!this.fileSystemHandler) {
@@ -364,6 +349,14 @@ export class AgentToolManager {
364349
return await this.callChatSettingsTool(toolName, args, conversationId)
365350
}
366351

352+
// Route to YoBrowser CDP tools
353+
if (toolName.startsWith('yo_browser_')) {
354+
const response = await presenter.yoBrowserPresenter.toolHandler.callTool(toolName, args)
355+
return {
356+
content: response
357+
}
358+
}
359+
367360
throw new Error(`Unknown Agent tool: ${toolName}`)
368361
}
369362

0 commit comments

Comments
 (0)