# 工具可见性分类机制 实施计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 在工具定义层引入 `visibility` 属性,从源头区分内部工具/普通工具/Skill,消除前端重复展示和 filter hack。 **Architecture:** 后端工具类型新增 `visibility` 字段,执行引擎根据该字段决定是否触发 UI 回调。前端移除 todo 过滤 hack,新建 `tool-card.vue` 组件区分 skill 和工具的展示。 **Tech Stack:** TypeScript, Midway.js, Vue 3, Element Plus, Socket.IO **Spec:** `docs/superpowers/specs/2026-04-14-tool-visibility-classification-design.md` --- ### Task 1: 后端 — 工具类型定义新增 visibility 字段 **Files:** - Modify: `packages/backend/src/modules/netaclaw/tools/common.ts:5-19` - [ ] **Step 1: 在 common.ts 中新增 ToolVisibility 类型和 visibility 字段** 在 `AgentTool` 类型定义之前,新增类型: ```typescript export type ToolVisibility = 'internal' | 'tool' | 'skill'; ``` 在 `AgentToolWithMeta` 类型中新增 `visibility` 可选字段: ```typescript export type AgentToolWithMeta = AgentTool & { ownerOnly?: boolean; displaySummary?: string; visibility?: ToolVisibility; }; ``` - [ ] **Step 2: 验证后端编译通过** Run: `cd packages/backend && npx tsc --noEmit 2>&1 | head -20` Expected: 无新增错误(已有错误可忽略) - [ ] **Step 3: Commit** ```bash git add packages/backend/src/modules/netaclaw/tools/common.ts git commit -m "feat(netaclaw): 工具类型新增 visibility 字段 (internal/tool/skill)" ``` --- ### Task 2: 后端 — 执行引擎根据 visibility 过滤回调 **Files:** - Modify: `packages/backend/src/modules/netaclaw/runtime/attempt.ts:59-78` - [ ] **Step 1: 修改 attempt.ts 工具执行循环** 将当前的工具执行循环(第 59-78 行)修改为根据 `visibility` 决定是否触发回调: ```typescript for (const tc of response.toolCalls) { toolCallCount++; const tool = tools.find(t => t.name === tc.name); const visibility = tool?.visibility ?? 'tool'; // 仅非 internal 工具触发 UI 回调 if (visibility !== 'internal') { onToolCall?.(tc.name, JSON.parse(tc.arguments)); } let resultText: string; if (!tool) { resultText = `错误: 工具 "${tc.name}" 不存在`; } else { try { const result = await tool.execute(tc.id, JSON.parse(tc.arguments)); resultText = typeof result === 'string' ? result : JSON.stringify(result); } catch (err: any) { resultText = `工具执行错误: ${err.message}`; } } // 仅非 internal 工具触发 UI 回调 if (visibility !== 'internal') { onToolResult?.(tc.name, resultText); } conversation.push({ role: 'tool', content: resultText, toolCallId: tc.id }); } ``` 关键点: - `tool` 变量的查找提前到回调判断之前 - `visibility` 从 tool 对象读取,默认 `'tool'` - `conversation.push` 不受影响(LLM 仍需看到工具返回值) - [ ] **Step 2: 验证后端编译通过** Run: `cd packages/backend && npx tsc --noEmit 2>&1 | head -20` Expected: 无新增错误 - [ ] **Step 3: Commit** ```bash git add packages/backend/src/modules/netaclaw/runtime/attempt.ts git commit -m "feat(netaclaw): 执行引擎根据 visibility 过滤 tool_call/tool_result 回调" ``` --- ### Task 3: 后端 — todo 工具标记为 internal **Files:** - Modify: `packages/backend/src/modules/netaclaw/runtime/agent.ts:70-79` - [ ] **Step 1: 在 agent.ts 中为 todoTool 设置 visibility: 'internal'** 将第 70-79 行的 todoTool 定义修改为: ```typescript const todoTool: AnyAgentTool = { ...todoToolSchema, label: '任务规划', visibility: 'internal', execute: async (_id: string, args: any) => { const result = executeTodo(todoStore, args); params.onTodoUpdate?.(result); return JSON.stringify(result); }, }; ``` 唯一改动:新增 `visibility: 'internal'` 一行。 - [ ] **Step 2: 验证后端编译通过** Run: `cd packages/backend && npx tsc --noEmit 2>&1 | head -20` Expected: 无新增错误 - [ ] **Step 3: Commit** ```bash git add packages/backend/src/modules/netaclaw/runtime/agent.ts git commit -m "feat(netaclaw): todo 工具标记为 internal,不再推送 tool_call/tool_result" ``` --- ### Task 4: 前端 — SkillProgress 类型新增 source 字段 **Files:** - Modify: `packages/frontend/src/modules/agent/types/index.d.ts:151-173` - [ ] **Step 1: 在 SkillProgress 接口中新增 source 字段** 在 `SkillProgress` 接口中新增 `source` 可选字段(第 152 行之后): ```typescript export interface SkillProgress { name: string; label?: string; status: 'running' | 'done' | 'error'; /** 来源类型:skill=Skill技能, tool=普通工具调用 */ source?: 'skill' | 'tool'; input?: any; result?: any; step?: string; percent?: number; detail?: string; current?: number; total?: number; taskId?: string; images?: string[]; tokens?: { input: number; output: number; total: number; apiCalls?: number }; } ``` - [ ] **Step 2: Commit** ```bash git add packages/frontend/src/modules/agent/types/index.d.ts git commit -m "feat(agent): SkillProgress 类型新增 source 字段区分 skill/tool" ``` --- ### Task 5: 前端 — Store 层标记 source 字段 **Files:** - Modify: `packages/frontend/src/modules/agent/store/chat.ts:264-316` - [ ] **Step 1: skill_start 事件处理中标记 source: 'skill'** 修改第 271-275 行,在 push 时添加 `source: 'skill'`: ```typescript case 'skill_start': { const startName = event.name; activeSkill.value = startName; const existingDone = skillProgress.value.find(s => s.name === startName && s.status === 'done'); const existingRunning = skillProgress.value.find(s => s.name === startName && s.status === 'running'); if (!existingDone && !existingRunning) { skillProgress.value.push({ name: startName, label: event.label || startName, status: 'running', source: 'skill', }); } break; } ``` - [ ] **Step 2: tool_call 事件处理中标记 source: 'tool'** 修改第 308-314 行,在 push 时添加 `source: 'tool'`: ```typescript case 'tool_call': { const toolName = event.toolName || event.name || 'unknown'; activeSkill.value = toolName; const existingRunning = skillProgress.value.find(s => s.name === toolName && s.status === 'running'); if (!existingRunning) { skillProgress.value.push({ name: toolName, label: toolName, status: 'running', source: 'tool', }); } break; } ``` - [ ] **Step 3: done 事件序列化中加入 source 字段** 在 `handleWSEvent` 的 `done` 分支中(约第 383 行),修改 `skillExecutions` 序列化,加入 `source`: ```typescript lastAssistant.metadata.skillExecutions = skillProgress.value.map(sp => ({ name: sp.name, label: sp.label, status: sp.status, result: sp.result, tokens: sp.tokens, source: sp.source })); ``` 唯一改动:在 map 对象中新增 `source: sp.source`。 - [ ] **Step 4: Commit** ```bash git add packages/frontend/src/modules/agent/store/chat.ts git commit -m "feat(agent): Store 层 skill/tool 事件标记 source 字段,序列化保留 source" ``` --- ### Task 6: 前端 — 新建 tool-card.vue 组件 **Files:** - Create: `packages/frontend/src/modules/agent/components/tool-card.vue` - [ ] **Step 1: 创建 tool-card.vue** ```vue ``` 样式部分(与 skill-card 类似但更简洁,无进度条区域): ```vue ``` - [ ] **Step 2: Commit** ```bash git add packages/frontend/src/modules/agent/components/tool-card.vue git commit -m "feat(agent): 新建 tool-card.vue 工具调用卡片组件" ``` --- ### Task 7: 前端 — chat.vue 移除 hack 并根据 source 选择组件 **Files:** - Modify: `packages/frontend/src/modules/agent/views/chat.vue:56-59` (模板) - Modify: `packages/frontend/src/modules/agent/views/chat.vue:341-343` (计算属性) - [ ] **Step 1: 在 chat.vue 的 script 中导入 tool-card 组件** 在已有的 `import` 区域(skill-card 导入附近)添加: ```typescript import ToolCard from '../components/tool-card.vue'; ``` - [ ] **Step 2: 移除 visibleSkillProgress 计算属性** 删除第 341-343 行: ```typescript // 删除这段 const visibleSkillProgress = computed(() => { return skillProgress.value.filter(sp => sp.name !== 'todo'); }); ``` - [ ] **Step 3: 修改模板中的 Skill 卡片渲染区域** 将第 56-59 行: ```vue
``` 替换为: ```vue
``` - [ ] **Step 4: 验证前端编译通过** Run: `cd packages/frontend && npx vue-tsc --noEmit 2>&1 | head -20` Expected: 无新增错误 - [ ] **Step 5: Commit** ```bash git add packages/frontend/src/modules/agent/views/chat.vue git commit -m "feat(agent): chat.vue 移除 todo filter hack,根据 source 区分 skill-card/tool-card" ``` --- ### Task 8: 端到端验证 **Files:** 无新增改动 - [ ] **Step 1: 启动后端验证编译** Run: `cd packages/backend && npx tsc --noEmit 2>&1 | head -30` Expected: 无新增错误 - [ ] **Step 2: 启动前端验证编译** Run: `cd packages/frontend && npx vue-tsc --noEmit 2>&1 | head -30` Expected: 无新增错误 - [ ] **Step 3: 功能验证清单** 手动测试(需启动 `pnpm dev`): 1. 发送一条需要任务规划的消息(如"帮我分析这个项目的架构,列出3个改进点") 2. 验证 todo-card 正常显示并实时更新(划掉已完成任务) 3. 验证对话流中不再出现 todo 的"工具完成"卡片 4. 如果 Agent 调用了其他工具(如搜索),验证 tool-card 正常显示 5. 如果 Agent 调用了 Skill,验证 skill-card 正常显示(带进度条) 6. 切换会话后,验证历史消息中的 skill/tool 卡片正确恢复 - [ ] **Step 4: 最终 Commit(如有修复)** ```bash git add -A git commit -m "fix(agent): 端到端验证修复" ```