Skip to content
Merged
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
37 changes: 17 additions & 20 deletions docs/architecture/tool-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ graph TB

AgentToolMgr[AgentToolManager]
FsHandler[AgentFileSystemHandler]
Browser[Yo Browser Tools]
YoBrowser[Yo Browser CDP]
end

subgraph "外部服务"
Expand All @@ -48,10 +48,10 @@ graph TB
McpClient --> MCPServers

AgentToolMgr --> FsHandler
AgentToolMgr --> Browser
AgentToolMgr --> YoBrowser

FsHandler --> Files
Browser --> Web
YoBrowser --> Web

classDef router fill:#e3f2fd
classDef mcp fill:#fff3e0
Expand All @@ -60,7 +60,7 @@ graph TB

class ToolP,Mapper router
class McpP,ServerMgr,ToolMgr,McpClient mcp
class AgentToolMgr,FsHandler,Browser agent
class AgentToolMgr,FsHandler,YoBrowser agent
class MCPServers,Files,Web external
```

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

### Browser 工具
### YoBrowser CDP 工具

```typescript
// 通过 Yo Browser Presenter 调用
async callBrowserTool(toolName: string, args: any): Promise<string> {
switch (toolName) {
case 'browser_navigate':
return await this.yoBrowserPresenter.navigate(args.url)
case 'browser_scrape':
return await this.yoBrowserPresenter.scrape(args.url)
case 'browser_screenshot':
return await this.yoBrowserPresenter.screenshot(args.url)
default:
throw new Error(`未知的 Browser 工具: ${toolName}`)
}
}
```
YoBrowser 提供基于 Chrome DevTools Protocol (CDP) 的最小工具集,在 agent 模式下直接可用。

**可用工具**:
- `yo_browser_tab_list` - 列出所有浏览器 tabs
- `yo_browser_tab_new` - 创建新 tab
- `yo_browser_tab_activate` - 激活指定 tab
- `yo_browser_tab_close` - 关闭 tab
- `yo_browser_cdp_send` - 发送 CDP 命令

**安全约束**:
- `local://` URL 禁止 CDP attach(在 `BrowserTab.ensureSession()` 中检查)
- 所有 CDP 命令通过 `webContents.debugger.sendCommand()` 执行

## 🔐 权限系统

Expand Down
2 changes: 1 addition & 1 deletion docs/archives/workspace-agent-refactoring-summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ graph TB

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

### 工具路由机制

Expand Down
83 changes: 83 additions & 0 deletions docs/specs/yobrowser-optimization/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# YoBrowser Optimization:实施方案(Plan)

## 现状盘点(基于代码)

- Renderer:`src/renderer/src/components/workspace/WorkspaceView.vue` 在 `agent` 模式下渲染 `WorkspaceBrowserTabs`,但不关心是否存在 tabs。
- Renderer:`src/renderer/src/stores/yoBrowser.ts` 已维护 tabs 与 `tabCount`(由 IPC 事件更新)。
- Main:YoBrowser 通过 `YoBrowserToolHandler` + `YoBrowserToolDefinitions` 暴露 `yo_browser_*` 工具,当前有 skill gating 逻辑(需要激活 `yo-browser-cdp` skill)。
- Agent loop:`src/main/presenter/agentPresenter/loop/toolCallProcessor.ts` 中 `TOOLS_REQUIRING_OFFLOAD` 包含 `yo_browser_cdp_send`。

## 总体设计

1) UI:Browser Tabs 分区只在 `tabCount > 0` 时出现。
2) YoBrowser 工具直接注入:agent 模式下直接提供 `yo_browser_*` 工具,不依赖 skills 体系。
3) 工具实现保持 CDP 方式:`yo_browser_cdp_send` + tab 管理,参数 schema 按 CDP 定义。

> 约束:不做任何 system prompt / browser context 缩减。

---

## 1) UI:Browser Tabs 分区仅在 `tabCount > 0` 时渲染

- 修改 `WorkspaceView.vue`:
- 引入 `useYoBrowserStore()`。
- 将 `showBrowserTabs` 改为:`chatMode.currentMode.value === 'agent' && yoBrowserStore.tabCount > 0`。

说明:
- `yoBrowserStore.tabCount` 已存在且由 tabs 数组计算。
- tabs 更新依赖现有 `YO_BROWSER_EVENTS.*`(TAB_CREATED/TAB_CLOSED/TAB_COUNT_CHANGED 等),无需新增事件。

