# 实施计划:Agent 文档工具 > 设计文档:`docs/superpowers/specs/2026-08-09-agent-document-tool-design.md` > 创建时间:2026-08-09 ## 概述 为 agent 系统新增"文档"工具,让 agent 能够创建、读取、更新、删除、列出文档(memory / document 两种类型),用于上下文优化与交付物产出。采用方案 A(纯 SQL 模糊搜索 + 摘要截断 + token 预算),零额外依赖。 ## 实施原则 - **TDD**:每个可测单元先写测试再写实现 - **DRY**:复用现有 `defineWrappedResponseHandler`、`R.success/R.error`、`validate`、`requireUser` 等工具 - **YAGNI**:不实现向量检索、不打通 articles、不做全文索引 - **频繁提交**:每个阶段完成后提交一次 - **小步前进**:每个 task 独立可验证 --- ## Phase 1:数据层(Schema + Migration + Seed) ### Task 1.1 — 创建 `agent_documents` 表 schema **文件**:`packages/drizzle-pkg/lib/schema/agent-document.ts`(新建) **内容**: - 定义 `agentDocuments` 表,字段: - `id` integer primary key autoincrement - `userId` integer not null(用户隔离) - `sessionId` text nullable(关联会话,可选) - `type` text not null default 'document'('memory' | 'document') - `title` text not null - `content` text not null - `summary` text nullable - `tags` text nullable(JSON 数组字符串) - `createdAt` integer not null(unix timestamp ms) - `updatedAt` integer not null(unix timestamp ms) - 定义 3 个索引: - `idx_agent_documents_user_id` on (userId) - `idx_agent_documents_user_type` on (userId, type) - `idx_agent_documents_user_session` on (userId, sessionId) - 导出 `AgentDocumentType` 常量数组 `['memory', 'document']` **验证**:文件存在、导出 `agentDocuments` 表对象、字段类型正确 **提交**:`feat(drizzle): add agent_documents table schema` ### Task 1.2 — 在 `AgentToolTypes` 枚举中新增 `document` **文件**:`packages/drizzle-pkg/lib/schema/agent-tool.ts`(修改) **内容**: - 在 `AgentToolTypes` 枚举中新增 `'document'` 值 **验证**:`AgentToolTypes` 包含 `'document'` **提交**:与 1.1 合并提交 ### Task 1.3 — 生成数据库迁移 **命令**: ```bash cd packages/drizzle-pkg && bun drizzle-kit generate ``` **验证**:`packages/drizzle-pkg/migrations/` 下新增 `0002_*.sql` 文件,包含 `CREATE TABLE agent_documents` 和 3 个 `CREATE INDEX` **提交**:`feat(drizzle): generate migration for agent_documents` ### Task 1.4 — 执行迁移 **命令**: ```bash cd packages/drizzle-pkg && bun drizzle-kit migrate ``` **验证**:开发数据库中 `agent_documents` 表存在 **提交**:与 1.3 合并(迁移文件即提交物) ### Task 1.5 — 在 seed.ts 中新增 document 工具种子 **文件**:`packages/drizzle-pkg/seed.ts`(修改) **内容**: - 在 `TOOLS_SEED` 数组末尾新增: ```ts { id: <下一个可用 id>, name: '文档工具', slug: 'document', description: '创建、读取、更新、删除和列出文档,用于记忆管理和交付物产出', type: 'document', config: { approvalRequiredActions: ['delete'], maxContentLength: 10000, defaultTokenBudget: 2000, maxListSize: 20, }, enabled: true, needsApproval: true, adminOnly: false, sortOrder: 150, } ``` - `sortOrder` 为 150(当前最大 140 + 10) **验证**:seed.ts 中 `TOOLS_SEED` 包含 slug='document' 的条目 **提交**:`feat(seed): add document tool seed data` --- ## Phase 2:Service 层(CRUD + 权限隔离) ### Task 2.1 — 编写 Service 层单元测试 **文件**:`server/service/agent-document/index.test.ts`(新建) **内容**:使用 `bun:test`,测试以下场景: - `createDocument`:创建成功,返回完整记录 - `createDocument`:content 超过 maxContentLength 抛错 - `getDocumentById`:存在且属于该用户 → 返回记录 - `getDocumentById`:存在但不属于该用户 → 返回 null(权限隔离) - `getDocumentById`:不存在 → 返回 null - `listDocuments`:按 userId 过滤 - `listDocuments`:按 type 过滤 - `listDocuments`:按关键词模糊搜索 title/content - `listDocuments`:分页正确 - `listDocuments`:list 结果不含 content 字段(上下文优化) - `updateDocument`:更新成功 - `updateDocument`:不属于该用户 → 返回 null - `deleteDocument`:删除成功 - `deleteDocument`:不属于该用户 → 返回 null - `generateSummary`:content 超过阈值时生成摘要 - `generateSummary`:content 短于阈值时返回 null **注意**:测试需要 mock 或使用测试数据库。参考 `server/service/captcha/store.test.ts` 的 `bun:test` 模式。由于 Service 直接操作 `dbGlobal`,测试使用独立测试数据库或 mock dbGlobal。 **验证**:测试文件存在,覆盖所有 CRUD + 权限 + 摘要场景 **提交**:`test(agent-document): add service layer unit tests` ### Task 2.2 — 实现 Service 层 **文件**:`server/service/agent-document/index.ts`(新建) **内容**: - 导入 `dbGlobal` from `#drizzle/db`、`agentDocuments` from `#drizzle/schema/agent-document`、`eq, and, like, desc, sql` from `drizzle-orm` - 导出类型 `AgentDocument`(inferSelect) - 导出函数: - `createDocument(params: { userId: number; sessionId?: string; type: 'memory' | 'document'; title: string; content: string; tags?: string[] }): Promise` - 自动生成 summary(调用 `generateSummary`) - 自动设置 createdAt/updatedAt - `getDocumentById(id: number, userId: number): Promise` - `WHERE id = ? AND userId = ?`(权限隔离) - `listDocuments(params: { userId: number; type?: string; keyword?: string; page: number; pageSize: number }): Promise<{ items: Omit[]; total: number }>` - list 结果排除 content 字段(上下文优化) - keyword 模糊匹配 title 和 content - 按 updatedAt desc 排序 - `updateDocument(id: number, userId: number, params: { title?: string; content?: string; tags?: string[] }): Promise` - `WHERE id = ? AND userId = ?` - content 更新时重新生成 summary - 更新 updatedAt - `deleteDocument(id: number, userId: number): Promise` - `WHERE id = ? AND userId = ?` - `generateSummary(content: string, maxChars = 200): string | null` - content.length > maxChars 时截取前 maxChars 字符 + '...' - 否则返回 null(表示无需摘要) **验证**:Task 2.1 的测试全部通过 ```bash bun test server/service/agent-document/index.test.ts ``` **提交**:`feat(agent-document): implement service layer with CRUD and permission isolation` --- ## Phase 3:Executor 层(工具执行器) ### Task 3.1 — 创建 document executor config **文件**:`server/service/agent-tool/executors/document/config.ts`(新建) **内容**:参考 `executors/fetch/config.ts` 模式 - 定义 `DocumentToolConfigSchema`: ```ts z.object({ approvalRequiredActions: z.array(z.string()).default(['delete']), maxContentLength: z.number().int().positive().default(10000), defaultTokenBudget: z.number().int().positive().default(2000), maxListSize: z.number().int().positive().default(20), }) ``` - 导出 `DEFAULT_DOCUMENT_CONFIG` - 导出 `parseDocumentConfig(config: unknown): DocumentToolConfig` **验证**:文件存在,schema 正确,默认值合理 **提交**:`feat(agent-tool/document): add config schema` ### Task 3.2 — 创建 document executor **文件**:`server/service/agent-tool/executors/document/document.ts`(新建) **内容**:实现 `ToolExecutor` 接口 - `buildInputSchema()`:返回 z.object,含 `action` 字段(enum: create/read/update/delete/list)+ 各 action 对应字段 - create: title, content, type?, tags?, sessionId? - read: id, fullContent? - update: id, title?, content?, tags? - delete: id - list: type?, keyword?, page?, pageSize? - `buildDescription()`:返回中文描述,说明各 action 用法 - `execute(input, ctx)`: - 根据 `input.action` 分发到对应处理函数 - 检查 `ctx.userId`,为 null 时返回 error - delete/update 操作检查 `ctx.approved`,若 config.approvalRequiredActions 包含该 action 且 `ctx.approved !== true`,返回 `{ success: false, error: '此操作需要审批' }` - create/update 时检查 content 长度不超过 `config.maxContentLength` - list 时 pageSize 不超过 `config.maxListSize` - read 时默认返回 summary,`fullContent=true` 才返回完整 content - 返回 `ToolResult` 格式 **验证**:executor 文件存在,实现 ToolExecutor 接口 **提交**:`feat(agent-tool/document): implement document executor` ### Task 3.3 — 创建 executor index 导出 **文件**:`server/service/agent-tool/executors/document/index.ts`(新建) **内容**: ```ts export { documentExecutor } from './document'; export { DEFAULT_DOCUMENT_CONFIG, parseDocumentConfig } from './config'; export type { DocumentToolConfig } from './config'; ``` **验证**:导出正确 **提交**:与 3.2 合并 ### Task 3.4 — 扩展 ToolContext 类型 **文件**:`server/service/agent-tool/registry.ts`(修改) **内容**: - 在 `ToolContext` 接口中新增 `approved?: boolean` 字段 **验证**:`ToolContext` 包含 `approved` 可选字段 **提交**:`feat(agent-tool): add approved field to ToolContext` ### Task 3.5 — 注册 document executor **文件**:`server/service/agent-tool/index.ts`(修改) **内容**: - 导入 `documentExecutor` from `./executors/document` - 在 `TOOL_TYPE_REGISTRY` 中新增 `'document': documentExecutor` **验证**:`TOOL_TYPE_REGISTRY` 包含 `'document'` 键 **提交**:`feat(agent-tool): register document executor` ### Task 3.6 — 修改 executeAgentTool 传递 approved 参数 **文件**:`server/service/agent-tool/index.ts`(修改) **内容**: - `executeAgentTool` 函数签名新增 `approved?: boolean` 参数 - 构造 `ToolContext` 时传入 `approved` - 在 `server/service/agent/chat-engine.ts` line 355 调用处传入 `approved: true`(审批通过后才执行) **验证**:`executeAgentTool` 接受并传递 `approved` 参数 **提交**:`feat(agent-tool): pass approved flag through executeAgentTool` --- ## Phase 4:API 层(REST 接口供前端查看页使用) ### Task 4.1 — 创建 API:GET /api/agent-documents(列表) **文件**:`server/api/agent-documents/index.get.ts`(新建) **内容**:参考 `server/api/articles/index.get.ts` 模式 - `defineWrappedResponseHandler` - `requireUser(event)` 获取 userId - 解析 query 参数:type, keyword, page (default 1), pageSize (default 20) - 调用 `listDocuments({ userId, type, keyword, page, pageSize })` - `R.success(result)` **验证**:API 可访问,返回当前用户的文档列表 **提交**:`feat(api): add GET /api/agent-documents list endpoint` ### Task 4.2 — 创建 API:GET /api/agent-documents/:id(详情) **文件**:`server/api/agent-documents/[id].get.ts`(新建) **内容**:参考 `server/api/articles/[id].get.ts` 模式 - `requireUser(event)` - 解析 id 参数 - 调用 `getDocumentById(id, userId)` - 不存在返回 `R.throwError(404, ...)` - `R.success(document)` **验证**:API 可访问,返回文档详情(含 content) **提交**:`feat(api): add GET /api/agent-documents/:id detail endpoint` ### Task 4.3 — 创建 API:PATCH /api/agent-documents/:id(更新) **文件**:`server/api/agent-documents/[id].patch.ts`(新建) **内容**: - `requireUser(event)` - 解析 id 参数 - `validate` body schema:title?, content?, tags? - 调用 `updateDocument(id, userId, data)` - 不存在返回 404 - `R.success(updated)` **验证**:API 可访问,更新成功 **提交**:`feat(api): add PATCH /api/agent-documents/:id update endpoint` ### Task 4.4 — 创建 API:DELETE /api/agent-documents/:id(删除) **文件**:`server/api/agent-documents/[id].delete.ts`(新建) **内容**: - `requireUser(event)` - 解析 id 参数 - 调用 `deleteDocument(id, userId)` - 不存在返回 404 - `R.success(null)` **验证**:API 可访问,删除成功 **提交**:`feat(api): add DELETE /api/agent-documents/:id delete endpoint` --- ## Phase 5:前端查看页 ### Task 5.1 — 创建文档列表页 **文件**:`app/pages/agent-documents/index.vue`(新建) **内容**:参考 `app/pages/articles/index.vue` 模式 - `definePageMeta({ layout: 'home' })` - 使用 `request` from `~/utils/http/factory` - 调用 `GET /api/agent-documents` 获取列表 - 展示表格/卡片列表:标题、类型(memory/document 标签)、摘要、更新时间 - 支持按类型筛选(全部 / memory / document) - 支持关键词搜索 - 分页 - 点击标题跳转 `/agent-documents/[id]` - 使用 `$toast` 提示错误 **验证**:页面可访问,显示文档列表,筛选/搜索/分页正常 **提交**:`feat(frontend): add agent documents list page` ### Task 5.2 — 创建文档详情页 **文件**:`app/pages/agent-documents/[id].vue`(新建) **内容**:参考 `app/pages/articles/[id].vue` 模式 - `definePageMeta({ layout: 'home' })` - 调用 `GET /api/agent-documents/:id` 获取详情 - 使用 `AgentMarkdown` 组件渲染 content - 显示标题、类型标签、创建时间、更新时间、tags - 编辑按钮 → 切换到编辑模式(textarea + 保存/取消) - 保存调用 `PATCH /api/agent-documents/:id` - 删除按钮 → 确认后调用 `DELETE /api/agent-documents/:id`,跳转回列表 - 使用 `$toast` 提示操作结果 **验证**:页面可访问,markdown 正确渲染,编辑/删除功能正常 **提交**:`feat(frontend): add agent document detail page with edit/delete` ### Task 5.3 — 在首页/导航中添加入口 **文件**:`app/layouts/home.vue` 或相关导航组件(修改) **内容**: - 添加"Agent 文档"导航链接,指向 `/agent-documents` - 仅登录用户可见 **验证**:导航中出现入口,点击跳转正确 **提交**:`feat(frontend): add agent documents nav entry` --- ## Phase 6:集成测试(E2E) ### Task 6.1 — E2E 测试:文档列表页可访问 **文件**:`e2e/specs/agent-documents.spec.ts`(新建) **内容**: - 登录 → 访问 `/agent-documents` - 验证页面标题、空状态提示 - 验证类型筛选按钮存在 **验证**:测试通过 **提交**:`test(e2e): add agent documents list page smoke test` ### Task 6.2 — E2E 测试:通过 agent 工具创建文档并验证 **文件**:`e2e/specs/agent-documents.spec.ts`(追加) **内容**: - 登录 → 在 agent 对话中发送消息触发 document 工具 create action - (需要 Mock LLM 返回工具调用,或直接通过 API 创建测试数据) - 访问 `/agent-documents` 验证文档出现在列表 - 点击进入详情页验证内容 **注意**:由于 Mock LLM 不支持工具调用,此测试通过 API 直接创建数据,验证前端展示 **验证**:测试通过 **提交**:`test(e2e): add agent document create and view test` --- ## Phase 7:收尾 ### Task 7.1 — 更新 AGENTS.md **文件**:`AGENTS.md`(修改) **内容**: - 在 E2E 测试文件表格中新增 `agent-documents.spec.ts` 行 - 如有其他需要记录的架构决策,补充说明 **验证**:AGENTS.md 更新 **提交**:`docs: update AGENTS.md with agent documents info` ### Task 7.2 — 全量验证 **命令**: ```bash # 类型检查 bun run typecheck # Lint bun run lint # 单元测试 bun test server/service/agent-document/ # E2E 测试 bun run test:e2e -- --grep "agent documents" ``` **验证**:全部通过 **提交**:如有修复则提交,否则无提交 --- ## 依赖关系 ``` Phase 1 (数据层) → Phase 2 (Service) → Phase 3 (Executor) → Phase 4 (API) ↓ Phase 5 (前端) → Phase 6 (E2E) → Phase 7 (收尾) ``` ## 风险与缓解 | 风险 | 缓解 | |------|------| | Service 测试需要数据库 | 使用 `bun:test` + 测试数据库,或 mock dbGlobal | | Mock LLM 不支持工具调用 | E2E 测试通过 API 直接创建数据验证前端 | | `executeAgentTool` 签名变更影响现有调用 | 新增可选参数,向后兼容 | | tags 字段 JSON 序列化 | Service 层统一处理 string[] ↔ JSON string 转换 | ## 预计工作量 - Phase 1:~30 分钟 - Phase 2:~1 小时 - Phase 3:~1.5 小时 - Phase 4:~45 分钟 - Phase 5:~1.5 小时 - Phase 6:~45 分钟 - Phase 7:~30 分钟 - **总计**:~6 小时