diff --git a/.gitignore b/.gitignore index 0b1d045..8732262 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ packages/desktop/out/ .DS_Store .claude/ .codingcode/ -docs/ +docs-hidden/ typecheck-output.txt neoneo-v2 cb_test.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..05a980e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,79 @@ +# 贡献指南 + +感谢你对 Coding Code 的关注!本文档介绍如何参与项目贡献。 + +## 开发环境 + +```bash +git clone https://github.com/phantom5099/coding-code.git +cd coding-code +pnpm install +``` + +## 开发命令 + +```bash +pnpm run typecheck # 类型检查 +pnpm test # 运行测试 +pnpm run dev # 开发模式(watch) +``` + +## 目录结构 + +``` +coding-code/ +├── packages/ +│ ├── codingcode/src/ # @codingcode/core — 核心引擎 +│ │ ├── agent/ # ReAct Loop(纯引擎,无副作用) +│ │ ├── llm/ # LLM 客户端工厂(多厂商) +│ │ ├── mcp/ # MCP 服务集成 +│ │ ├── context/ # 上下文管理 + 自动压缩 +│ │ ├── session/ # JSONL 会话持久化 +│ │ ├── memory/ # 长期记忆(用户/项目级) +│ │ ├── checkpoint/ # 变更跟踪 + Shadow Git +│ │ ├── tools/ # 工具注册表 + 执行器 +│ │ │ └── domains/ # 按域分类的工具实现 +│ │ ├── approval/ # 执行前审批流水线 +│ │ ├── hooks/ # 可插拔钩子系统 +│ │ ├── skills/ # 技能系统(Markdown 技能包) +│ │ ├── subagent/ # 子智能体加载和注册 +│ │ ├── scheduler/ # 调度服务 +│ │ ├── rules/ # 规则注入 +│ │ ├── client/ # 客户端(HTTP / Direct / SSE) +│ │ ├── core/ # 核心工具类型和路径 +│ │ ├── runtime/ # 项目运行时 +│ │ ├── sandbox/ # 沙箱(stub,预留) +│ │ ├── server/ # Hono HTTP 服务 + SSE +│ │ ├── cli.ts # CLI 入口 +│ │ └── layer.ts # Effect Layer 入口 +│ ├── tui/src/ # @codingcode/tui — Ink 终端 UI +│ │ ├── components/ # App, InputBox, MessageItem 等 +│ │ └── hooks/ # useAgentRunner, useTerminalSize +│ ├── desktop/ # @codingcode/desktop — Electron 桌面应用 +│ │ ├── electron/ # 主进程(IPC、文件服务、Git 服务) +│ │ └── src/ # React 前端(Agent UI、设置面板) +│ └── infra/src/ # @codingcode/infra — 基础设施 +├── config/ # 模型配置 +│ └── models.json # 模型/厂商目录 +├── docs/ # 项目文档 +└── package.json # pnpm workspaces monorepo +``` + +## 提交 PR + +1. Fork 本仓库 +2. 创建功能分支:`git checkout -b feature/my-feature` +3. 提交变更:请确保通过 `pnpm run typecheck` 和 `pnpm test` +4. 推送分支:`git push origin feature/my-feature` +5. 创建 Pull Request + +## 代码规范 + +- TypeScript 严格模式 +- Effect TS 管理依赖注入和错误处理 +- 新功能需附带测试 + +## 报告问题 + +- 使用 [GitHub Issues](https://github.com/phantom5099/coding-code/issues) 提交 Bug 报告或功能建议 +- 请包含复现步骤、预期行为和实际行为 diff --git a/README.md b/README.md index 78652d0..30bc5ad 100644 --- a/README.md +++ b/README.md @@ -1,449 +1,161 @@ -# Coding Code - -> 手写 ReAct Loop · 零框架依赖 · 多端统一 · 深度可配置 +
-**Coding Code** 是一个终端原生的 AI 编程助手。核心引擎纯手写 ReAct 循环,通过 HTTP 服务化对外暴露,TUI / Web 等所有端共享同一份编排逻辑。用户可以自由配置子智能体、工具、提示词、钩子、模型和工作流——没有黑盒,所有行为都可定制。 +# Coding Code -## 三层设计 +**手写 ReAct Loop · 零框架依赖 · 多端统一 · 深度可配置** -| 层 | 包 | 职责 | -|---|---|---| -| 客户端 | `@codingcode/tui` | Ink/React 终端渲染,纯 UI 层 | -| 核心 | `@codingcode/core` | ReAct 引擎、工具系统、MCP 集成、上下文管理、长期记忆、会话持久化、HTTP 服务 | -| 基础设施 | `@codingcode/infra` | 配置加载、日志、共享类型 | +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/) -## 设计原则 +终端原生的 AI 编程助手。核心引擎纯手写 ReAct 循环,通过 HTTP 服务化对外暴露,TUI / Desktop / SDK 等所有端共享同一份编排逻辑。没有黑盒,所有行为都可定制。 -- **Agent 是纯 ReAct 循环** — 不持有 Session、不调 Bus、不感知传输协议。Agent 只做一件事:while 循环调用 LLM + 执行工具。 -- **编排逻辑写死在一处** — `orchestration/bootstrap.ts` 是唯一的跨域编排入口,TUI、HTTP API、SDK 都走同一份代码。 -- **所有端共享 HTTP API** — Agent 作为独立 HTTP 服务运行,任何能发 HTTP 请求的客户端都可以接入。 -- **Effect TS 托管依赖** — 编译期强制处理错误,Layer 声明式装配,测试时可替换任意服务。 +
--- -## 快速开始 - -```bash -# 安装依赖 -npm install - -# 配置 API Key(以 DeepSeek 为例) -export DEEPSEEK_API_KEY=sk-xxx - -# 启动(server + TUI) -npm start - -# 仅启动 server(供 Web / SDK 调用) -npm start serve - -# 仅启动 TUI(连接已有 server) -npm start tui -``` - -首次启动后,默认以 **Coder** 角色运行,使用 `models.json` 中标记为 `active` 的模型。 - -### 目录结构 +## 核心特性 -``` -coding-agent/ -├── packages/ -│ ├── codingcode/src/ # @codingcode/core — 核心引擎 -│ │ ├── agent/ # ReAct Loop(纯引擎,无副作用) -│ │ ├── llm/ # LLM 客户端工厂(多厂商) -│ │ ├── mcp/ # MCP 服务集成 -│ │ ├── context/ # 上下文管理 + 自动压缩 -│ │ ├── session/ # JSONL 会话持久化 -│ │ ├── memory/ # 长期记忆(用户/项目级) -│ │ ├── checkpoint/ # 变更跟踪 + Git Shadow -│ │ ├── tools/ # 工具注册表 + 执行器 -│ │ │ └── domains/ # 按域分类的工具实现 -│ │ ├── approval/ # 执行前审批流水线 -│ │ ├── hooks/ # 可插拔钩子系统 -│ │ ├── prompts/ # 系统提示词构建 -│ │ ├── rules/ # 规则注入 -│ │ ├── sandbox/ # OS 级沙箱集成 -│ │ ├── server/ # Hono HTTP 服务 + SSE -│ │ ├── orchestration/ # 跨域编排入口 -│ │ ├── agent-state/ # 代理状态管理 -│ │ ├── subagent/ # 子智能体加载和注册 -│ │ ├── skills/ # 技能系统 -│ │ └── cli.ts # CLI 入口 -│ ├── tui/src/ # @codingcode/tui — Ink 终端 UI -│ │ ├── components/ # App, InputBox, MessageItem 等 -│ │ └── hooks/ # useAgentRunner, useTerminalSize -│ └── infra/src/ # @codingcode/infra — 基础设施 -├── models.json # 模型/厂商目录 -└── package.json # npm workspaces monorepo -``` +- 🔄 **手写 ReAct 循环** — 不依赖外部 Agent 框架,完全可控的 Agent 引擎 +- 🌐 **HTTP 服务化** — Agent 作为独立 HTTP 服务运行,任何端平等接入 +- 🔧 **深度可配置** — 子智能体、工具、提示词、钩子、模型全部可定制 +- 🛡️ **审批流水线** — 六层决策链 + 预设安全规则,开箱即用 +- 🧠 **长期记忆** — 跨会话自动提取和加载用户/项目上下文 +- 🔌 **MCP 集成** — 通过 Model Context Protocol 扩展工具能力 +- 📡 **实时流式** — SSE 推送,多端同步响应 +- 💾 **Checkpoint** — Shadow Git 变更跟踪与一键回滚 --- -## 配置 - -Coding Code 的核心哲学是**所有行为都可配置**。用户通过以下配置控制 Agent: +## 快速开始 -| 配置文件 | 作用 | -|---|---| -| `models.json` | 模型厂商、模型列表、API 地址 | -| `codingcode.yaml` | 应用级配置(并发数、超时、token 预算等) | -| `~/.codingcode/rules.md` + `./AGENTS.md` | 全局 + 项目级规则,注入 system prompt | -| `mcp.json` (可选) | MCP 服务配置 | -| `~/.codingcode/memory.yaml` (可选) | 长期记忆配置 | +### 前置要求 -### 模型配置 (`models.json`) +- Node.js >= 18 +- 一个 LLM API Key(支持 DeepSeek、OpenAI、Gemini 等厂商) -```json -{ - "active": "deepseek", - "providers": [ - { - "name": "deepseek", - "driver": "deepseek", - "base_url": "https://api.deepseek.com", - "api_key_env": "DEEPSEEK_API_KEY", - "default_model": "deepseek-v4-flash", - "models": [ - { "id": "deepseek-v4-flash", "name": "DeepSeek V4 Flash" }, - { "id": "deepseek-chat", "name": "DeepSeek V3" } - ] - } - ] -} -``` +### 安装与启动 -- `driver`: `"deepseek"` 使用原生 SDK,`"openai"` 使用 OpenAI 兼容 API -- `active`: 指定默认使用的厂商 -- `api_key_env`: 从环境变量读取 API Key -- 运行时可通过 `/model` 命令或 API 切换模型 +```bash +# 1. 克隆并安装 +git clone https://github.com/phantom5099/coding-code.git +cd coding-code +pnpm install -### 规则配置 +# 2. 配置 API Key +export DEEPSEEK_API_KEY=sk-xxx +# 3. 启动(server + TUI) +pnpm start ``` -~/.codingcode/rules.md # 全局规则,所有项目生效 -./AGENTS.md # 项目级规则,自动注入 system prompt -``` - -规则以 Markdown 编写,在每次 LLM 调用时自动注入到 system prompt 中。可以在这里定义编码规范、项目约定、安全策略等。 - ---- - -## 工具系统 - -### 内置工具 -#### 文件操作 (fs) -| 工具 | 功能 | -|---|---| -| `read_file` | 读取文件,支持 offset/limit 行号控制 | -| `write_file` | 写入/创建文件 | -| `edit_file` | 编辑文件中的特定代码段 | -| `glob_files` | 模式匹配查找文件 | -| `search_code` | 正则搜索项目代码 | - -#### 命令执行 -| 工具 | 功能 | -|---|---| -| `execute_command` | 执行 shell 命令(带沙箱过滤和超时) | - -#### 网络 -| 工具 | 功能 | -|---|---| -| `fetch_url` | HTTP GET 请求(带超时) | -| `search_web` | Web 搜索(需配置) | +启动成功后,终端会显示交互式 TUI 界面。 -#### 代理状态 -| 工具 | 功能 | -|---|---| -| `read_todo` | 读取代理的任务列表 | -| `write_todo` | 修改代理的任务列表 | -| `tool_search` | 发现和加载可用工具 | - -#### 子智能体 -| 工具 | 功能 | -|---|---| -| `delegate_to_subagent` | 将任务委派给子智能体 | +### 其他启动方式 -### 工具加载机制 +```bash +pnpm start serve # 仅启动 HTTP server(供 Web / SDK 调用) +pnpm start tui # 仅启动 TUI(连接已有 server) +``` -- **Core 工具**:始终可用,在启动时注册 -- **Deferred 工具**:按需加载,通过 `tool_search` 发现后动态加载 -- **MCP 工具**:从 MCP 服务自动导入和注册 +### SDK 调用示例 -### 自定义工具 +```typescript +import { createHttpClient } from '@codingcode/core/client/http'; -工具通过 `ToolService` 注册,每个工具实现 `ToolDefinition` 接口: +const client = await createHttpClient('http://localhost:8080'); -```typescript -interface ToolDefinition { - name: string; - description: string; - parameters: ZodSchema | Record; - jsonSchema?: Record; - deferred?: boolean; - execute(args: Record, signal?: AbortSignal): Promise; +for await (const chunk of client.sendMessage('帮我写一个快排')) { + if (chunk.type === 'text') { + process.stdout.write(chunk.text); + } } ``` -在 `cli.ts` 中向 `ToolService` 注册新工具,Agent 会自动将其暴露给 LLM。 - -### 沙箱隔离 - -所有工具执行经过两层安全保护: - -**审批流水线**(始终生效):六层决策链——规则引擎(硬 deny)→ 只读白名单 → 权限模式 → 钩子策略 → 用户确认 → 审计日志。开箱即用,无需额外安装。 - -**OS 级沙箱**(可选):需要安装 `@anthropic-ai/sandbox-runtime`,提供文件系统隔离、网络隔离、防绕过等能力。 - --- -## 长期记忆系统 (Memory) +## 架构 -Coding Code 支持多会话的长期记忆,自动从对话中提取和存储关键信息: - -### 内存类型 - -| 类型 | 位置 | 作用 | -|---|---|---| -| **用户级** | `~/.codingcode/memory/` | 跨所有项目的个人知识库 | -| **项目级** | `./.codingcode/memory/` | 特定项目的上下文 | - -### 记忆内容 - -- **user**: 用户角色、技能、偏好 -- **feedback**: 工作流程中的教训和已验证的方法 -- **project**: 当前项目的目标、deadline、决策 -- **reference**: 外部资源和文档链接 - -### 自动提取 - -Agent 在每次会话后自动: -1. 从对话中识别值得保存的信息 -2. 按类型分类和结构化 -3. 存储到对应的内存文件 -4. 在下次启动时自动加载到 system prompt - -### 手动编辑 - -记忆文件采用 Markdown 格式,支持手动编辑: - -```markdown ---- -name: feature-name -description: one-line summary -metadata: - type: user/feedback/project/reference ---- - -Memory content here... ``` +┌──────────────────────────────────────────────────────────┐ +│ 客户端层 │ +│ @codingcode/tui (Ink) · @codingcode/desktop (Electron) │ +└──────────────────────────┬───────────────────────────────┘ + │ HTTP / SSE(AgentClient 接口) +┌──────────────────────────┴───────────────────────────────┐ +│ 核心引擎层 │ +│ @codingcode/core │ +│ ReAct Loop · 工具 · MCP · 上下文 · 记忆 · Checkpoint │ +│ 钩子 · 子智能体 · 技能 · 审批 · 会话 · 调度 │ +└──────────────────────────┬───────────────────────────────┘ + │ +┌──────────────────────────┴───────────────────────────────┐ +│ 基础设施层 │ +│ @codingcode/infra │ +│ 配置加载 · 日志 · 共享类型 │ +└──────────────────────────────────────────────────────────┘ +``` + +**设计原则**:Agent 是纯 ReAct 循环,不持有 Session、不感知传输协议。所有端通过统一的 `AgentClient` 接口接入,共享同一份编排逻辑。Effect TS 托管依赖注入,编译期强制处理错误。 --- -## MCP 集成 (Model Context Protocol) +## 配置 -Coding Code 集成 MCP 协议,允许通过外部服务扩展工具: +所有行为都可配置。核心配置文件: -### 配置 MCP 服务 +| 配置文件 | 作用 | 详见 | +|---------|------|------| +| `config/models.json` | 模型厂商、模型列表、API 地址 | [→ configuration.md](docs/configuration.md) | +| `codingcode.yaml` | 应用级配置(并发数、超时等) | [→ configuration.md](docs/configuration.md) | +| `~/.codingcode/rules.md` + `./AGENTS.md` | 全局 + 项目级规则,注入 system prompt | [→ configuration.md](docs/configuration.md) | +| `mcp.yaml` (可选) | MCP 服务配置 | [→ mcp.md](docs/mcp.md) | -在项目根目录创建 `mcp.json`: +快速配置模型(`config/models.json`): ```json { - "mcpServers": [ - { - "name": "custom-tools", - "command": "node", - "args": ["./server.js"], - "type": "stdio" - } - ] + "providers": [{ + "name": "deepseek", + "driver": "deepseek", + "base_url": "https://api.deepseek.com", + "api_key_env": "DEEPSEEK_API_KEY", + "default_model": "deepseek-v4-flash", + "models": [ + { "id": "deepseek-v4-flash", "name": "DeepSeek V4 Flash", "context_window": 1048576, "max_output_tokens": 384000 } + ] + }] } ``` -### 自动集成 - -启动时,Coding Code 会: -1. 连接所有配置的 MCP 服务 -2. 列出各服务提供的工具 -3. 自动注册为 Tool Definition -4. Agent 可直接调用,无需额外配置 - -### MCP 工具白名单 - -在角色配置中指定工具白名单时,MCP 工具遵循同样规则。 - ---- - -## Checkpoint 系统 - -Coding Code 记录所有文件变更,支持查看历史和回滚: - -### 功能 - -- **Shadow Git**: 独立的变更日志,不依赖用户的 .git -- **Ledger**: 按会话记录每个文件操作 -- **Diff 视图**: 查看特定会话前后的文件差异 -- **回滚**: 恢复到任意检查点 - -### 使用 - -```bash -# 查看变更历史 -GET /api/sessions/:id/checkpoint - -# 恢复到特定检查点 -POST /api/sessions/:id/checkpoint/restore -``` - ---- - -## 钩子系统 (Hooks) - -8 个可插拔钩子点,用户可以在关键节点注入自定义逻辑: - -| 钩子点 | 触发时机 | -|---|---| -| `tool.execute.before` | 工具执行前 | -| `tool.execute.after` | 工具执行成功后 | -| `tool.execute.error` | 工具执行失败后 | -| `llm.request.before` | LLM 调用前 | -| `llm.response.after` | LLM 响应成功后 | -| `llm.response.error` | LLM 调用失败后 | -| `session.save.before` | 会话保存前 | -| `session.save.after` | 会话保存后 | - -```typescript -hookRegistry.on('llm.request.before', async (messages) => { - const estimatedTokens = JSON.stringify(messages).length / 4; - console.log(`[Hook] 即将调用 LLM,预估 ${Math.round(estimatedTokens)} tokens`); -}); -``` +`driver` 支持 `"deepseek"`(原生 SDK)、`"openai"`(OpenAI 兼容 API)和 `"gemini"`(Google Gemini)。运行时可通过 `/model` 命令切换。 --- -## 子智能体系统 (Subagent) - -每个子 Agent 是独立的 ReAct 引擎实例,拥有受限的工具集和独立的上下文: - -### 特性 - -- **独立执行**: 子 Agent 在独立的 Effect Context 中运行 -- **受限工具集**: 每个子 Agent 模板定义自己的工具白名单 -- **独立上下文**: 不共享主 Agent 的消息历史 -- **自由定义**: 用户可配置任意数量的子 Agent 模板 - -### 子 Agent 模板 - -在 `subagents.json` 中定义(或在代码中注册): - -```typescript -{ - name: "code-searcher", - description: "专门搜索代码库的子 Agent", - tools: ["search_code", "read_file"], - systemPrompt: "You are a code search specialist...", - maxSteps: 10, - timeoutMs: 60000 -} -``` - -### 使用 - -主 Agent 通过 `delegate_to_subagent` 工具委派任务: +## 功能导航 -```typescript -// Agent 调用 -await agent.executeTool('delegate_to_subagent', { - subagent: 'code-searcher', - task: 'Find all usages of getUserById function' -}); -``` +| 功能 | 说明 | 文档 | +|------|------|------| +| 🛠️ 工具系统 | 内置文件/命令/网络工具 + 自定义工具注册 + 审批流水线 | [→ tools.md](docs/tools.md) | +| 🧠 长期记忆 | 跨会话自动提取用户偏好、项目上下文,支持手动编辑 | [→ memory.md](docs/memory.md) | +| 🔌 MCP 集成 | 通过 Model Context Protocol 连接外部工具服务 | [→ mcp.md](docs/mcp.md) | +| 💾 Checkpoint | Shadow Git 变更跟踪、Diff 视图、一键回滚 | [→ checkpoint.md](docs/checkpoint.md) | +| 🪝 钩子系统 | 18 个可插拔钩子点,在关键节点注入自定义逻辑 | [→ hooks.md](docs/hooks.md) | +| 🤖 子智能体 | 独立 ReAct 实例,受限工具集,可并行委派任务 | [→ subagent.md](docs/subagent.md) | +| 📦 上下文压缩 | 超预算自动压缩,截断/总结两种策略 | [→ context.md](docs/context.md) | +| 🎯 技能系统 | 可插拔的 Markdown 技能包,扩展 Agent 能力 | [→ skills.md](docs/skills.md) | --- ## 工作流 -完整的工作流如下: - ``` -用户输入 - ↓ -System Prompt(角色 + 规则 + 记忆) - ↓ -LLM 调用(可切换模型) - ↓ -ReAct Loop(步数可配置) - ├── Tool Call - ├── Approval Pipeline(审批) - ├── Tool Execution(沙箱执行) - ├── Hook Points(钩子注入) +用户输入 → System Prompt(角色 + 规则 + 记忆 + 技能)→ LLM 调用 → ReAct Loop + ├── Tool Call → Approval → Execution → Hook ├── Subagent Delegation(可选,并行) - └── Context Compression(自动触发,90% token 预算) - ↓ -响应流(SSE 实时推送) - ↓ -会话持久化(JSONL) - ↓ -Checkpoint 记录(文件变更) - ↓ -Memory 提取(自动保存) -``` - -每个环节都可配置,没有黑盒的"工作流引擎"。 - ---- - -## 多端接入 - -Coding Code 的 Agent 是一个**独立的 HTTP 服务**,所有端平等接入: - -``` - ┌─────────────────┐ - │ Hono HTTP Server │ - │ /api/sessions │ - │ /api/messages │← SSE 流式响应 - │ /api/models │ - │ /api/roles │ - └────────┬────────┘ - │ - ┌────────────────────┼────────────────────┐ - ▼ ▼ ▼ - ┌─────────┐ ┌─────────┐ ┌─────────┐ - │ TUI │ │ Web │ │ SDK │ - │ (Ink) │ │ (React) │ │ (Node) │ - └─────────┘ └─────────┘ └─────────┘ -``` - -### TUI (`@codingcode/tui`) - -- 基于 **Ink v7** (React for terminal) -- 键盘快捷键: 消息聚焦 (↑↓)、展开详情 (Ctrl+O)、面板切换 (/model, /role, /sessions, /help) -- 通过 HTTP 客户端连接 server,不持有 Agent 引用 - -### SDK - -```typescript -import { CodingCodeClient } from '@codingcode/core'; - -const client = new CodingCodeClient('http://localhost:3000'); -const session = await client.createSession(); - -for await (const chunk of client.sendMessage(session.id, '帮我写一个快排')) { - process.stdout.write(chunk); -} + └── Context Compression(自动触发) +→ SSE 流式响应 → 会话持久化 → Checkpoint → Memory 提取 ``` ---- - -## 上下文管理与压缩 - -上下文管理是独立域,不耦合在 Agent 内部: - -- **自动压缩**: 当上下文超过 token 预算的 90%(默认 200K),自动触发压缩 -- **两种策略**: - 1. 截断旧工具输出 - 2. 总结最早的消息 -- **显式压缩**: 可通过 `POST /api/sessions/:id/compact` 手动触发 +每个环节都可配置,没有黑盒。 --- @@ -457,26 +169,41 @@ for await (const chunk of client.sendMessage(session.id, '帮我写一个快排' | LLM SDK | Vercel AI SDK v6 + @ai-sdk/deepseek + @ai-sdk/openai | | HTTP 框架 | Hono 4.x | | TUI | Ink 7.x + React 19 | +| Desktop | Electron 35 + React 19 + Zustand 5 | | MCP | @modelcontextprotocol/sdk 1.29.x | | 校验 | Zod 4.x | | 日志 | pino 9.x + pino-pretty 13.x | | 配置 | YAML | | 测试 | vitest | -| 包管理 | npm workspaces (monorepo) | +| 包管理 | pnpm workspaces (monorepo) | --- ## 开发 ```bash -npm install -npm run typecheck # 类型检查 -npm test # 运行测试 -npm run dev # 开发模式(watch) +pnpm install +pnpm run typecheck # 类型检查 +pnpm test # 运行测试 +pnpm run dev # 开发模式(watch) ``` +详见 [CONTRIBUTING.md](CONTRIBUTING.md)。 + --- +## 贡献 + +欢迎贡献!请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 了解: + +- 如何提交 Issue 和 PR +- 开发环境搭建 +- 代码规范和提交约定 + +## 安全 + +如发现安全漏洞,请通过 [GitHub Security Advisories](https://github.com/phantom5099/coding-code/security/advisories/new) 私密报告,请勿公开提交 Issue。 + ## License -MIT +[MIT](LICENSE) diff --git a/docs/checkpoint.md b/docs/checkpoint.md new file mode 100644 index 0000000..98e4121 --- /dev/null +++ b/docs/checkpoint.md @@ -0,0 +1,121 @@ +# Checkpoint 系统 + +Coding Code 记录所有文件变更,支持查看历史和回滚。本文档介绍 Shadow Git、Ledger、Diff 视图和回滚功能。 + +--- + +## 工作原理 + +Checkpoint 系统基于 Shadow Git 实现——一个独立于用户 `.git` 的变更日志系统: + +- **存储位置**:`~/.codingcode/project/{encodedProjectPath}/checkpoint/repo.git` +- **隔离机制**:使用 `--git-dir` 和 `--work-tree` 分离,不影响用户仓库 +- **大小上限**:1024 MB +- **文件锁**:`repo.lock` 防止并发写入 + +### 忽略规则 + +以下目录和文件不会被跟踪: + +- `node_modules/`、`.venv/`、`dist/`、`build/` +- `*.log`、`.env`、`.DS_Store` + +### 提交格式 + +Shadow Git 的提交消息格式为: + +- `turn-{shortSid}-{turnId}-baseline`:轮次开始前的快照 +- `turn-{shortSid}-{turnId}-final`:轮次结束后的快照 + +--- + +## API 接口 + +所有路由挂载在 `/api/sessions` 下。 + +### 查看 Diff + +| 路由 | 方法 | 说明 | +|------|------|------| +| `/api/sessions/:id/checkpoints/latest/diff` | GET | 获取最新 checkpoint 的 diff | +| `/api/sessions/:id/checkpoints/:turnId/diff` | GET | 获取指定 turn 的 diff | + +响应格式: + +```typescript +interface CheckpointDiff { + turnId: number; + files: Array<{ + path: string; + status: string; // added / modified / deleted + diff: string; // unified diff + insertions: number; + deletions: number; + }>; +} +``` + +### 回退文件 + +| 路由 | 方法 | Body | 说明 | +|------|------|------|------| +| `/api/sessions/:id/checkpoints/latest/revert-file` | POST | `{ cwd, file }` | 回退最新 checkpoint 的单个文件 | +| `/api/sessions/:id/checkpoints/latest/revert-files` | POST | `{ cwd, files }` | 回退最新 checkpoint 的多个文件 | + +### 回滚到指定轮次 + +| 路由 | 方法 | Body | 说明 | +|------|------|------|------| +| `/api/sessions/:id/rollback-preview` | GET | query: `throughTurnId` | 预览回退到指定 turn 的 diff | +| `/api/sessions/:id/rollback-code-to-turn` | POST | `{ cwd, throughTurnId }` | 代码回退到指定 turn | +| `/api/sessions/:id/rollback-context` | POST | `{ cwd, throughTurnId }` | 上下文回退到指定 turn | +| `/api/sessions/:id/rollback-both-to-turn` | POST | `{ cwd, throughTurnId }` | 代码 + 上下文同时回滚 | + +### 撤销回滚 + +| 路由 | 方法 | Body | 说明 | +|------|------|------|------| +| `/api/sessions/:id/undo-code-rollback` | POST | `{ cwd, force?, files? }` | 撤销上次代码回滚 | +| `/api/sessions/:id/rollback-state` | GET | - | 获取当前回退状态 | + +--- + +## Ledger 数据结构 + +每次回滚操作记录为 `CodeRestoreEntry`: + +```typescript +interface CodeRestoreEntry { + id: string; // 唯一标识 + sessionId: string; // 会话 ID + action: 'checkpoint-files' | 'rollback-to-turn'; // 操作类型 + throughTurnId: number; // 回退到的轮次 + affectedTurns: number[]; // 受影响的轮次列表 + selectedFiles: string[]; // 受影响的文件列表 + safetyCommit: string; // 安全提交的 SHA + timestamp: string; // 操作时间 +} +``` + +存储位置:`{gitDir}/../last-restore-{shortSid}.json` + +--- + +## 回退状态查询 + +通过 `GET /api/sessions/:id/rollback-state` 获取当前回退状态: + +```typescript +interface RollbackState { + context: { + active: boolean; + currentThroughTurnId: number | null; + }; + code: { + canUndoLast: boolean; + lastEntry: CodeRestoreEntry | null; + revertedFiles: string[]; + lastEntryId: string | null; + }; +} +``` diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..fae0f11 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,193 @@ +# 配置 + +Coding Code 的核心哲学是所有行为都可配置。本文档详细介绍所有配置文件及其选项。 + +--- + +## 配置文件总览 + +| 配置文件 | 位置 | 作用 | 详见 | +|---------|------|------|------| +| `codingcode.yaml` | `~/.codingcode/config.yaml` | 应用级配置 | 本文档 | +| `models.json` | `config/models.json` | 模型厂商、模型列表、API 地址 | 本文档 | +| `rules.md` | `~/.codingcode/rules.md` + `./AGENTS.md` | 全局 + 项目级规则 | 本文档 | +| `mcp.yaml` | `~/.codingcode/mcp.yaml` + `.codingcode/mcp.yaml` | MCP 服务配置 | [→ mcp.md](mcp.md) | +| `hooks.yaml` | `~/.codingcode/hooks.yaml` + `.codingcode/hooks.yaml` | 钩子配置 | [→ hooks.md](hooks.md) | +| `agents/*.md` | `~/.codingcode/agents/` + `.codingcode/agents/` | 子智能体 profile | [→ subagent.md](subagent.md) | +| `memory.md` | `~/.codingcode/memory.md` + `.codingcode/memory.md` | 长期记忆 | [→ memory.md](memory.md) | + +--- + +## codingcode.yaml + +应用级主配置文件,存放在 `~/.codingcode/config.yaml`。使用 `deepMerge` 合并默认值。 + +### 完整配置项 + +```yaml +server: + port: 8080 # HTTP 服务端口 + +maxSteps: 200 # Agent 最大步数 +maxStopContinuations: 2 # 最大停止续行次数 + +# activeModel: # 可选,覆盖 models.json 中的默认模型 +# model: "" # 模型 ID +# apiKeyEnv: "" # API Key 环境变量名 + +context: + compactionModel: "" # 压缩用模型,空字符串回退主模型 + +memory: + enabled: false # 启用长期记忆 + model: "" # 记忆提取模型,空字符串回退主模型 + projectFile: ".codingcode/memory.md" # 项目记忆文件路径 + userFile: "~/.codingcode/memory.md" # 用户记忆文件路径 + maxBytes: 16384 # 记忆文件最大字节数 + promptMaxBytes: 8192 # 注入提示的最大字节数 + extraTypes: [] # 自定义记忆类型 + disabledTypes: [] # 禁用的记忆类型名 +``` + +### 字段详细说明 + +| 字段 | 默认值 | 说明 | +|------|--------|------| +| `server.port` | `8080` | HTTP 服务监听端口 | +| `maxSteps` | `200` | 单次 Agent 执行的最大步数限制 | +| `maxStopContinuations` | `2` | Agent 停止后最大续行次数 | +| `activeModel` | 无(可选) | 覆盖 models.json 中的默认模型,不设置则使用 models.json 配置 | +| `context.compactionModel` | `''` | 上下文压缩使用的模型,空字符串回退到主会话 LLM | +| `memory.enabled` | `false` | 是否启用长期记忆系统 | +| `memory.model` | `''` | 记忆提取使用的模型,空字符串回退到主模型 | +| `memory.projectFile` | `'.codingcode/memory.md'` | 项目级记忆文件路径 | +| `memory.userFile` | `'~/.codingcode/memory.md'` | 用户级记忆文件路径 | +| `memory.maxBytes` | `16384` | 单个记忆文件的最大字节数 | +| `memory.promptMaxBytes` | `8192` | 注入 system prompt 的记忆内容最大字节数 | +| `memory.extraTypes` | `[]` | 自定义记忆类型列表 | +| `memory.disabledTypes` | `[]` | 禁用的内置记忆类型名列表 | + +### 自定义记忆类型示例 + +```yaml +memory: + enabled: true + extraTypes: + - name: feedback + description: 工作流程中的教训和已验证的方法 + enabled: true + - name: decision + description: 重要的架构和设计决策 + enabled: true + disabledTypes: + - reference +``` + +--- + +## models.json + +模型配置文件,存放在 `config/models.json`。定义可用的 LLM 厂商和模型。 + +### 完整格式 + +```json +{ + "providers": [ + { + "name": "deepseek", + "driver": "deepseek", + "base_url": "https://api.deepseek.com", + "api_key_env": "DEEPSEEK_API_KEY", + "default_model": "deepseek-v4-flash", + "models": [ + { + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "context_window": 1048576, + "max_output_tokens": 384000, + "capabilities": { "vision": "supported", "reasoning": "supported" } + } + ] + }, + { + "name": "openai", + "driver": "openai", + "base_url": "https://api.openai.com/v1", + "api_key_env": "OPENAI_API_KEY", + "default_model": "gpt-4o", + "models": [ + { + "id": "gpt-4o", + "name": "GPT-4o", + "context_window": 128000, + "max_output_tokens": 16384, + "capabilities": { "vision": "supported", "reasoning": "supported" } + } + ] + } + ] +} +``` + +### 字段说明 + +| 字段 | 说明 | +|------|------| +| `providers[].name` | 厂商名称,用于切换和引用 | +| `providers[].driver` | 驱动类型:`"deepseek"` 使用原生 SDK,`"openai"` 使用 OpenAI 兼容 API,`"gemini"` 使用 Google Gemini | +| `providers[].base_url` | API 基础 URL | +| `providers[].api_key_env` | 从环境变量读取 API Key 的变量名 | +| `providers[].default_model` | 该厂商的默认模型 ID | +| `providers[].models[]` | 可用模型列表,每项包含 `id`、`name`、`context_window`、`max_output_tokens`、`capabilities` | + +### 运行时切换 + +- TUI 中输入 `/model` 命令可切换模型 +- 通过 API `POST /api/models` 切换 + +--- + +## 规则配置 + +规则以 Markdown 编写,在每次 LLM 调用时自动注入到 system prompt 中。 + +### 配置文件位置 + +| 级别 | 路径 | 说明 | +|------|------|------| +| 全局 | `~/.codingcode/rules.md` | 所有项目生效 | +| 项目 | `./AGENTS.md` | 仅当前项目生效 | + +两级规则合并注入,项目级规则追加在全局规则之后。 + +### 规则内容示例 + +```markdown +# 项目规则 + +## 编码规范 +- 使用函数式编程风格,避免 class +- 所有公共函数必须包含 JSDoc 注释 +- 变量命名使用 camelCase + +## 安全策略 +- 禁止在代码中硬编码密钥 +- 所有外部输入必须校验 + +## 项目约定 +- 测试文件放在 src/ 同级的 __tests__/ 目录 +- 提交信息格式:type(scope): description +``` + +--- + +## 项目级子配置 + +在 `.codingcode/config.yaml` 中可配置项目级子选项: + +```yaml +# .codingcode/config.yaml +subagent: + enabled: true # 是否允许子智能体 +``` diff --git a/docs/context.md b/docs/context.md new file mode 100644 index 0000000..c563509 --- /dev/null +++ b/docs/context.md @@ -0,0 +1,157 @@ +# 上下文管理与压缩 + +上下文管理是独立域,不耦合在 Agent 内部。本文档介绍两层压缩机制、触发条件、压缩策略和手动触发方式。 + +--- + +## 两层压缩机制 + +Coding Code 采用两层压缩策略,在不同阈值下自动触发: + +### 微压缩 (Micro Compact) + +轻量级压缩,对旧工具输出进行截断: + +| 配置项 | 值 | 说明 | +|--------|-----|------| +| 触发阈值 | `promptEstimate > contextWindow * 0.25` | prompt 估算超过上下文窗口 25% 时触发 | +| 压缩对象 | 旧 turn(`< currentTurnId - 1`)中的长工具输出 | 只压缩可压缩工具的输出 | +| 最小字符数 | 120 字符 | 短于 120 字符的输出不会被截断 | +| 压缩方式 | 生成 `CompactEvent` 写入 JSONL | 保留前后部分,中间省略 | + +**可压缩工具**:`read_file`、`execute_command`、`search_code`、`search_files`、`web_search`、`fetch_url`、`write_file`、`edit_file` + +### LLM 压缩 (LLM Compaction) + +深度压缩,调用 LLM 生成结构化摘要: + +| 配置项 | 值 | 说明 | +|--------|-----|------| +| 触发阈值 | `promptEstimate > modelMaxTokens * 0.9` | prompt 估算超过模型最大 token 90% 时触发 | +| 保留最近 turn | 1 | 保留最近 1 个 turn 不压缩 | +| 压缩方式 | 调用 LLM 生成摘要 | 输出 `...` 块 | +| 增量压缩 | 是 | 找到已有 SummaryEvent,只压缩 `lastSummarizedTurnId` 之后的事件 | +| 失败追踪 | 连续 3 次失败后停止 | 24 小时 TTL 后重置 | + +--- + +## 压缩输出格式 + +LLM 压缩的摘要包含 10 个固定小节: + +``` + +自由推理区域,分析对话内容和关键信息 + + + +### Primary Request +用户的核心请求 + +### Key Technical Concepts +涉及的关键技术概念 + +### Files and Code Sections +相关文件和代码段 + +### Errors and Fixes +遇到的错误和修复 + +### Problem Solving +问题解决过程 + +### Decision Rationale +决策理由 + +### All User Messages +所有用户消息摘要 + +### Pending Tasks +待处理任务 + +### Current Work +当前工作内容 + +### Optional Next Step +可选的下一步 + +``` + +--- + +## 事件类型 + +压缩操作在 JSONL 会话文件中记录为以下事件类型: + +```typescript +// 微压缩事件 +interface CompactEvent { + type: 'compact'; + uuid: string; + startTurnId: number; + endTurnId: number; + timestamp: string; +} + +// LLM 压缩摘要事件 +interface SummaryEvent { + type: 'summary'; + uuid: string; + replaces: string[]; // 被替换的事件 UUID 列表 + summaryText: string; // 摘要文本 + lastSummarizedTurnId: number; // 最后压缩到的 turn ID + timestamp: string; +} +``` + +--- + +## 配置 + +在 `codingcode.yaml` 中配置上下文压缩: + +```yaml +context: + compactionModel: "" # 压缩用模型,空字符串回退到主会话 LLM +``` + +### 硬编码常量 + +以下常量当前硬编码在 `context/service.ts` 中: + +| 常量 | 值 | 说明 | +|------|-----|------| +| `MICRO_COMPACT_THRESHOLD` | `0.25` | 微压缩触发比例 | +| `MICRO_COMPACT_MIN_CHARS` | `120` | 微压缩最小字符数 | +| `COMPACTION_THRESHOLD` | `0.9` | LLM 压缩触发比例 | +| `KEEP_RECENT_TURNS` | `1` | 保留最近 turn 数 | +| `REACTIVE_COMPACT_MAX_RETRIES` | `3` | 最大重试次数 | + +--- + +## 手动触发 + +通过 API 手动触发 LLM 压缩: + +| 路由 | 方法 | Body | 说明 | +|------|------|------|------| +| `/api/sessions/:id/compact` | POST | `{ cwd }` | 手动触发上下文压缩 | + +返回: + +```typescript +interface CompressResult { + didCompress: boolean; // 是否执行了压缩 + released: number; // 释放的 token 数 + promptEstimate: number; // 压缩后的 prompt 估算 +} +``` + +手动触发会强制执行 LLM 压缩,不受阈值限制。 + +也可通过 `AgentClient` SDK 调用: + +```typescript +const client = await createHttpClient('http://localhost:8080'); +await client.compact(); +``` diff --git a/docs/hooks.md b/docs/hooks.md new file mode 100644 index 0000000..ce8b8b2 --- /dev/null +++ b/docs/hooks.md @@ -0,0 +1,236 @@ +# 钩子系统 + +Coding Code 提供可插拔的钩子点,用户可以在关键节点注入自定义逻辑。本文档介绍所有钩子点、回调签名、注册 API 和用户钩子配置。 + +--- + +## 钩子点 + +### 工具执行 + +| 钩子点 | 触发时机 | 类型 | +|--------|---------|------| +| `tool.execute.before` | 工具执行前 | observer | +| `tool.execute.after` | 工具执行成功后 | observer | +| `tool.execute.error` | 工具执行失败后 | observer | +| `tool.execute.denied` | 工具被审批拒绝后 | observer | +| `tool.approval.pre` | 审批决策前 | decision | +| `tool.approval.post` | 审批决策后 | observer | + +### LLM 调用 + +| 钩子点 | 触发时机 | 类型 | +|--------|---------|------| +| `llm.request.before` | LLM 调用前 | observer | +| `llm.response.after` | LLM 响应成功后 | observer | +| `llm.response.error` | LLM 调用失败后 | observer | + +### 会话 + +| 钩子点 | 触发时机 | 类型 | +|--------|---------|------| +| `session.save.before` | 会话保存前 | observer | +| `session.save.after` | 会话保存后 | observer | + +### Agent 生命周期 + +| 钩子点 | 触发时机 | 类型 | +|--------|---------|------| +| `agent.turn.start` | Agent 轮次开始 | observer | +| `agent.step.before` | Agent 步骤执行前 | observer | +| `agent.turn.stop` | Agent 轮次停止 | observer | +| `agent.turn.end` | Agent 轮次结束 | observer | + +### 子智能体 + +| 钩子点 | 触发时机 | 类型 | +|--------|---------|------| +| `agent.subagent.spawn.before` | 子智能体创建前 | decision(可 deny) | +| `agent.subagent.spawn.after` | 子智能体创建后 | observer | +| `agent.subagent.complete` | 子智能体完成时 | observer | + +--- + +## 回调函数签名 + +### Observer 钩子 + +```typescript +type ObserverHandler = (payload: Record) => void | Promise; +``` + +Observer 钩子只观察事件,不返回决策。适用于日志、监控、通知等场景。 + +### Decision 钩子 + +```typescript +type DecisionHandler = (payload: Record) => HookDecision | null | Promise; + +interface HookDecision { + decision?: 'allow' | 'deny' | 'ask' | 'continue'; + reason?: string; + injection?: string; // 注入到 LLM 上下文的文本 + modifiedInput?: Record; // 修改工具调用参数 + modifiedOutput?: unknown; // 修改工具输出 +} +``` + +Decision 钩子可以返回决策,影响后续流程: + +- `allow`:直接放行,跳过后续审批层 +- `deny`:拒绝执行,附带 reason +- `ask`:要求用户确认 +- `continue`:继续到下一层 +- `null`:不干预,继续正常流程 + +--- + +## HookRegistry API + +`HookService` 是 Effect.Service,提供以下方法: + +### 注册钩子 + +```typescript +// 注册 observer 钩子,返回取消函数 +const unsubscribe = hookService.register('tool.execute.after', async (payload) => { + console.log(`工具 ${payload.toolName} 执行完成,耗时 ${payload.duration}ms`); +}); + +// 注册 decision 钩子,支持 priority +const unsub = hookService.registerDecision('tool.approval.pre', async (payload) => { + if (payload.toolName === 'execute_command' && payload.args.command.includes('rm')) { + return { decision: 'ask', reason: '删除命令需要确认' }; + } + return null; // 不干预 +}, { priority: 100 }); +``` + +### 生命周期管理 + +| 方法 | 说明 | +|------|------| +| `register(point, handler, opts?)` | 注册 observer 钩子,返回取消函数 | +| `registerDecision(point, handler, opts?)` | 注册 decision 钩子,支持 priority | +| `emit(point, payload)` | 触发 observer 钩子 | +| `emitDecision(point, payload)` | 触发 decision 钩子,返回第一个非 null 决策 | +| `reloadUserHooks(projectPath)` | 重新加载项目级用户钩子配置 | +| `attachSessionHooks(sessionId, hooks)` | 附加会话级钩子 | +| `disableHook(projectPath, name)` | 禁用指定钩子 | +| `enableHook(projectPath, name)` | 启用指定钩子 | +| `disposeSession(sessionId)` | 清理会话级钩子 | +| `disposeProject(projectPath)` | 清理项目级钩子 | + +### 钩子作用域 + +钩子按作用域分层,优先级从高到低: + +1. **session** — 会话级,通过 `attachSessionHooks` 附加 +2. **project** — 项目级,从 `.codingcode/hooks.yaml` 加载 +3. **global** — 全局级,从 `~/.codingcode/hooks.yaml` 加载 + +同一作用域内按 `priority` 排序,数值越大优先级越高。 + +--- + +## 用户钩子配置 + +通过 YAML 文件配置钩子,无需编写代码: + +### 配置文件位置 + +| 级别 | 路径 | +|------|------| +| 全局 | `~/.codingcode/hooks.yaml` | +| 项目 | `.codingcode/hooks.yaml` | + +### 配置格式 + +```yaml +hooks: + - name: log-llm-calls + description: 记录所有 LLM 调用 + point: llm.request.before + type: observer + command: node + args: ["./scripts/log-llm.js"] + priority: 10 + enabled: true + + - name: block-dangerous-commands + description: 阻止危险命令 + point: tool.approval.pre + type: decision + command: node + args: ["./scripts/check-command.js"] + env: + BLOCKED_COMMANDS: "rm,rmdir,format" + priority: 100 + enabled: true +``` + +### UserHookConfig 完整字段 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `name` | `string` | 是 | 钩子名称,用于 enable/disable | +| `description` | `string` | 否 | 钩子描述 | +| `point` | `HookPoint` | 是 | 钩子点名称 | +| `type` | `'observer' \| 'decision'` | 是 | 钩子类型 | +| `command` | `string` | 是 | 执行命令 | +| `args` | `string[]` | 否 | 命令参数 | +| `env` | `Record` | 否 | 环境变量 | +| `priority` | `number` | 否 | 优先级,默认 0 | +| `enabled` | `boolean` | 是 | 是否启用 | + +### 执行机制 + +用户钩子通过子进程执行: + +- payload 通过 stdin 传入 JSON +- decision 钩子从 stdout 读取 JSON 响应(需符合 `HookDecision` 格式) +- 超时时间 30 秒 +- 非零退出码视为错误,decision 钩子错误时返回 `continue` + +--- + +## 使用示例 + +### 记录 LLM 调用 token 估算 + +```typescript +hookService.register('llm.request.before', async (payload) => { + const messages = payload.messages as unknown[]; + const estimatedTokens = JSON.stringify(messages).length / 4; + console.log(`[Hook] 即将调用 LLM,预估 ${Math.round(estimatedTokens)} tokens`); +}); +``` + +### 拦截危险命令 + +```typescript +hookService.registerDecision('tool.approval.pre', async (payload) => { + if (payload.toolName === 'execute_command') { + const command = payload.args?.command as string; + if (command?.includes('rm -rf')) { + return { decision: 'deny', reason: '禁止递归强制删除' }; + } + } + return null; +}); +``` + +### 修改工具参数 + +```typescript +hookService.registerDecision('tool.approval.pre', async (payload) => { + if (payload.toolName === 'execute_command') { + // 强制所有命令在项目目录下执行 + return { + decision: 'continue', + modifiedInput: { ...payload.args, cwd: '/safe/directory' } + }; + } + return null; +}); +``` diff --git a/docs/mcp.md b/docs/mcp.md new file mode 100644 index 0000000..5ebd9e2 --- /dev/null +++ b/docs/mcp.md @@ -0,0 +1,116 @@ +# MCP 集成 + +Coding Code 集成 Model Context Protocol,允许通过外部服务扩展工具能力。本文档介绍 MCP 服务配置、传输类型和自动集成流程。 + +--- + +## 配置 MCP 服务 + +MCP 配置文件使用 YAML 格式,支持项目级和全局级两个层级: + +| 级别 | 路径 | 说明 | +|------|------|------| +| 全局 | `~/.codingcode/mcp.yaml` | 所有项目共享 | +| 项目 | `.codingcode/mcp.yaml` | 仅当前项目生效 | + +项目级配置与全局级合并时,同名服务器以项目级为准。环境变量支持 `${VAR_NAME}` 插值。 + +### 配置格式 + +```yaml +servers: + - name: custom-tools + command: node + args: ["./server.js"] + env: + API_KEY: ${MY_API_KEY} + concurrency: 3 + autoReconnect: true + + - name: remote-api + url: https://mcp.example.com/api + headers: + Authorization: "Bearer ${MCP_TOKEN}" + concurrency: 5 +``` + +### McpServerConfig 完整字段 + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `name` | `string` | 必填 | 服务器名称,用于工具命名空间化和白名单引用 | +| `command` | `string` | - | stdio 传输:可执行命令 | +| `args` | `string[]` | - | stdio 传输:命令参数 | +| `env` | `Record` | - | stdio 传输:环境变量,支持 `${VAR}` 插值 | +| `url` | `string` | - | HTTP 传输:服务器 URL | +| `headers` | `Record` | - | HTTP 传输:请求头 | +| `concurrency` | `number` | `3` | 最大并发工具调用数 | +| `autoReconnect` | `boolean` | `true` | 断线自动重连 | + +--- + +## 传输类型 + +### stdio 传输 + +通过子进程与 MCP 服务器通信,需要指定 `command` 和 `args`: + +```yaml +servers: + - name: filesystem + command: npx + args: ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"] +``` + +### HTTP 传输 (StreamableHTTP) + +通过 HTTP 与远程 MCP 服务器通信,需要指定 `url`: + +```yaml +servers: + - name: remote-tools + url: https://mcp.example.com/api + headers: + Authorization: "Bearer ${MCP_TOKEN}" +``` + +传输类型自动判断:有 `command` 则为 stdio,否则为 HTTP。 + +--- + +## 自动集成 + +启动时,Coding Code 会: + +1. 合并全局 + 项目配置(项目覆盖同名全局) +2. 连接所有配置的 MCP 服务 +3. 调用各服务的 `listTools()` 获取工具列表 +4. 每个工具通过 `mcpToolToDefinition()` 转换为 `ToolDefinition`,名称空间化为 `serverName:toolName` +5. Agent 可直接调用,无需额外配置 + +### 连接生命周期 + +MCP 连接使用 lease 机制管理会话级生命周期: + +- 每个会话通过 `addLease` 建立与 MCP 服务器的关联 +- 会话结束时通过 `removeLease` 释放关联 +- 当某个服务器没有任何活跃 lease 时,自动断开连接 +- `autoReconnect: true` 时,断线后自动重连 + +--- + +## MCP 工具白名单 + +在子智能体 profile 中通过 `mcpServers` 字段指定允许的 MCP 服务: + +```yaml +# .codingcode/agents/my-agent.md +--- +name: my-agent +description: 使用特定 MCP 服务的 Agent +tools: ["read_file", "search_code"] +mcpServers: ["filesystem"] # 只允许使用 filesystem 服务的工具 +--- +``` + +在 `ToolVisibilityPolicy` 中通过 `allowedMcpServers` 控制可见的 MCP 服务。 diff --git a/docs/memory.md b/docs/memory.md new file mode 100644 index 0000000..3beb16b --- /dev/null +++ b/docs/memory.md @@ -0,0 +1,123 @@ +# 长期记忆系统 + +Coding Code 支持跨会话的长期记忆,自动从对话中提取和存储关键信息。本文档介绍记忆类型、内容分类、自动提取机制和手动编辑方法。 + +--- + +## 内存类型 + +| 类型 | 位置 | 作用 | +|---|---|---| +| **用户级** | `~/.codingcode/memory.md` | 跨所有项目的个人知识库 | +| **项目级** | `./.codingcode/memory.md` | 特定项目的上下文 | + +路径可在 `codingcode.yaml` 的 `memory.projectFile` 和 `memory.userFile` 中自定义。 + +--- + +## 记忆内容 + +内置三种记忆类型: + +| 类型 | 提取来源 | 内容 | +|------|---------|------| +| `user` | `[user]` 标签的消息 | 用户角色、技能栈、工作偏好及对 Agent 的纠正 | +| `project` | `[user]` + `[assistant]` 消息 | 架构决策、技术选型、部署信息 | +| `reference` | `[user]` + `[tool:*]` 消息 | 外部资源、文档、Dashboard 链接 | + +可通过 `memory.extraTypes` 添加自定义记忆类型,通过 `memory.disabledTypes` 禁用内置类型。 + +--- + +## 自动提取 + +Agent 在每次会话后自动执行记忆提取: + +1. 构建 system prompt,包含各记忆类型的提取指引 +2. 发送已有记忆 + 会话记录给 LLM +3. LLM 输出 `...` 块 +4. 提取块内容,返回新记忆文本(null 表示无新内容) +5. 矛盾时新信息替换旧条目,同一会话以最新为准 + +提取使用的模型可通过 `memory.model` 配置,留空则回退到主会话模型。 + +--- + +## 记忆文件格式 + +记忆文件使用 Markdown 格式,自动提取内容包裹在标记块中: + +```markdown + +### user +- 偏好使用函数式编程风格 +- 常用技术栈:React + TypeScript + +### project +- 采用 monorepo 架构,使用 pnpm workspaces +- 入口文件:packages/codingcode/src/cli.ts + +### reference +- [API 文档](https://example.com/api) + + +手动添加的内容可以写在标记块之外,不会被自动提取覆盖。 +``` + +### 标记块机制 + +- `replaceAutoBlock()`:原子替换 `` 和 `` 之间的内容 +- `stripMarkersForPrompt()`:去掉标记后注入系统提示 +- `enforceMaxBytes()`:按 `### ` 小节逐个裁剪到字节上限(默认 16384 字节) +- `mergeAutoBlocks()`:以 `### ` 小节名为 key 合并,incoming 覆盖 base + +--- + +## 配置 + +在 `codingcode.yaml` 中配置记忆系统: + +```yaml +memory: + enabled: true # 启用长期记忆(默认 false) + model: "" # 记忆提取模型,空字符串回退到主模型 + projectFile: ".codingcode/memory.md" # 项目记忆文件路径 + userFile: "~/.codingcode/memory.md" # 用户记忆文件路径 + maxBytes: 16384 # 记忆文件最大字节数 + promptMaxBytes: 8192 # 注入提示的最大字节数 + extraTypes: [] # 自定义记忆类型 + disabledTypes: [] # 禁用的记忆类型名 +``` + +### 自定义记忆类型 + +```yaml +memory: + enabled: true + extraTypes: + - name: feedback + description: 工作流程中的教训和已验证的方法 + enabled: true + - name: decision + description: 重要的架构和设计决策 + enabled: true + disabledTypes: + - reference # 禁用内置的 reference 类型 +``` + +--- + +## 手动编辑 + +记忆文件采用 Markdown 格式,支持手动编辑。手动内容可写在 `` 标记之后,不会被自动提取覆盖: + +```markdown + +### user +- 偏好使用函数式编程风格 + + +### 手动备注 +- 项目部署流程:npm run build -> scp dist/ -> pm2 restart +- 数据库连接字符串在 Vault 中 +``` diff --git a/docs/skills.md b/docs/skills.md new file mode 100644 index 0000000..b71d0c9 --- /dev/null +++ b/docs/skills.md @@ -0,0 +1,116 @@ +# 技能系统 + +Coding Code 支持可插拔的 Markdown 技能包,扩展 Agent 在特定场景下的能力。本文档介绍技能的发现、加载和配置机制。 + +--- + +## 什么是技能 + +技能是一组以 Markdown 编写的指令和资源,在 Agent 调用前注入到 system prompt 中。每个技能包含: + +- **instruction**:SKILL.md 的 Markdown 正文,作为技能指令注入 +- **references**:附带的参考文件(代码片段、文档等) +- **scripts**:附带的脚本文件 +- **assets**:附带的二进制资源 + +--- + +## 技能发现 + +技能从两个位置自动发现: + +| 级别 | 路径 | 说明 | +|------|------|------| +| 全局 | `~/.codingcode/skills/` | 所有项目共享 | +| 项目 | `.codingcode/skills/` | 仅当前项目生效 | + +每个技能是一个目录,目录下必须包含 `SKILL.md` 文件: + +``` +.codingcode/skills/ +├── code-review/ +│ ├── SKILL.md # 技能指令(必需) +│ ├── review-checklist.md # 参考文件 +│ └── run-review.sh # 脚本文件 +└── api-design/ + ├── SKILL.md + └── openapi-template.yaml +``` + +--- + +## SKILL.md 格式 + +SKILL.md 是纯 Markdown 文件,正文部分作为技能指令注入 system prompt: + +```markdown +# Code Review Skill + +You are now performing a code review. Follow these steps: + +1. Read the diff carefully +2. Check for security issues +3. Verify error handling +4. Suggest improvements + +## Review Checklist +- [ ] No hardcoded secrets +- [ ] All inputs validated +- [ ] Error messages are helpful +``` + +--- + +## 技能类型 + +```typescript +interface Skill { + readonly name: string; // 技能名称 + readonly description: string; // 技能描述 + readonly instruction: string; // SKILL.md 的 Markdown body + readonly references: ReadonlyArray<{ // 参考文件 + path: string; + content: string; + }>; + readonly scripts: ReadonlyArray<{ // 脚本文件 + path: string; + content: string; + }>; + readonly assets: ReadonlyArray<{ // 二进制资源 + path: string; + mimeType: string; + size: number; + }>; + readonly metadata: Record; // 自定义元数据 +} +``` + +--- + +## 技能管理 API + +通过 `AgentClient` SDK 管理技能: + +```typescript +const client = await createHttpClient('http://localhost:8080'); + +// 列出所有技能 +const skills = await client.listSkills(); +// 返回:Array<{ name: string, description: string, enabled: boolean }> + +// 启用/禁用技能 +await client.toggleSkill({ name: 'code-review', enabled: true }); +``` + +也可通过 HTTP API: + +| 路由 | 方法 | 说明 | +|------|------|------| +| `/api/settings/skills` | GET | 列出所有技能 | +| `/api/settings/skills/toggle` | POST | 启用/禁用技能 | + +--- + +## 配置 + +技能的启用/禁用状态持久化在项目配置中。禁用的技能不会被注入 system prompt,但仍保留在技能目录中,可随时重新启用。 diff --git a/docs/subagent.md b/docs/subagent.md new file mode 100644 index 0000000..b28d38b --- /dev/null +++ b/docs/subagent.md @@ -0,0 +1,167 @@ +# 子智能体系统 + +每个子 Agent 是独立的 ReAct 引擎实例,拥有受限的工具集和独立的上下文。本文档介绍子 Agent 的特性、配置格式、内置 profile 和执行流程。 + +--- + +## 特性 + +- **独立执行**:子 Agent 在独立的 Effect Context 中运行 +- **受限工具集**:每个子 Agent profile 定义自己的工具白名单 +- **独立上下文**:不共享主 Agent 的消息历史 +- **独立模型**:可指定与主 Agent 不同的模型 +- **独立 MCP**:可连接指定的 MCP 服务器 +- **独立钩子**:可附加专属的钩子配置 +- **自由定义**:用户可配置任意数量的子 Agent profile + +--- + +## AgentProfile 类型定义 + +```typescript +interface AgentProfile { + name: string; // profile 名称,用于 dispatch_agent 引用 + description: string; // 功能描述,LLM 据此决定是否委派 + systemPrompt?: string; // 自定义系统提示词 + tools?: string[]; // 允许使用的工具白名单 + mcpServers?: string[]; // 允许连接的 MCP 服务白名单 + readonly?: boolean; // 是否只读模式 + maxSteps?: number; // 最大执行步数 + model?: string; // 使用的模型 ID + hooks?: UserHookConfig[]; // 专属钩子配置 + disabled?: boolean; // 是否禁用 +} +``` + +--- + +## 配置格式 + +子 Agent 使用 Markdown + frontmatter 格式配置,存放在 `.codingcode/agents/` 目录下: + +| 级别 | 路径 | 说明 | +|------|------|------| +| 全局 | `~/.codingcode/agents/*.md` | 所有项目共享 | +| 项目 | `.codingcode/agents/*.md` | 仅当前项目生效 | + +项目级同名 profile 覆盖全局级。 + +### 示例 + +```markdown +--- +name: code-searcher +description: 专门搜索代码库的子 Agent,擅长定位函数定义和引用 +tools: ["read_file", "search_code", "search_files"] +readonly: true +maxSteps: 100 +model: deepseek-chat +disabled: false +--- +You are a code search specialist. Your job is to find specific code patterns, function definitions, and usages in the codebase. Always provide the file path and line numbers in your results. +``` + +### 字段说明 + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `name` | `string` | 必填 | profile 名称 | +| `description` | `string` | 必填 | 功能描述,LLM 据此决定是否委派任务 | +| `systemPrompt` | `string` | frontmatter 之后的正文 | 系统提示词 | +| `tools` | `string[]` | 所有内置工具 | 允许使用的工具白名单 | +| `mcpServers` | `string[]` | 无 | 允许连接的 MCP 服务名列表 | +| `readonly` | `boolean` | `false` | 只读模式下只允许只读工具 | +| `maxSteps` | `number` | 继承主 Agent | 最大执行步数 | +| `model` | `string` | 继承主 Agent | 使用的模型 ID | +| `hooks` | `UserHookConfig[]` | 无 | 专属钩子配置 | +| `disabled` | `boolean` | `false` | 禁用此 profile | + +--- + +## 内置 Profile + +系统内置两个子 Agent profile: + +### explore + +只读代码探索 Agent,用于快速浏览和理解代码库: + +```yaml +name: explore +description: 只读代码探索 +tools: [read_file, search_files, search_code, fetch_url, tool_search] +readonly: true +maxSteps: 180 +``` + +### plan + +只读代码研究 + 规划 Agent,可执行命令来验证环境: + +```yaml +name: plan +description: 只读代码研究和规划 +tools: [read_file, search_files, search_code, execute_command, fetch_url, tool_search] +readonly: true +maxSteps: 180 +``` + +--- + +## 执行流程 + +主 Agent 通过 `dispatch_agent` 工具委派任务,完整执行流程如下: + +1. **检查开关**:验证全局子智能体开关是否启用 (`resolveSubagentEnabled`) +2. **解析 profile**:查找对应的 AgentProfile (`runtime.resolveSubagentProfile`) +3. **检查禁用**:验证该 profile 是否被禁用 (`resolveAgentDisabled`) +4. **创建 LLM**:如果 profile 指定了 model,创建对应的 LLM 客户端 +5. **钩子决策**:触发 `agent.subagent.spawn.before` 决策钩子(可 deny 阻止) +6. **创建子会话**:嵌套在父会话下,设置 `parentSessionId` +7. **Fork 审批**:如果非 readonly,fork 审批服务 +8. **附加钩子**:附加 profile 中定义的 hooks +9. **连接 MCP**:连接 profile 中指定的 MCP 服务器(会话级 lease) +10. **构建工具策略**:根据 profile.tools 和 ToolVisibilityPolicy 过滤可用工具 +11. **执行**:调用 `runner.runStream()` 执行子智能体 +12. **钩子通知**:触发 `agent.subagent.spawn.after` 钩子 +13. **收集输出**:提取事件流中的最终输出 +14. **清理**:断开 MCP 连接,移除 hooks +15. **完成钩子**:触发 `agent.subagent.complete` 钩子 + +--- + +## 使用示例 + +### 通过 dispatch_agent 工具委派 + +```typescript +// Agent 自动调用 +await agent.executeTool('dispatch_agent', { + agent: 'explore', + prompt: 'Find all usages of getUserById function' +}); +``` + +### 自定义子 Agent + +创建 `.codingcode/agents/security-auditor.md`: + +```markdown +--- +name: security-auditor +description: 安全审计 Agent,检查代码中的安全漏洞 +tools: ["read_file", "search_code", "search_files"] +readonly: true +maxSteps: 50 +model: deepseek-chat +--- +You are a security audit specialist. Review code for common vulnerabilities: +- SQL injection +- XSS +- CSRF +- Path traversal +- Command injection +Report findings with severity level and remediation suggestions. +``` + +然后在对话中请求安全审计时,主 Agent 会自动委派给 `security-auditor`。 diff --git a/docs/tools.md b/docs/tools.md new file mode 100644 index 0000000..7ddd82f --- /dev/null +++ b/docs/tools.md @@ -0,0 +1,145 @@ +# 工具系统 + +Coding Code 的工具系统是 Agent 与外部世界交互的核心机制。本文档介绍内置工具、加载机制、自定义工具开发和沙箱隔离。 + +--- + +## 内置工具 + +### 文件操作 (fs) + +| 工具 | 功能 | 关键参数 | +|---|---|---| +| `read_file` | 读取文件内容 | `path: string`(文件路径),`offset: number`(起始行,默认 1),`limit: number`(行数,默认 200,最大 500) | +| `write_file` | 写入/创建文件 | `path: string`,`content: string` | +| `edit_file` | 编辑文件中的特定代码段 | `path: string`,`old_string: string`(被替换的文本,至少 1 字符),`new_string: string`(替换后的文本) | +| `search_code` | 正则搜索项目代码 | `pattern: string`(正则表达式),`glob: string`(文件匹配模式,默认 `**/*`),`max_results: number`(默认 30,最大 100) | +| `search_files` | 模式匹配查找文件 | `pattern: string`(glob 模式),`path: string`(搜索目录,默认 `.`),`max_results: number`(默认 50,最大 500) | + +### 命令执行 + +| 工具 | 功能 | 关键参数 | +|---|---|---| +| `execute_command` | 执行 shell 命令 | `command: string`,`cwd: string`(可选,工作目录),`timeout_ms: number`(超时毫秒,默认 30000) | + +### 网络 + +| 工具 | 功能 | 关键参数 | +|---|---|---| +| `fetch_url` | HTTP GET 请求 | `url: string`(合法 URL),`max_length: number`(最大响应长度,默认 100000,最大 500000) | +| `web_search` | Web 搜索 | `query: string`,`max_results: number`(默认 8,最大 20) | + +### 代理状态 + +| 工具 | 功能 | 关键参数 | +|---|---|---| +| `todo_write` | 修改代理的任务列表 | `plan: Array<{ step: string, status: 'pending' \| 'in_progress' \| 'completed' }>`(最大条目数有限制) | +| `tool_search` | 发现和加载可用工具 | `query: string`(搜索关键词,至少 1 字符) | + +### 子智能体 + +| 工具 | 功能 | 关键参数 | +|---|---|---| +| `dispatch_agent` | 将任务委派给子智能体 | `agent: string`(子智能体名称),`prompt: string`(任务描述,至少 1 字符) | + +--- + +## 工具加载机制 + +工具按加载时机分为三类: + +- **Core 工具**:始终可用,在启动时注册。包括上述所有内置工具。 +- **Deferred 工具**:按需加载,通过 `tool_search` 发现后动态加载。这类工具标记了 `deferred: true`,不会在初始工具列表中暴露给 LLM,只有当 LLM 主动调用 `tool_search` 查询后才会加载。 +- **MCP 工具**:从 MCP 服务自动导入和注册。名称空间化为 `serverName:toolName` 格式,避免不同服务间的工具名冲突。 + +工具解析流程:`createSessionToolResolver()` 合并 builtin + project MCP + tool_search + dispatch_agent,根据 `AgentProfile.tools` 和 `ToolVisibilityPolicy` 过滤后提供给 Agent。 + +--- + +## 自定义工具 + +工具通过 `ToolService` 注册,每个工具实现 `ToolDefinition` 接口: + +```typescript +interface ToolDefinition { + name: string; + description: string; + shortDescription?: string; // 简短描述,用于工具列表展示 + deferred?: boolean; // 是否延迟加载 + parameters: z.ZodTypeAny; // Zod schema 定义参数 + jsonSchema?: Record; // 可选的 JSON Schema 覆盖 + execute: (args: unknown, ctx?: ToolExecCtx) => Effect.Effect; +} + +interface ToolExecCtx { + signal?: AbortSignal; // 取消信号 + sessionId?: string; // 当前会话 ID + turnId?: number; // 当前轮次 + projectPath?: string; // 项目路径 +} +``` + +在 `cli.ts` 中向 `ToolService` 注册新工具,Agent 会自动将其暴露给 LLM。 + +### 工具可见性策略 + +通过 `ToolVisibilityPolicy` 控制工具的可见性: + +```typescript +interface ToolVisibilityPolicy { + allowedTools?: Set; // 允许的工具白名单 + allowedMcpServers?: Set; // 允许的 MCP 服务白名单 + allowToolSearch?: boolean; // 是否允许 tool_search + allowDeferredTools?: boolean; // 是否允许延迟工具 +} +``` + +--- + +## 沙箱隔离 + +所有工具执行经过两层安全保护: + +### 审批流水线(始终生效) + +六层决策链,按顺序执行,任一层返回 deny/allow 即终止: + +| 层级 | 名称 | 逻辑 | +|------|------|------| +| 1 | **RuleEngine** | 规则引擎匹配,支持 glob 模式匹配工具名和参数,按优先级排序 | +| 2 | **ReadonlyWhitelist** | 只读工具自动放行(read_file, search_code, search_files, fetch_url, web_search, dispatch_agent, todo_write) | +| 3 | **PermissionMode** | 权限模式判断:`plan`(只允许只读)、`bypass`(全部放行)、`acceptEdits`(非破坏性工具放行)、`default`(继续下一层) | +| 4 | **HookPreToolUse** | 钩子决策,可返回 allow/deny/ask/continue,支持 `modifiedInput` 修改参数 | +| 5 | **UserConfirmation** | 异步用户确认,支持 allow/deny/always/never 四种响应,always/never 会持久化为规则 | +| 6 | **AuditLog** | 每一层决策后记录审计日志,通过 `tool.approval.post` 钩子发出 | + +### 预设安全规则 + +系统内置 9 条默认规则(不可删除): + +| 规则 | 动作 | 说明 | +|------|------|------| +| `rm -rf /` | deny | 禁止递归删除根目录 | +| `sudo` | deny | 禁止提权执行 | +| `curl \| sh` | deny | 禁止管道执行远程脚本 | +| `chmod u+s` | deny | 禁止设置 SUID 位 | +| `shutdown` | deny | 禁止系统关机 | +| `/etc/shadow` | deny | 禁止读取影子密码文件 | +| `/etc/passwd` | deny | 禁止读取系统密码文件 | +| `.ssh` | ask | 访问 SSH 目录需确认 | +| `.env` | ask | 访问环境变量文件需确认 | + +### 权限模式 + +```typescript +type PermissionMode = 'default' | 'acceptEdits' | 'plan' | 'bypass'; +``` + +- `default`:逐层审批,危险操作需用户确认 +- `acceptEdits`:非破坏性工具自动放行,减少确认弹窗 +- `plan`:只允许只读工具,适合纯分析场景 +- `bypass`:全部放行,跳过所有审批(慎用) + +### OS 级沙箱(预留) + +`packages/codingcode/src/sandbox/` 目前是 stub 实现(`SandboxService` 为空类),尚未集成实际的沙箱运行时。审批流水线已提供基本安全保障,OS 级沙箱将在未来版本中实现。