---

## 2) YoBrowser 工具直接注入(agent 模式,不依赖 skills)

### 2.1 移除 tool definitions 的 skill gating

- `src/main/presenter/browser/YoBrowserToolHandler.ts`
- 删除 `getActiveSkills()` 方法或不再使用。
- `getToolDefinitions()` 直接返回 `getYoBrowserToolDefinitions()`(不再受 `activeSkills` 控制)。

### 2.2 同步更新 AgentToolManager 注入逻辑

- `src/main/presenter/agentPresenter/acp/agentToolManager.ts`
- `getAllToolDefinitions()` 中,在 agent 模式下直接追加 `yoBrowserPresenter.toolHandler.getToolDefinitions()`(不再传递/依赖 conversationId 做 gating)。
- `callTool()` 中,`toolName.startsWith('yo_browser_')` 分支保持不变(继续路由到 YoBrowser handler)。

### 2.3 移除 skill 文档与残留引用

- 删除 `resources/skills/yo-browser-cdp/` 整个目录。
- `docs/architecture/tool-system.md`:
- 删除或改写“YoBrowser CDP 工具仅在 `yo-browser-cdp` skill 激活时可用”的描述。
- 改为:“YoBrowser CDP 工具在 agent 模式下直接可用”。
- 全局搜索 `yo-browser-cdp` / `allowedTools` / `skill gated`,确保没有残留引用(代码、文档、测试)。

---

## 3) 工具实现:CDP 方式 + 参数定义(保持现状)

### 3.1 工具集合(无需改动)

- `yo_browser_tab_list`
- `yo_browser_tab_new`
- `yo_browser_tab_activate`
- `yo_browser_tab_close`
- `yo_browser_cdp_send`

### 3.2 参数 schema(保持现状,无需改动)

- `src/main/presenter/browser/YoBrowserToolDefinitions.ts`:
- `cdp_send` 参数:`{ tabId?: string, method: string, params?: object }`。
- 其他 tab 管理工具参数保持不变。

### 3.3 安全边界(保持现状)

- `src/main/presenter/browser/BrowserTab.ensureSession()`:
- 检查 `currentUrl.startsWith('local://')`,若为真则抛出错误(禁止 CDP attach)。

---

## 不在本计划内

- system prompt / browser context 的缩减或重写。
- 任何对 YoBrowser UI 行为(窗口位置/大小等)的调整。
- skills 体系(YoBrowser 不再使用 skills 来控制工具可见性)。
65 changes: 65 additions & 0 deletions docs/specs/yobrowser-optimization/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# YoBrowser Optimization(UI + CDP 工具)

## 背景

当前 YoBrowser 在 Workspace 侧边栏存在 UI 问题:
- `src/renderer/src/components/workspace/WorkspaceView.vue` 在 `agent` 模式下总会渲染 `WorkspaceBrowserTabs` 分区,即便没有任何 tab,也会出现一块空区域。

## 目标(Goals)

1. **UI**:只有存在 YoBrowser tabs 时,Workspace 侧边栏才显示 Browser Tabs 分区。
2. **Agent 工具直接注入**:YoBrowser 工具(`yo_browser_*`)在 agent 模式下直接可用,无需激活任何 skill。

## 非目标(Non-Goals)

- 不调整 YoBrowser window 的 UI、尺寸、布局、位置策略。
- 不修改 `BrowserContextBuilder.buildSystemPrompt` 的注入策略(不做减少/压缩/裁剪)。
- 不改造其他 agent 工具(filesystem/bash/mcp 等)。
- 不使用 skills 系统来控制 YoBrowser 工具的可见性。

## 用户故事(User Stories)

- 作为用户,我不希望在没有任何浏览器 tab 的情况下,Workspace 侧边栏仍出现空的 Browser Tabs 分区。
- 作为 agent 用户,我希望 YoBrowser 自动化能力以 CDP 为核心,工具在 agent 模式下直接可用。

## 约束与假设(Constraints & Assumptions)

- YoBrowser 现有实现已经基于 Electron Debugger/CDP(`CDPManager`, `BrowserTab.ensureSession()`)。
- 安全边界:`local://` URL 禁止绑定 CDP(`BrowserTab` 现有逻辑已做限制)。

## 验收标准(Acceptance Criteria)

### A. UI:Workspace Browser Tabs 展示逻辑

- [ ] `src/renderer/src/components/workspace/WorkspaceView.vue` 仅在 `chatMode === 'agent' && yoBrowserStore.tabCount > 0` 时渲染 `WorkspaceBrowserTabs`。
- [ ] 当 `tabCount === 0` 时,不显示 Browser Tabs 分区(不保留空白区域)。

### B. 工具:YoBrowser CDP 工具直接注入(agent 模式)

- [ ] agent tool definitions 中包含 `yo_browser_*` 工具(agent 模式下直接可用)。
- [ ] agent 的 tool call 路由正确处理 `yo_browser_*` 工具(`toolName.startsWith('yo_browser_')`)。
- [ ] 不依赖 skills 系统(不检查 `activeSkills`)。

### C. 工具实现:CDP 方式 + 合适的参数定义

- [ ] 工具集合:
- `yo_browser_tab_list`:列出 tabs 与 active tab。
- `yo_browser_tab_new`:创建新 tab(可选 url)。
- `yo_browser_tab_activate`:激活 tab。
- `yo_browser_tab_close`:关闭 tab。
- `yo_browser_cdp_send`:向指定/当前 tab 的 CDP session 发送 `{ method, params }`。
- [ ] 参数 schema 符合 CDP 使用方式(method、params 等)。
- [ ] 保留安全边界:`local://` 禁止 CDP attach。

### D. Prompt/Context

- [ ] `BrowserContextBuilder.buildSystemPrompt` 的注入保持现状(不做减少/压缩/裁剪)。

### E. 兼容性

- [ ] 不涉及数据迁移。
- [ ] 现有 YoBrowser UI/窗口/Tab 生命周期保持可用。

## Open Questions

无。
61 changes: 61 additions & 0 deletions docs/specs/yobrowser-optimization/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# YoBrowser Optimization:任务拆分(Tasks)

## Phase 1:UI(Workspace 侧边栏)

1. 调整调整 Browser Tabs 分区显示条件
- 文件:`src/renderer/src/components/workspace/WorkspaceView.vue`
- 改动:`WorkspaceBrowserTabs` 仅在 `chatMode === 'agent' && yoBrowserStore.tabCount > 0` 时渲染。
- 验收:无 tabs 时不出现分区;有 tabs 时出现并能点击切换。

2.(可选)补 renderer 单测
- 文件:`test/renderer/**`(按现有测试组织落位)
- 用例:tabCount=0/1 下的条件渲染。

---

## Phase 2:移除 YoBrowser skill gating

3. 移除 YoBrowser tool definitions 的 skill gating
- 文件:`src/main/presenter/browser/YoBrowserToolHandler.ts`
- 改动:删除 `getActiveSkills()` 方法或不再使用;`getToolDefinitions()` 直接返回 `getYoBrowserToolDefinitions()`。
- 验收:不再依赖 `activeSkills`。

4. 调整 AgentToolManager 注入逻辑(不再依赖 conversationId 做 gating)
- 文件:`src/main/presenter/agentPresenter/acp/agentToolManager.ts`
- 改动:`getAllToolDefinitions()` 中,agent 模式下直接追加 `yoBrowserPresenter.toolHandler.getToolDefinitions()`(可不传 conversationId)。
- 验收:tool definitions 包含 `yo_browser_*`。

5. 删除 skill 文档与残留引用
- 删除 `resources/skills/yo-browser-cdp/` 整个目录。
- 文件:`docs/architecture/tool-system.md`(以及搜索到的其他文档)
- 改动:删除或改写“仅在 `yo-browser-cdp` skill 激活时可用”的描述;改为“agent 模式下直接可用”。
- 全局搜索:确认没有残留的 `yo-browser-cdp` / `skill gated` 引用。

---

## Phase 3:验证工具实现(保持 CDP 方式)

6. 验证工具参数定义
- 文件:`src/main/presenter/browser/YoBrowserToolDefinitions.ts`
- 验收:`yo_browser_cdp_send` 参数为 `{ tabId?: string, method: string, params?: object }`。

7. 验证安全边界
- 文件:`src/main/presenter/browser/BrowserTab.ts`
- 验收:`ensureSession()` 中有 `local://` URL 检查。

8.(可选)补 main 单测
- 验证:
- agent 模式下 tool definitions 包含 `yo_browser_*`。
- `callTool()` 正确路由到 YoBrowser handler。

---

## Phase 4:验收与质量门禁

9. 手工验收
- Agent 模式下:无 tabs 时 Workspace 不显示 Browser Tabs;创建 tab 后显示。
- Agent 模式下:不激活任何 skill,`yo_browser_*` 工具直接可用。

10. 质量门禁
- `pnpm run format && pnpm run lint && pnpm run typecheck`
- `pnpm test`
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"chokidar": "^5.0.0",
"compare-versions": "^6.1.1",
"cross-spawn": "^7.0.6",
"diff": "^7.0.0",
"diff": "^8.0.3",
"electron-log": "^5.4.3",
"electron-store": "^8.2.0",
"electron-updater": "^6.6.2",
Expand Down
45 changes: 19 additions & 26 deletions src/main/presenter/agentPresenter/acp/agentToolManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IConfigPresenter, IYoBrowserPresenter, MCPToolDefinition } from '@shared/presenter'
import type { IConfigPresenter, MCPToolDefinition } from '@shared/presenter'
import { zodToJsonSchema } from 'zod-to-json-schema'
import { z } from 'zod'
import fs from 'fs'
Expand Down Expand Up @@ -52,14 +52,12 @@ export interface AgentToolCallResult {
}

interface AgentToolManagerOptions {
yoBrowserPresenter: IYoBrowserPresenter
agentWorkspacePath: string | null
configPresenter: IConfigPresenter
commandPermissionHandler?: CommandPermissionService
}

export class AgentToolManager {
private readonly yoBrowserPresenter: IYoBrowserPresenter
private agentWorkspacePath: string | null
private fileSystemHandler: AgentFileSystemHandler | null = null
private bashHandler: AgentBashHandler | null = null
Expand Down Expand Up @@ -232,7 +230,6 @@ export class AgentToolManager {
}

constructor(options: AgentToolManagerOptions) {
this.yoBrowserPresenter = options.yoBrowserPresenter
this.agentWorkspacePath = options.agentWorkspacePath
this.configPresenter = options.configPresenter
this.commandPermissionHandler = options.commandPermissionHandler
Expand Down Expand Up @@ -275,17 +272,7 @@ export class AgentToolManager {
this.agentWorkspacePath = effectiveWorkspacePath
}

// 1. Yo Browser tools (agent mode only)
if (isAgentMode) {
try {
const yoDefs = await this.yoBrowserPresenter.getToolDefinitions(context.supportsVision)
defs.push(...yoDefs)
} catch (error) {
logger.warn('[AgentToolManager] Failed to load Yo Browser tool definitions', { error })
}
}

// 2. FileSystem tools (agent mode only)
// 1. FileSystem tools (agent mode only)
if (isAgentMode && this.fileSystemHandler) {
const fsDefs = this.getFileSystemToolDefinitions()
defs.push(...fsDefs)
Expand Down Expand Up @@ -324,6 +311,15 @@ export class AgentToolManager {
}
}

// 5. YoBrowser CDP tools (agent mode only)
if (isAgentMode) {
try {
defs.push(...presenter.yoBrowserPresenter.toolHandler.getToolDefinitions())
} catch (error) {
logger.warn('[AgentToolManager] Failed to load YoBrowser tools', { error })
}
}

return defs
}

Expand All @@ -335,17 +331,6 @@ export class AgentToolManager {
args: Record<string, unknown>,
conversationId?: string
): Promise<AgentToolCallResult | string> {
// Route to Yo Browser tools
if (toolName.startsWith('browser_')) {
const response = await this.yoBrowserPresenter.callTool(
toolName,
args as Record<string, unknown>
)
return {
content: typeof response === 'string' ? response : JSON.stringify(response)
}
}

// Route to FileSystem tools
if (this.isFileSystemTool(toolName)) {
if (!this.fileSystemHandler) {
Expand All @@ -364,6 +349,14 @@ export class AgentToolManager {
return await this.callChatSettingsTool(toolName, args, conversationId)
}

// Route to YoBrowser CDP tools
if (toolName.startsWith('yo_browser_')) {
const response = await presenter.yoBrowserPresenter.toolHandler.callTool(toolName, args)
return {
content: response
}
}

throw new Error(`Unknown Agent tool: ${toolName}`)
}

Expand Down
Loading