mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 21:05:51 +00:00
Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
956e98a445 | ||
|
|
cee62bc654 | ||
|
|
5fc7c8e13d | ||
|
|
300faa18d0 | ||
|
|
96ec96c720 | ||
|
|
13a0bfc479 | ||
|
|
84f0271813 | ||
|
|
ed4bdb9338 | ||
|
|
e4ce08fe39 | ||
|
|
92f8a92fbb | ||
|
|
a67e2d0e97 | ||
|
|
8c629858ab | ||
|
|
494eab7204 | ||
|
|
b83c3008d0 | ||
|
|
66d2671c98 | ||
|
|
c7bc8c8636 | ||
|
|
673ccd1800 | ||
|
|
d1ab38c089 | ||
|
|
f9d011164a | ||
|
|
481e2a58a9 | ||
|
|
c5edee431f | ||
|
|
a57ca08566 | ||
|
|
6536757428 | ||
|
|
a0dc4540ca | ||
|
|
7e4df5c3e9 | ||
|
|
4d939e5722 | ||
|
|
2e9aaf4993 | ||
|
|
34154ee3f5 | ||
|
|
29cc74a170 | ||
|
|
d2b66d9d2c | ||
|
|
d70e7f7f05 | ||
|
|
72a2093cd6 | ||
|
|
b5c299f5d2 | ||
|
|
ac42ce2d67 | ||
|
|
c659912517 | ||
|
|
a14b7f352b | ||
|
|
c5ab83a3fc | ||
|
|
03b7f9b453 | ||
|
|
bddd146f25 | ||
|
|
c8d08d235b | ||
|
|
a02dc0bded | ||
|
|
3cb1e50b25 | ||
|
|
cfab161e28 | ||
|
|
90027279e6 | ||
|
|
3470783ced | ||
|
|
8169b96250 | ||
|
|
fe08cacf8d | ||
|
|
5a4c820e1d | ||
|
|
1a4e9702c2 | ||
|
|
2273a0bcfe | ||
|
|
b80483c23e | ||
|
|
8442aaadd2 | ||
|
|
dad3ad2b8d | ||
|
|
b5b81dfe49 | ||
|
|
ecbd5a93e4 | ||
|
|
be80da4ce0 | ||
|
|
fce40fed1f | ||
|
|
a7e03a5b30 | ||
|
|
05cabbbd73 | ||
|
|
d4b30d32c3 | ||
|
|
e0484e2817 | ||
|
|
2fb1c9dcd8 | ||
|
|
bbb8b613a9 | ||
|
|
c63b875ae3 | ||
|
|
9b8503d13d | ||
|
|
3cf94fbda0 | ||
|
|
9a3081dff6 | ||
|
|
bd6448ecda | ||
|
|
1071270ce3 | ||
|
|
711440474c | ||
|
|
8399d9ed20 | ||
|
|
513ccc3003 | ||
|
|
e770f1ef9d | ||
|
|
227083d31f | ||
|
|
14c46df881 | ||
|
|
e0e4ee41c2 | ||
|
|
e9861415c0 | ||
|
|
423f114db6 | ||
|
|
c8a502f81f | ||
|
|
09fc515edb | ||
|
|
2fea429dc6 | ||
|
|
6a9da9d546 | ||
|
|
d27c6cbc64 | ||
|
|
ffd1c366eb | ||
|
|
5beeebad59 | ||
|
|
c676ac4693 | ||
|
|
eeb0f2776e | ||
|
|
6a70056910 | ||
|
|
7088fe3c8b | ||
|
|
b060eabda9 | ||
|
|
9da7345f8e | ||
|
|
8137b66a46 | ||
|
|
b681139b63 | ||
|
|
0b1e678fb7 | ||
|
|
81073135e2 | ||
|
|
ff03fe7fcb | ||
|
|
c82f59943c | ||
|
|
e70319e8f5 | ||
|
|
609e91143f | ||
|
|
637531f81f | ||
|
|
875510e1eb | ||
|
|
34bbc1d403 | ||
|
|
a14d3dc8f0 | ||
|
|
ab3d8ef87e | ||
|
|
dfce6d02f9 | ||
|
|
01cf45f4ac | ||
|
|
e6affc7053 | ||
|
|
bb07836231 | ||
|
|
87230cf3bf | ||
|
|
8c619a215c | ||
|
|
2b0d31aaca | ||
|
|
562e9daadd | ||
|
|
8b2532a9c1 |
@@ -1,22 +0,0 @@
|
||||
#!/bin/sh
|
||||
# pre-commit hook: 对暂存的文件运行 Biome 检查
|
||||
# 仅检查 src/ 下的 .ts/.tsx/.js/.jsx 文件
|
||||
|
||||
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '^src/.*\.(ts|tsx|js|jsx)$')
|
||||
|
||||
if [ -z "$STAGED_FILES" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Running Biome lint on staged files..."
|
||||
|
||||
# 使用 biome lint 对暂存文件进行检查(仅 lint,不格式化,不自动修复)
|
||||
echo "$STAGED_FILES" | xargs bunx biome lint --no-errors-on-unmatched
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo ""
|
||||
echo "Biome lint failed. Fix errors or use --no-verify to bypass."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
19
.github/workflows/ci.yml
vendored
19
.github/workflows/ci.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: macos-latest
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -20,8 +20,19 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: bun test
|
||||
- name: Type check
|
||||
run: bunx tsc --noEmit
|
||||
|
||||
- name: Test with Coverage
|
||||
run: |
|
||||
set -o pipefail
|
||||
bun test --coverage --coverage-reporter=lcov 2>&1 | grep -vE '^\s*(\(pass\)|\(skip\))' | sed '/^.*\/__tests__\/.*:$/d' | cat -s
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
file: ./coverage/lcov.info
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- name: Build
|
||||
run: bun run build
|
||||
run: bun run build:vite
|
||||
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -12,9 +12,9 @@ src/utils/vendor/
|
||||
|
||||
# AI tool runtime directories
|
||||
.agents/
|
||||
.codex/
|
||||
.claude/
|
||||
.omx/
|
||||
|
||||
.docs/task/
|
||||
# Binary / screenshot files (root only)
|
||||
/*.png
|
||||
*.bmp
|
||||
@@ -29,3 +29,12 @@ __pycache__/
|
||||
logs
|
||||
|
||||
data
|
||||
.omc
|
||||
.codex/*
|
||||
!.codex/agents/
|
||||
!.codex/agents/**
|
||||
!.codex/skills/
|
||||
!.codex/skills/**
|
||||
.codex/skills/.system/**
|
||||
!.codex/prompts/
|
||||
!.codex/prompts/**
|
||||
|
||||
78
.impeccable.md
Normal file
78
.impeccable.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Impeccable Design Context
|
||||
|
||||
## Users
|
||||
|
||||
**Primary**: Technical teams and enterprises using AI-assisted coding in production workflows.
|
||||
- DevOps engineers managing remote agents via RCS dashboard
|
||||
- Development teams collaborating through shared sessions
|
||||
- Individual developers using terminal CLI daily
|
||||
|
||||
**Context**: Used during focused work sessions — debugging, code review, agent orchestration. Users are in "get things done" mode, not browsing. They value efficiency but also appreciate warmth and personality.
|
||||
|
||||
**Job to be done**: Make advanced AI coding tools accessible and controllable, especially features that normally require enterprise accounts or Anthropic OAuth.
|
||||
|
||||
## Brand Personality
|
||||
|
||||
**3 words**: Warm, Considered, Human
|
||||
|
||||
**Voice**: Like a knowledgeable colleague who's genuinely enthusiastic about the craft — not a corporate product manager. Community-first, open, slightly playful. Chinese developer community culture (贴吧/discord 温暖氛围).
|
||||
|
||||
**Emotional goals**: Confidence (this tool is solid), Warmth (this community is welcoming), Delight (small moments of personality make the difference).
|
||||
|
||||
**References**:
|
||||
- **Anthropic's own design language** — their clean, considered aesthetic with warm undertones. The terra cotta/burnt orange as a human accent. Lots of breathing room. Typography-forward.
|
||||
- **NOT**: Generic AI product (no ChatGPT blue, no gradient text, no "AI slop"). NOT corporate SaaS (no Salesforce-blue dashboards, no enterprise sterility).
|
||||
|
||||
**Anti-references**: Corporate enterprise dashboards, generic AI product pages, anything that looks like it was "designed by committee."
|
||||
|
||||
## Aesthetic Direction
|
||||
|
||||
**Theme**: Light + Dark dual mode (user/system preference switch)
|
||||
|
||||
**Tone**: Anthropomorphic warmth meets terminal precision. The brand orange (Claude's terra cotta) is the thread that ties everything together — it's the human element in a technical world.
|
||||
|
||||
**Typography**: Clean, considered, with good hierarchy. Terminal-native for CLI; modern web fonts for Web UI (RCS dashboard, docs). Favor readability and personality.
|
||||
|
||||
**Color**:
|
||||
- Primary: Claude orange family (`#D77757` / terra cotta)
|
||||
- Accent: Warm neutrals tinted toward orange
|
||||
- Semantic: Success/Error/Warning following Anthropic's established palette
|
||||
- Dark mode: Warm dark surfaces (not cold blue-black)
|
||||
|
||||
**Differentiation**: The CCB brand sits at the intersection of "serious tool" and "community project." It should feel like Anthropic's design principles applied to an open-source context — less corporate polish, more human craft. The mascot "Clawd" and the playful "踩踩背" naming hint at personality that the design should honor.
|
||||
|
||||
**Scope**: All Web UI — RCS control panel, documentation site, landing pages.
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. **Considered over clever** — Every design choice should feel intentional, not trendy. If it doesn't serve the user, it doesn't ship.
|
||||
2. **Warmth through subtlety** — Orange tints on neutrals, breathing room in layouts, personality in copy. Not giant emoji or aggressive color.
|
||||
3. **Density with clarity** — Technical users need information density, but not chaos. Every pixel earns its place.
|
||||
4. **Community voice** — The design should feel like it was made by people who use it, not by a distant design team. Slightly rough edges are fine if they're honest.
|
||||
5. **Anthropic's shadow** — When in doubt, follow Anthropic's design instincts — the clean layouts, the generous spacing, the warm color temperature. Then add the community touch.
|
||||
|
||||
## Existing Design Assets
|
||||
|
||||
### Brand Colors (from theme system)
|
||||
- Claude Orange: `rgb(215,119,87)` / `#D77757`
|
||||
- Claude Blue: `rgb(87,105,247)` / `#5769F7`
|
||||
- Permission Blue: `rgb(87,105,247)`
|
||||
- Auto Accept Violet: `rgb(135,0,255)`
|
||||
- Plan Mode Teal: `rgb(0,102,102)`
|
||||
- Success: `rgb(78,186,101)`
|
||||
- Error: `rgb(255,107,128)`
|
||||
- Warning: `rgb(255,193,7)`
|
||||
|
||||
### Logo
|
||||
- CCB text + orange play button icon
|
||||
- Dark/Light SVG variants in `docs/logo/`
|
||||
- Favicon: Orange circle `#D97706` with white play triangle
|
||||
|
||||
### Mascot
|
||||
- "Clawd" — terminal-art character with multiple poses
|
||||
- Theme-aware coloring
|
||||
|
||||
### Theme System
|
||||
- 7 variants: dark, light, dark-ansi, light-ansi, dark-daltonized, light-daltonized, auto
|
||||
- 89+ semantic color tokens
|
||||
- Full documentation in `packages/@ant/ink/docs/04-theme-system.md`
|
||||
2
.mintignore
Normal file
2
.mintignore
Normal file
@@ -0,0 +1,2 @@
|
||||
src/
|
||||
packages/
|
||||
269
CLAUDE.md
269
CLAUDE.md
@@ -4,7 +4,22 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a **reverse-engineered / decompiled** version of Anthropic's official Claude Code CLI tool. The goal is to restore core functionality while trimming secondary capabilities. Many modules are stubbed or feature-flagged off. The codebase has ~1341 tsc errors from decompilation (mostly `unknown`/`never`/`{}` types) — these do **not** block Bun runtime execution.
|
||||
This is a **reverse-engineered / decompiled** version of Anthropic's official Claude Code CLI tool. The goal is to restore core functionality while trimming secondary capabilities. Many modules are stubbed or feature-flagged off. TypeScript strict mode is enforced(见 Working with This Codebase 段的 tsc 要求)。
|
||||
|
||||
## Git Commit Message Convention
|
||||
|
||||
使用 **Conventional Commits** 规范:
|
||||
|
||||
```
|
||||
<type>: <描述>
|
||||
```
|
||||
|
||||
常见 type:`feat`、`fix`、`docs`、`chore`、`refactor`
|
||||
|
||||
示例:
|
||||
- `feat: 添加模型 1M 上下文切换`
|
||||
- `fix: 修复初次登陆的校验问题`
|
||||
- `chore: remove prefetchOfficialMcpUrls call on startup`
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -21,11 +36,14 @@ bun run dev:inspect
|
||||
# Pipe mode
|
||||
echo "say hello" | bun run src/entrypoints/cli.tsx -p
|
||||
|
||||
# Build (code splitting, outputs dist/cli.js + ~450 chunk files)
|
||||
# Build (code splitting, outputs dist/cli.js + chunk files)
|
||||
bun run build
|
||||
|
||||
# Build with Vite (alternative build pipeline)
|
||||
bun run build:vite
|
||||
|
||||
# Test
|
||||
bun test # run all tests
|
||||
bun test # run all tests (3175 tests / 207 files / 0 fail)
|
||||
bun test src/utils/__tests__/hash.test.ts # run single file
|
||||
bun test --coverage # with coverage report
|
||||
|
||||
@@ -40,6 +58,14 @@ bun run health
|
||||
# Check unused exports
|
||||
bun run check:unused
|
||||
|
||||
# Full check (typecheck + lint + test) — run after completing any task
|
||||
bun run test:all
|
||||
|
||||
bun run typecheck
|
||||
|
||||
# Remote Control Server
|
||||
bun run rcs
|
||||
|
||||
# Docs dev server (Mintlify)
|
||||
bun run docs:dev
|
||||
```
|
||||
@@ -51,26 +77,30 @@ bun run docs:dev
|
||||
### Runtime & Build
|
||||
|
||||
- **Runtime**: Bun (not Node.js). All imports, builds, and execution use Bun APIs.
|
||||
- **Build**: `build.ts` 执行 `Bun.build()` with `splitting: true`,入口 `src/entrypoints/cli.tsx`,输出 `dist/cli.js` + chunk files。默认启用 `AGENT_TRIGGERS_REMOTE`、`CHICAGO_MCP`、`VOICE_MODE` feature。构建后自动替换 `import.meta.require` 为 Node.js 兼容版本(产物 bun/node 都可运行)。
|
||||
- **Dev mode**: `scripts/dev.ts` 通过 Bun `-d` flag 注入 `MACRO.*` defines,运行 `src/entrypoints/cli.tsx`。默认启用 `BUDDY`、`TRANSCRIPT_CLASSIFIER`、`BRIDGE_MODE`、`AGENT_TRIGGERS_REMOTE`、`CHICAGO_MCP`、`VOICE_MODE` 六个 feature。
|
||||
- **Build**: `build.ts` 执行 `Bun.build()` with `splitting: true`,入口 `src/entrypoints/cli.tsx`,输出 `dist/cli.js` + chunk files。Build 默认启用 19 个 feature(见下方 Feature Flag 段)。构建后自动替换 `import.meta.require` 为 Node.js 兼容版本(产物 bun/node 都可运行)。
|
||||
- **Dev mode**: `scripts/dev.ts` 通过 Bun `-d` flag 注入 `MACRO.*` defines,运行 `src/entrypoints/cli.tsx`。默认启用全部 feature。
|
||||
- **Module system**: ESM (`"type": "module"`), TSX with `react-jsx` transform.
|
||||
- **Monorepo**: Bun workspaces — internal packages live in `packages/` resolved via `workspace:*`.
|
||||
- **Monorepo**: Bun workspaces — 15 个 workspace packages + 若干辅助目录 in `packages/` resolved via `workspace:*`。
|
||||
- **Lint/Format**: Biome (`biome.json`)。`bun run lint` / `bun run lint:fix` / `bun run format`。
|
||||
- **Defines**: 集中管理在 `scripts/defines.ts`。当前版本 `2.1.888`。
|
||||
- **CI**: GitHub Actions — `ci.yml`(构建+测试)、`release-rcs.yml`(RCS 发布)、`update-contributors.yml`(自动更新贡献者)。
|
||||
|
||||
### Entry & Bootstrap
|
||||
|
||||
1. **`src/entrypoints/cli.tsx`** — True entrypoint。`main()` 函数按优先级处理多条快速路径:
|
||||
1. **`src/entrypoints/cli.tsx`** (373 行) — True entrypoint。`main()` 函数按优先级处理多条快速路径:
|
||||
- `--version` / `-v` — 零模块加载
|
||||
- `--dump-system-prompt` — feature-gated (DUMP_SYSTEM_PROMPT)
|
||||
- `--claude-in-chrome-mcp` / `--chrome-native-host`
|
||||
- `--computer-use-mcp` — 独立 MCP server 模式
|
||||
- `--daemon-worker=<kind>` — feature-gated (DAEMON)
|
||||
- `remote-control` / `rc` / `bridge` — feature-gated (BRIDGE_MODE)
|
||||
- `daemon` — feature-gated (DAEMON)
|
||||
- `remote-control` / `rc` / `remote` / `sync` / `bridge` — feature-gated (BRIDGE_MODE)
|
||||
- `daemon` [subcommand] — feature-gated (DAEMON)
|
||||
- `ps` / `logs` / `attach` / `kill` / `--bg` — feature-gated (BG_SESSIONS)
|
||||
- `new` / `list` / `reply` — Template job commands
|
||||
- `environment-runner` / `self-hosted-runner` — BYOC runner
|
||||
- `--tmux` + `--worktree` 组合
|
||||
- 默认路径:加载 `main.tsx` 启动完整 CLI
|
||||
2. **`src/main.tsx`** (~4680 行) — Commander.js CLI definition。注册大量 subcommands:`mcp` (serve/add/remove/list...)、`server`、`ssh`、`open`、`auth`、`plugin`、`agents`、`auto-mode`、`doctor`、`update` 等。主 `.action()` 处理器负责权限、MCP、会话恢复、REPL/Headless 模式分发。
|
||||
2. **`src/main.tsx`** (~6981 行) — Commander.js CLI definition。注册大量 subcommands:`mcp` (serve/add/remove/list...)、`server`、`ssh`、`open`、`auth`、`plugin`、`agents`、`auto-mode`、`doctor`、`update` 等。主 `.action()` 处理器负责权限、MCP、会话恢复、REPL/Headless 模式分发。
|
||||
3. **`src/entrypoints/init.ts`** — One-time initialization (telemetry, config, trust dialog)。
|
||||
|
||||
### Core Loop
|
||||
@@ -82,21 +112,27 @@ bun run docs:dev
|
||||
### API Layer
|
||||
|
||||
- **`src/services/api/claude.ts`** — Core API client. Builds request params (system prompt, messages, tools, betas), calls the Anthropic SDK streaming endpoint, and processes `BetaRawMessageStreamEvent` events.
|
||||
- Supports multiple providers: Anthropic direct, AWS Bedrock, Google Vertex, Azure.
|
||||
- Provider selection in `src/utils/model/providers.ts`.
|
||||
- **7 providers**: `firstParty` (Anthropic direct), `bedrock` (AWS), `vertex` (Google Cloud), `foundry`, `openai`, `gemini`, `grok` (xAI)。
|
||||
- Provider selection in `src/utils/model/providers.ts`。优先级:modelType 参数 > 环境变量 > 默认 firstParty。
|
||||
|
||||
### Tool System
|
||||
|
||||
- **`src/Tool.ts`** — Tool interface definition (`Tool` type) and utilities (`findToolByName`, `toolMatchesName`).
|
||||
- **`src/tools.ts`** — Tool registry. Assembles the tool list; some tools are conditionally loaded via `feature()` flags or `process.env.USER_TYPE`.
|
||||
- **`src/tools/<ToolName>/`** — 61 个 tool 目录(如 BashTool, FileEditTool, GrepTool, AgentTool, WebFetchTool, LSPTool, MCPTool 等)。每个 tool 包含 `name`、`description`、`inputSchema`、`call()` 及可选的 React 渲染组件。
|
||||
- **`src/tools/shared/`** — Tool 共享工具函数。
|
||||
- **`src/tools.ts`** (392 行) — Tool registry. Assembles the tool list; tools are imported from `@claude-code-best/builtin-tools` package. Some tools are conditionally loaded via `feature()` flags or `process.env.USER_TYPE`.
|
||||
- **`packages/builtin-tools/src/tools/`** — 59 个子目录(含 shared/testing 等工具目录),通过 `@claude-code-best/builtin-tools` 包导出。主要分类:
|
||||
- **文件操作**: FileEditTool, FileReadTool, FileWriteTool, GlobTool, GrepTool
|
||||
- **Shell/执行**: BashTool, PowerShellTool, REPLTool
|
||||
- **Agent 系统**: AgentTool, TaskCreateTool, TaskUpdateTool, TaskListTool, TaskGetTool
|
||||
- **规划**: EnterPlanModeTool, ExitPlanModeV2Tool, VerifyPlanExecutionTool
|
||||
- **Web/MCP**: WebFetchTool, WebSearchTool, MCPTool, McpAuthTool
|
||||
- **调度**: CronCreateTool, CronDeleteTool, CronListTool
|
||||
- **其他**: LSPTool, ConfigTool, SkillTool, EnterWorktreeTool, ExitWorktreeTool 等
|
||||
|
||||
### UI Layer (Ink)
|
||||
|
||||
- **`src/ink.ts`** — Ink render wrapper with ThemeProvider injection.
|
||||
- **`src/ink/`** — Custom Ink framework (forked/internal): custom reconciler, hooks (`useInput`, `useTerminalSize`, `useSearchHighlight`), virtual list rendering.
|
||||
- **`src/components/`** — 大量 React 组件(170+ 项),渲染于终端 Ink 环境中。关键组件:
|
||||
- **`packages/@ant/ink/`** — Custom Ink framework(forked/internal),包含 components、core、hooks、keybindings、theme、utils。注意:不是 `src/ink/`。
|
||||
- **`src/components/`** — 149 个组件目录/文件,渲染于终端 Ink 环境中。关键组件:
|
||||
- `App.tsx` — Root provider (AppState, Stats, FpsMetrics)
|
||||
- `Messages.tsx` / `MessageRow.tsx` — Conversation message rendering
|
||||
- `PromptInput/` — User input handling
|
||||
@@ -112,10 +148,45 @@ bun run docs:dev
|
||||
- **`src/state/selectors.ts`** — State selectors.
|
||||
- **`src/bootstrap/state.ts`** — Module-level singletons for session-global state (session ID, CWD, project root, token counts, model overrides, client type, permission mode).
|
||||
|
||||
### Workspace Packages
|
||||
|
||||
| Package | 说明 |
|
||||
|---------|------|
|
||||
| `packages/@ant/ink/` | Forked Ink 框架(components、hooks、keybindings、theme) |
|
||||
| `packages/@ant/computer-use-mcp/` | Computer Use MCP server(截图/键鼠/剪贴板/应用管理) |
|
||||
| `packages/@ant/computer-use-input/` | 键鼠模拟(dispatcher + darwin/win32/linux backend) |
|
||||
| `packages/@ant/computer-use-swift/` | 截图 + 应用管理(dispatcher + per-platform backend) |
|
||||
| `packages/@ant/claude-for-chrome-mcp/` | Chrome 浏览器控制(通过 `--chrome` 启用) |
|
||||
| `packages/@ant/model-provider/` | Model provider 抽象层 |
|
||||
| `packages/builtin-tools/` | 内置工具集(60 个 tool 实现,通过 `@claude-code-best/builtin-tools` 导出) |
|
||||
| `packages/agent-tools/` | Agent 工具集 |
|
||||
| `packages/acp-link/` | ACP 代理服务器(WebSocket → ACP agent 桥接) |
|
||||
| `packages/cc-knowledge/` | Claude Code 知识库(非 workspace 包) |
|
||||
| `packages/langfuse-dashboard/` | Langfuse 可观测性面板(非 workspace 包) |
|
||||
| `packages/mcp-client/` | MCP 客户端库 |
|
||||
| `packages/mcp-server/` | MCP 服务端库(非 workspace 包) |
|
||||
| `packages/remote-control-server/` | 自托管 Remote Control Server(Docker 部署,含 Web UI)— Web UI 已重构为 React + Vite + Radix UI,支持 ACP agent 接入 |
|
||||
| `packages/swarm/` | Swarm 解耦模块(非 workspace 包) |
|
||||
| `packages/shell/` | Shell 抽象(非 workspace 包) |
|
||||
| `packages/audio-capture-napi/` | 原生音频捕获(已恢复) |
|
||||
| `packages/color-diff-napi/` | 颜色差异计算(完整实现,11 tests) |
|
||||
| `packages/image-processor-napi/` | 图像处理(已恢复) |
|
||||
| `packages/modifiers-napi/` | 键盘修饰键检测(stub) |
|
||||
| `packages/url-handler-napi/` | URL scheme 处理(stub) |
|
||||
|
||||
### Bridge / Remote Control
|
||||
|
||||
- **`src/bridge/`** (~35 files) — Remote Control / Bridge 模式。feature-gated by `BRIDGE_MODE`。包含 bridge API、会话管理、JWT 认证、消息传输、权限回调等。Entry: `bridgeMain.ts`。
|
||||
- **`src/bridge/`** (~38 files) — Remote Control / Bridge 模式。feature-gated by `BRIDGE_MODE`。包含 bridge API、会话管理、JWT 认证、消息传输、权限回调等。Entry: `bridgeMain.ts`。
|
||||
- **`packages/remote-control-server/`** — 自托管 RCS,支持 Docker 部署,含 Web UI 控制面板(React 19 + Vite + Radix UI)。支持 ACP agent 通过 acp-link 接入(ACP WebSocket handler、relay handler、SSE event stream)。通过 `bun run rcs` 启动。
|
||||
- CLI 快速路径: `claude remote-control` / `claude rc` / `claude bridge`。
|
||||
- 详见 `docs/features/remote-control-self-hosting.md`。
|
||||
|
||||
### ACP Protocol (Agent Client Protocol)
|
||||
|
||||
- **`src/services/acp/`** — ACP agent 实现,包含 `agent.ts`(AcpAgent 类)、`bridge.ts`(Claude Code ↔ ACP 桥接)、`permissions.ts`(权限处理)、`entry.ts`(入口)。
|
||||
- **`packages/acp-link/`** — ACP 代理服务器,将 WebSocket 客户端桥接到 ACP agent。提供 `acp-link` CLI 命令,支持自定义端口/HTTPS/认证/会话管理、RCS 集成(REST 注册 + WS identify 两步流程)、权限模式透传(fallback: 客户端传值 > config > `ACP_PERMISSION_MODE` 环境变量)。
|
||||
- ACP 权限管道改进:`createAcpCanUseTool` 统一权限流水线,`applySessionMode` 模式同步,`bypassPermissions` 可用性检测(非 root/sandbox 环境)。
|
||||
- ACP Plan 可视化已支持 `session/update plan` 类型的消息展示(PlanView 组件,含进度条/状态图标/优先级标签)。
|
||||
|
||||
### Daemon Mode
|
||||
|
||||
@@ -128,91 +199,47 @@ bun run docs:dev
|
||||
|
||||
### Feature Flag System
|
||||
|
||||
Feature flags control which functionality is enabled at runtime:
|
||||
Feature flags control which functionality is enabled at runtime. 代码中统一通过 `import { feature } from 'bun:bundle'` 导入,调用 `feature('FLAG_NAME')` 返回 `boolean`。
|
||||
|
||||
- **在代码中使用**: 统一通过 `import { feature } from 'bun:bundle'` 导入,调用 `feature('FLAG_NAME')` 返回 `boolean`。**不要**在 `cli.tsx` 或其他文件里自己定义 `feature` 函数或覆盖这个 import。
|
||||
- **启用方式**: 通过环境变量 `FEATURE_<FLAG_NAME>=1`。例如 `FEATURE_BUDDY=1 bun run dev` 启用 BUDDY 功能。
|
||||
- **Dev 默认 features**: `BUDDY`、`TRANSCRIPT_CLASSIFIER`、`BRIDGE_MODE`、`AGENT_TRIGGERS_REMOTE`、`CHICAGO_MCP`、`VOICE_MODE`(见 `scripts/dev.ts`)。
|
||||
- **Build 默认 features**: `AGENT_TRIGGERS_REMOTE`、`CHICAGO_MCP`、`VOICE_MODE`(见 `build.ts`)。
|
||||
- **常见 flag**: `BUDDY`, `DAEMON`, `BRIDGE_MODE`, `BG_SESSIONS`, `PROACTIVE`, `KAIROS`, `VOICE_MODE`, `FORK_SUBAGENT`, `SSH_REMOTE`, `DIRECT_CONNECT`, `TEMPLATES`, `CHICAGO_MCP`, `BYOC_ENVIRONMENT_RUNNER`, `SELF_HOSTED_RUNNER`, `COORDINATOR_MODE`, `UDS_INBOX`, `LODESTONE`, `ABLATION_BASELINE` 等。
|
||||
- **类型声明**: `src/types/internal-modules.d.ts` 中声明了 `bun:bundle` 模块的 `feature` 函数签名。
|
||||
**启用方式**: 环境变量 `FEATURE_<FLAG_NAME>=1`。例如 `FEATURE_BUDDY=1 bun run dev`。
|
||||
|
||||
**Build 默认 features**(19 个,见 `build.ts`):
|
||||
- 基础: `BUDDY`, `TRANSCRIPT_CLASSIFIER`, `BRIDGE_MODE`, `AGENT_TRIGGERS_REMOTE`, `CHICAGO_MCP`, `VOICE_MODE`
|
||||
- 统计/缓存: `SHOT_STATS`, `PROMPT_CACHE_BREAK_DETECTION`, `TOKEN_BUDGET`
|
||||
- P0 本地: `AGENT_TRIGGERS`, `ULTRATHINK`, `BUILTIN_EXPLORE_PLAN_AGENTS`, `LODESTONE`
|
||||
- P1 API 依赖: `EXTRACT_MEMORIES`, `VERIFICATION_AGENT`, `KAIROS_BRIEF`, `AWAY_SUMMARY`, `ULTRAPLAN`
|
||||
- P2: `DAEMON`
|
||||
|
||||
**Dev mode 默认**: 全部启用(见 `scripts/dev.ts`)。
|
||||
|
||||
**类型声明**: `src/types/internal-modules.d.ts` 中声明了 `bun:bundle` 模块的 `feature` 函数签名。
|
||||
|
||||
**新增功能的正确做法**: 保留 `import { feature } from 'bun:bundle'` + `feature('FLAG_NAME')` 的标准模式,在运行时通过环境变量或配置控制,不要绕过 feature flag 直接 import。
|
||||
|
||||
### Multi-API 兼容层
|
||||
|
||||
支持 OpenAI、Gemini、Grok 三种第三方 API,通过 `/login` 命令配置,均采用流适配器模式转为 Anthropic 内部格式。详见各兼容层的 docs 文档。
|
||||
|
||||
### 穷鬼模式(Budget Mode)
|
||||
|
||||
- 通过 `/poor` 命令切换,持久化到 `settings.json`。
|
||||
- 启用后跳过 `extract_memories`、`prompt_suggestion` 和 `verification_agent`,显著减少 token 消耗。
|
||||
- 实现在 `src/commands/poor/poorMode.ts`。
|
||||
|
||||
### Stubbed/Deleted Modules
|
||||
|
||||
| Module | Status |
|
||||
|--------|--------|
|
||||
| Computer Use (`@ant/*`) | Restored — `computer-use-swift`, `computer-use-input`, `computer-use-mcp`, `claude-for-chrome-mcp` 均有完整实现,macOS + Windows 可用,Linux 后端待完成 |
|
||||
| `*-napi` packages | `audio-capture-napi`、`image-processor-napi` 已恢复实现;`color-diff-napi` 完整实现;`url-handler-napi`、`modifiers-napi` 仍为 stub |
|
||||
| Voice Mode | Restored — `src/voice/`、`src/hooks/useVoiceIntegration.tsx`、`src/services/voiceStreamSTT.ts` 等,Push-to-Talk 语音输入(需 Anthropic OAuth) |
|
||||
| OpenAI 兼容层 | Restored — `src/services/api/openai/`,支持 Ollama/DeepSeek/vLLM 等任意 OpenAI 协议端点,通过 `CLAUDE_CODE_USE_OPENAI=1` 启用 |
|
||||
| Computer Use (`@ant/*`) | Restored — macOS + Windows + Linux(后端完整度不一) |
|
||||
| `*-napi` packages | `audio-capture-napi`、`image-processor-napi` 已恢复;`color-diff-napi` 完整;`modifiers-napi`、`url-handler-napi` 仍为 stub |
|
||||
| Voice Mode | Restored — Push-to-Talk 语音输入(需 Anthropic OAuth) |
|
||||
| OpenAI/Gemini/Grok 兼容层 | Restored |
|
||||
| Remote Control Server | Restored — 自托管 RCS + Web UI |
|
||||
| Analytics / GrowthBook / Sentry | Empty implementations |
|
||||
| Magic Docs / LSP Server | Removed |
|
||||
| Plugins / Marketplace | Removed |
|
||||
| MCP OAuth | Simplified |
|
||||
|
||||
### Computer Use
|
||||
|
||||
Feature flag `CHICAGO_MCP`,dev/build 默认启用。实现跨平台屏幕操控(macOS + Windows 可用,Linux 待完成)。
|
||||
|
||||
- **`packages/@ant/computer-use-mcp/`** — MCP server,注册截图/键鼠/剪贴板/应用管理工具
|
||||
- **`packages/@ant/computer-use-input/`** — 键鼠模拟,dispatcher + per-platform backend(`backends/darwin.ts`、`win32.ts`、`linux.ts`)
|
||||
- **`packages/@ant/computer-use-swift/`** — 截图 + 应用管理,同样 dispatcher + per-platform backend
|
||||
- **`packages/@ant/claude-for-chrome-mcp/`** — Chrome 浏览器控制(独立于 Computer Use,通过 `--chrome` CLI 参数启用)
|
||||
|
||||
详见 `docs/features/computer-use.md`。
|
||||
|
||||
### Voice Mode
|
||||
|
||||
Feature flag `VOICE_MODE`,dev/build 默认启用。Push-to-Talk 语音输入,音频通过 WebSocket 流式传输到 Anthropic STT(Nova 3)。需要 Anthropic OAuth(非 API key)。
|
||||
|
||||
- **`src/voice/voiceModeEnabled.ts`** — 三层门控(feature flag + GrowthBook + OAuth auth)
|
||||
- **`src/hooks/useVoice.ts`** — React hook 管理录音状态和 WebSocket 连接
|
||||
- **`src/services/voiceStreamSTT.ts`** — STT WebSocket 流式传输
|
||||
|
||||
详见 `docs/features/voice-mode.md`。
|
||||
|
||||
### OpenAI 兼容层
|
||||
|
||||
通过 `CLAUDE_CODE_USE_OPENAI=1` 环境变量启用,支持任意 OpenAI Chat Completions 协议端点(Ollama、DeepSeek、vLLM 等)。流适配器模式:在 `queryModel()` 中将 Anthropic 格式请求转为 OpenAI 格式,再将 SSE 流转换回 `BetaRawMessageStreamEvent`,下游代码完全不改。
|
||||
|
||||
- **`src/services/api/openai/`** — client、消息/工具转换、流适配、模型映射
|
||||
- **`src/utils/model/providers.ts`** — 添加 `'openai'` provider 类型(最高优先级)
|
||||
|
||||
关键环境变量:`CLAUDE_CODE_USE_OPENAI`、`OPENAI_API_KEY`、`OPENAI_BASE_URL`、`OPENAI_MODEL`、`OPENAI_DEFAULT_OPUS_MODEL`、`OPENAI_DEFAULT_SONNET_MODEL`、`OPENAI_DEFAULT_HAIKU_MODEL`。详见 `docs/plans/openai-compatibility.md`。
|
||||
|
||||
### Gemini 兼容层
|
||||
|
||||
通过 `CLAUDE_CODE_USE_GEMINI=1` 环境变量或 `modelType: "gemini"` 设置启用,支持 Google Gemini API。独立的环境变量体系,不与 OpenAI 或 Anthropic 配置混杂。
|
||||
|
||||
- **`src/services/api/gemini/`** — client、模型映射、类型定义
|
||||
- **`src/utils/model/providers.ts`** — 添加 `'gemini'` provider 类型
|
||||
- **`src/utils/managedEnvConstants.ts`** — Gemini 专用的 managed env vars
|
||||
|
||||
关键环境变量:
|
||||
- `CLAUDE_CODE_USE_GEMINI` - 启用 Gemini provider
|
||||
- `GEMINI_API_KEY` - API 密钥(必填)
|
||||
- `GEMINI_BASE_URL` - API 端点(可选,默认 `https://generativelanguage.googleapis.com/v1beta`)
|
||||
- `GEMINI_MODEL` - 直接指定模型(最高优先级)
|
||||
- `GEMINI_DEFAULT_HAIKU_MODEL` / `GEMINI_DEFAULT_SONNET_MODEL` / `GEMINI_DEFAULT_OPUS_MODEL` - 按能力级别映射
|
||||
- `GEMINI_DEFAULT_HAIKU_MODEL_NAME` / `DESCRIPTION` / `SUPPORTED_CAPABILITIES` - 显示名称和描述
|
||||
- `GEMINI_SMALL_FAST_MODEL` - 快速任务使用的模型(可选)
|
||||
|
||||
模型映射优先级(`src/services/api/gemini/modelMapping.ts`):
|
||||
1. `GEMINI_MODEL` - 直接覆盖
|
||||
2. `GEMINI_DEFAULT_*_MODEL` - 独立配置(推荐)
|
||||
3. `ANTHROPIC_DEFAULT_*_MODEL` - 向后兼容 fallback(已废弃)
|
||||
4. 原样返回 Anthropic 模型名
|
||||
|
||||
使用示例:
|
||||
```bash
|
||||
export CLAUDE_CODE_USE_GEMINI=1
|
||||
export GEMINI_API_KEY="your-api-key"
|
||||
export GEMINI_DEFAULT_SONNET_MODEL="gemini-2.5-flash"
|
||||
export GEMINI_DEFAULT_OPUS_MODEL="gemini-2.5-pro"
|
||||
```
|
||||
|
||||
### Key Type Files
|
||||
|
||||
- **`src/types/global.d.ts`** — Declares `MACRO`, `BUILD_TARGET`, `BUILD_ENV` and internal Anthropic-only identifiers.
|
||||
@@ -223,20 +250,74 @@ export GEMINI_DEFAULT_OPUS_MODEL="gemini-2.5-pro"
|
||||
## Testing
|
||||
|
||||
- **框架**: `bun:test`(内置断言 + mock)
|
||||
- **当前状态**: 3175 tests / 207 files / 0 fail
|
||||
- **单元测试**: 就近放置于 `src/**/__tests__/`,文件名 `<module>.test.ts`
|
||||
- **集成测试**: `tests/integration/` — 4 个文件(cli-arguments, context-build, message-pipeline, tool-chain)
|
||||
- **共享 mock/fixture**: `tests/mocks/`(api-responses, file-system, fixtures/)
|
||||
- **命名**: `describe("functionName")` + `test("behavior description")`,英文
|
||||
- **Mock 模式**: 对重依赖模块使用 `mock.module()` + `await import()` 解锁(必须内联在测试文件中,不能从共享 helper 导入)
|
||||
- **当前状态**: ~1623 tests / 114 files (110 unit + 4 integration) / 0 fail(详见 `docs/testing-spec.md`)
|
||||
- **包测试**: `packages/` 下各包也有独立测试(如 `color-diff-napi` 11 tests)
|
||||
|
||||
### Mock 使用规范
|
||||
|
||||
**只 mock 有副作用的依赖链,不 mock 纯函数/纯数据模块。**
|
||||
|
||||
被迫 mock 的根源:`log.ts` / `debug.ts` → `bootstrap/state.ts`(模块级 `realpathSync` / `randomUUID` 副作用)。必须 mock 的模块:`log.ts`、`debug.ts`、`bun:bundle`、`settings/settings.js`、`config.ts`、`auth.ts`、第三方网络库。
|
||||
|
||||
不要 mock:纯函数模块(`errors.ts`、`stringUtils.js`)、mock 值与真实实现相同的模块、mock 路径与实际 import 不匹配的模块。
|
||||
|
||||
路径规则:统一用 `.ts` 扩展名 + `src/*` 别名路径,禁止双重 mock 同一模块。
|
||||
|
||||
### 类型检查
|
||||
|
||||
项目使用 TypeScript strict 模式,**tsc 必须零错误**。每次修改后运行:
|
||||
|
||||
```bash
|
||||
bun run typecheck # equivalent to bun run typecheck
|
||||
```
|
||||
|
||||
**类型规范**:
|
||||
- 生产代码禁止 `as any`;测试文件中 mock 数据可用 `as any`
|
||||
- 类型不匹配优先用 `as unknown as SpecificType` 双重断言,或补充 interface
|
||||
- 未知结构对象用 `Record<string, unknown>` 替代 `any`
|
||||
- 联合类型用类型守卫(type guard)收窄,不要强转
|
||||
- `msg.request` 属性访问:`const req = msg.request as Record<string, unknown>`
|
||||
- Ink `color` prop:用 `as keyof Theme` 而非 `as any`
|
||||
|
||||
## Working with This Codebase
|
||||
|
||||
- **Don't try to fix all tsc errors** — they're from decompilation and don't affect runtime.
|
||||
- **tsc must pass** — `bun run typecheck` 必须零错误,任何修改都不能引入新的类型错误。
|
||||
- **Feature flags** — 默认全部关闭(`feature()` 返回 `false`)。Dev/build 各有自己的默认启用列表。不要在 `cli.tsx` 中重定义 `feature` 函数。
|
||||
- **React Compiler output** — Components have decompiled memoization boilerplate (`const $ = _c(N)`). This is normal.
|
||||
- **`bun:bundle` import** — `import { feature } from 'bun:bundle'` 是 Bun 内置模块,由运行时/构建器解析。不要用自定义函数替代它。
|
||||
- **`bun:bundle` import** — `import { feature } from 'bun:bundle'` 是 Bun 内置模块,由运行时/构建器解析。不要用自定义函数替代它。**`feature()` 只能直接用在 `if` 语句或三元表达式的条件位置**(Bun 编译器限制),不能赋值给变量、不能放在箭头函数体里、不能作为 `&&` 链的一部分。正确:`if (feature('X')) {}` 或 `feature('X') ? a : b`。
|
||||
- **`src/` path alias** — tsconfig maps `src/*` to `./src/*`. Imports like `import { ... } from 'src/utils/...'` are valid.
|
||||
- **MACRO defines** — 集中管理在 `scripts/defines.ts`。Dev mode 通过 `bun -d` 注入,build 通过 `Bun.build({ define })` 注入。修改版本号等常量只改这个文件。
|
||||
- **构建产物兼容 Node.js** — `build.ts` 会自动后处理 `import.meta.require`,产物可直接用 `node dist/cli.js` 运行。
|
||||
- **Biome 配置** — 大量 lint 规则被关闭(decompiled 代码不适合严格 lint)。`.tsx` 文件用 120 行宽 + 强制分号;其他文件 80 行宽 + 按需分号。
|
||||
- **Ink 框架在 `packages/@ant/ink/`** — 不是 `src/ink/`(该目录不存在)。Ink 相关的组件、hooks、keybindings 都在 packages 中。
|
||||
- **Provider 优先级** — `modelType` 参数 > 环境变量 > 默认 `firstParty`。新增 provider 需在 `src/utils/model/providers.ts` 注册。
|
||||
|
||||
## Design Context
|
||||
|
||||
Impeccable 设计上下文保存在 `.impeccable.md` 中。设计 Web UI(RCS 控制面板、文档站、着陆页)时必须参考该文件。
|
||||
|
||||
### 核心设计原则
|
||||
|
||||
1. **Considered over clever** — 每个设计选择都应感觉有意为之,而非追逐潮流
|
||||
2. **Warmth through subtlety** — 通过橙色色调的中性色、留白布局、有温度的文案来传达温暖
|
||||
3. **Density with clarity** — 技术用户需要信息密度,但不能混乱
|
||||
4. **Community voice** — 设计应感觉是由使用者创造的,而非遥远的设计团队
|
||||
5. **Anthropic's shadow** — 遵循 Anthropic 的设计直觉:干净的布局、充足的间距、温暖的色温
|
||||
|
||||
### 品牌色
|
||||
|
||||
- 主色:Claude Orange `#D77757`(terra cotta)
|
||||
- 辅色:Claude Blue `#5769F7`
|
||||
- 暗色模式使用温暖的深色表面(非冷蓝黑色)
|
||||
|
||||
### 目标用户
|
||||
|
||||
技术团队/企业,在专业工作流中使用 AI 辅助编程。友好的开源社区氛围,非企业 SaaS 风格。
|
||||
|
||||
### 视觉参考
|
||||
|
||||
Anthropic 公司的设计风格 — 干净、考究、温暖的底色。大量留白,以排版为核心。避免 AI 产品常见的设计套路(渐变文字、玻璃态、霓虹色)。
|
||||
|
||||
189
DEV-LOG.md
189
DEV-LOG.md
@@ -1,5 +1,194 @@
|
||||
# DEV-LOG
|
||||
|
||||
## /poor 省流模式 (2026-04-11)
|
||||
|
||||
新增 `/poor` 命令,toggle 关闭 `extract_memories` 和 `prompt_suggestion`,省 token。
|
||||
|
||||
- 新增 `POOR` feature flag(build.ts + dev.ts)
|
||||
- `src/commands/poor/` — 命令定义 + toggle 实现 + 状态管理
|
||||
- `src/query/stopHooks.ts` — POOR 模式激活时跳过 extract_memories 和 prompt_suggestion
|
||||
|
||||
---
|
||||
|
||||
## Pipe IPC + LAN Pipes + Monitor Tool + 工具恢复 (2026-04-08 ~ 2026-04-11)
|
||||
|
||||
**分支**: `feat/pr-package-adapt`
|
||||
|
||||
### 背景
|
||||
|
||||
从 decompiled 代码恢复大量 stub 为完整实现,同时新增 LAN 跨机器通讯能力。本次 PR 覆盖:Pipe IPC 系统、LAN Pipes、Monitor Tool、20+ 工具/组件<E7BB84><E4BBB6><EFBFBD>复、REPL hook 架构重构。
|
||||
|
||||
### 实现
|
||||
|
||||
#### 1. PipeServer TCP 双模式(`src/utils/pipeTransport.ts`)
|
||||
|
||||
从原始的纯 UDS 服务器扩展为 UDS + TCP 双模式:
|
||||
|
||||
- 提取 `setupSocket()` 共享方法,UDS 和 TCP 的 socket 处理逻辑完全一致
|
||||
- `start(options?: PipeServerOptions)` 新增可选参数 `{ enableTcp, tcpPort }`
|
||||
- 内部维护两个 `net.Server`(UDS + TCP),共享同一组 `clients: Set<Socket>` 和 `handlers`
|
||||
- TCP server 绑定 `0.0.0.0` + 动态端口(port=0 由 OS 分配)
|
||||
- `tcpAddress` getter 暴露 TCP 端口信息
|
||||
- `close()` 同时关闭两个 server
|
||||
- 新增类型:`PipeTransportMode`、`TcpEndpoint`、`PipeServerOptions`
|
||||
|
||||
PipeClient 对应扩展:
|
||||
- 构造函数新增可选 `TcpEndpoint` 参数
|
||||
- `connect()` 根据是否有 TCP endpoint 分派到 `connectTcp()` 或 `connectUds()`
|
||||
- TCP 连接不需要文件存在轮询,直接建立连接
|
||||
|
||||
#### 2. LAN Beacon — UDP Multicast 发现(`src/utils/lanBeacon.ts`,新文件)
|
||||
|
||||
零配置局域网 peer 发现:
|
||||
|
||||
- **协议**:UDP multicast 组 `224.0.71.67`("CC" ASCII),端口 `7101`,TTL=1
|
||||
- **Announce 包**:JSON `{ proto, pipeName, machineId, hostname, ip, tcpPort, role, ts }`
|
||||
- **广播间隔**:3 秒,首次在 socket bind 完成后立即发送
|
||||
- **Peer 超时**:15 秒无 announce 视为 lost
|
||||
- **事件**:`peer-discovered`、`peer-lost`
|
||||
- **存储**:module-level singleton `getLanBeacon()`/`setLanBeacon()`,不挂在 Zustand state 上
|
||||
|
||||
关键修复:
|
||||
- `addMembership(group, localIp)` + `setMulticastInterface(localIp)` 指定 LAN 网卡,解决 Windows 上 WSL/Docker 虚拟网卡劫持 multicast 的问题
|
||||
- announce/cleanup 定时器移入 `bind()` 回调内,修复 socket 未就绪时发送的竞态
|
||||
|
||||
#### 3. Registry 扩展(`src/utils/pipeRegistry.ts`)
|
||||
|
||||
- `PipeRegistryEntry` 新增 `tcpPort?` 和 `lanVisible?` 字段
|
||||
- `mergeWithLanPeers(registry, lanPeers)` 合并本地 registry 和 LAN beacon peers,本地优先
|
||||
|
||||
#### 4. Peer Address 扩展(`src/utils/peerAddress.ts`)
|
||||
|
||||
- `parseAddress()` 新增 `tcp` scheme:`tcp:192.168.1.20:7100`
|
||||
- 新增 `parseTcpTarget()` 解析 `host:port` 字符串
|
||||
|
||||
#### 5. REPL 集成(`src/screens/REPL.tsx`)
|
||||
|
||||
三个阶段的改动:
|
||||
|
||||
**Bootstrap**:`createPipeServer()` 时根据 `feature('LAN_PIPES')` 传入 TCP 选项 → 启动 `LanBeacon` → 注册 entry 携带 tcpPort
|
||||
|
||||
**Heartbeat**(每 5 秒):
|
||||
- `refreshDiscoveredPipes()` 同时包含本地 subs 和 LAN beacon peers,防止 LAN peer 状态被覆盖
|
||||
- auto-attach 循环统一遍历本地 subs + LAN peers,LAN peers 通过 TCP endpoint 连接
|
||||
- cleanup 检查 LAN beacon peers 列表,避免误删存活的 LAN 连接
|
||||
- attach 请求携带 `machineId`,接收方区分 LAN peer(不要求 sub 角色)
|
||||
|
||||
**Cleanup**:通过 `getLanBeacon()` 获取并 `stop()`,`setLanBeacon(null)` 清除
|
||||
|
||||
#### 6. 命令更新
|
||||
|
||||
- `/pipes`(`src/commands/pipes/pipes.ts`):显示 `[LAN]` 标记的远端实例
|
||||
- `/attach`(`src/commands/attach/attach.ts`):自动查找 LAN beacon 获取 TCP endpoint
|
||||
- `SendMessageTool`(`src/tools/SendMessageTool/SendMessageTool.ts`):支持 `tcp:` scheme,权限检查要求用户确认
|
||||
|
||||
#### 7. Feature Flag
|
||||
|
||||
`LAN_PIPES` — 在 `scripts/dev.ts` 和 `build.ts` 的默认 features 列表中启用。所有 LAN 代码路径均通过 `feature('LAN_PIPES')` 门控。
|
||||
|
||||
#### 8. Pipe IPC 基础系统(`UDS_INBOX` feature)
|
||||
|
||||
- `PipeServer`/`PipeClient`:UDS 传输,NDJSON 协议(共享 `ndjsonFramer.ts`)
|
||||
- `PipeRegistry`:machineId 绑定的角色分配(main/sub),文件锁,并行探测
|
||||
- Master/slave attach 流程、prompt 转发、permission 转发
|
||||
- Heartbeat 生命周期(5s 间隔,stale entry 清理,busy flag 防重叠)
|
||||
- 命令:`/pipes`、`/attach`、`/detach`、`/send`、`/claim-main`、`/pipe-status`
|
||||
|
||||
#### 9. Monitor Tool(`MONITOR_TOOL` feature)
|
||||
|
||||
- `MonitorTool`:AI 可调用的后台 shell 监控工具
|
||||
- `/monitor` 命令:用户快捷入口,Windows 兼容(watch → PowerShell 循环)
|
||||
- `MonitorMcpTask`:从 stub 恢复完整生命周期(register/complete/fail/kill)
|
||||
- `MonitorPermissionRequest`:React 权限确认 UI
|
||||
- `MonitorMcpDetailDialog`:Shift+Down 详情面板
|
||||
|
||||
#### 10. 工具恢复(stub → 实现)
|
||||
|
||||
- SnipTool、SleepTool、ListPeersTool、SendUserFileTool
|
||||
- WebBrowserTool、SubscribePRTool、PushNotificationTool
|
||||
- CtxInspectTool、TerminalCaptureTool、WorkflowTool
|
||||
- REPLTool (.js → .ts)、VerifyPlanExecutionTool (.js → .ts)、SuggestBackgroundPRTool (.js → .ts)
|
||||
- 组件 .ts → .tsx 重写:MonitorPermissionRequest、ReviewArtifactPermissionRequest、MonitorMcpDetailDialog、WorkflowDetailDialog、WorkflowPermissionRequest
|
||||
|
||||
#### 11. REPL Hook 架构重构
|
||||
|
||||
从 REPL.tsx 提取 ~830 行 Pipe IPC 内联代码为 4 个独立 hook:
|
||||
|
||||
| Hook | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `usePipeIpc` | 623 | 生命周期:bootstrap、handlers、heartbeat、cleanup |
|
||||
| `usePipeRelay` | 38 | slave→master 消息回传(通过 `setPipeRelay` singleton) |
|
||||
| `usePipePermissionForward` | 159 | 权限请求转发 + 流式通知显示 |
|
||||
| `usePipeRouter` | 130 | selected pipe 输入路由 + role/IP 标签显示 |
|
||||
|
||||
共享工具:`ndjsonFramer.ts` 替换 3 份重复的 NDJSON 解析。
|
||||
|
||||
#### 12. Feature Flags 新增启用
|
||||
|
||||
UDS_INBOX、LAN_PIPES、MONITOR_TOOL、FORK_SUBAGENT、KAIROS、COORDINATOR_MODE、WORKFLOW_SCRIPTS、HISTORY_SNIP、CONTEXT_COLLAPSE
|
||||
|
||||
### 踩坑记录
|
||||
|
||||
1. **Multicast 绑错网卡**:Windows 上 `addMembership(group)` 不指定本地接口时,默认绑到 WSL/Docker 虚拟网卡(`172.19.112.1`),LAN 上的真实机器收不到。必须 `addMembership(group, localIp)` + `setMulticastInterface(localIp)`。
|
||||
|
||||
2. **Beacon ref 丢失**:最初用 `(store.getState() as any)._lanBeacon` 挂载 beacon 引用,但 Zustand `setState` 展开 `prev` 时不包含 `_lanBeacon` 属性,下次读取就是 `undefined`。改为 module-level singleton 解决。
|
||||
|
||||
3. **Heartbeat 清洗 LAN 连接**:`refreshDiscoveredPipes()` 每 5 秒用仅含本地 registry subs 的列表完全覆盖 `discoveredPipes` + `selectedPipes`,LAN peer 的发现和选择状态被持续清空。必须在 refresh 中同时包含 beacon peers。
|
||||
|
||||
4. **Heartbeat cleanup 误删**:`!aliveSubNames.has(slaveName)` 导致 LAN peer(不在本地 registry)被判定为死连接每 5 秒清除一次。需要同时检查 beacon peers 列表。
|
||||
|
||||
5. **跨机器 attach 被拒**:两台机器各自为 `main`,attach handler 硬编码 `role !== 'sub'` 拒绝。通过 attach_request 携带 `machineId`,接收方对不同 machineId 的请求放行。
|
||||
|
||||
6. **`feature()` 使用约束**:Bun 的 `feature()` 是编译时常量,只能在 `if` 语句或三元条件中直接使用,不能赋值给变量(如 `const x = feature('...')`),否则构建报错。
|
||||
|
||||
### 已知限制
|
||||
|
||||
- TCP 无认证:同 LAN 内任何设备知道端口号即可连接
|
||||
- JSON.parse 无 schema 验证:code review 建议增加 Zod 校验
|
||||
- Beacon 明文广播 IP/hostname/machineId:建议后续 hash 处理
|
||||
- `getLocalIp()` 可能返回 VPN 地址:多网卡环境需更精确的接口选择
|
||||
|
||||
### 测试
|
||||
|
||||
- `src/utils/__tests__/lanBeacon.test.ts`:7 个测试(mock dgram)
|
||||
- `src/utils/__tests__/peerAddress.test.ts`:8 个测试(纯函数)
|
||||
- 全量:2190 pass / 0 fail
|
||||
|
||||
### 防火墙配置
|
||||
|
||||
**Windows**(管理员 PowerShell):
|
||||
```powershell
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Beacon (UDP)" -Direction Inbound -Protocol UDP -LocalPort 7101 -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Pipes (TCP)" -Direction Inbound -Protocol TCP -LocalPort 1024-65535 -Program (Get-Command bun).Source -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Beacon Out (UDP)" -Direction Outbound -Protocol UDP -RemotePort 7101 -Action Allow -Profile Private
|
||||
```
|
||||
|
||||
**macOS**(首次运行时系统会弹出"允许接受传入连接"对话框,点击允许即可。手动放行):
|
||||
```bash
|
||||
# 如果使用 pf <20><><EFBFBD>火墙,添加规则:
|
||||
echo "pass in proto udp from any to any port 7101" | sudo pfctl -ef -
|
||||
# 或<><E68896>接在 System Settings → Network → Firewall 中允许 bun 进程
|
||||
```
|
||||
|
||||
**Linux**(firewalld):
|
||||
```bash
|
||||
sudo firewall-cmd --zone=trusted --add-port=7101/udp --permanent
|
||||
sudo firewall-cmd --zone=trusted --add-port=1024-65535/tcp --permanent
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
**Linux**(iptables):
|
||||
```bash
|
||||
sudo iptables -A INPUT -p udp --dport 7101 -j ACCEPT
|
||||
sudo iptables -A INPUT -p tcp --dport 1024:65535 -m owner --uid-owner $(id -u) -j ACCEPT
|
||||
sudo iptables-save | sudo tee /etc/iptables/rules.v4
|
||||
```
|
||||
|
||||
**通用验证**:确认网络为局域网(非公共 WiFi),路<EFBC8C><E8B7AF><EFBFBD>器未开启 AP 隔离。
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Daemon + Remote Control Server 还原 (2026-04-07)
|
||||
|
||||
**分支**: `feat/daemon-remote-control-server`
|
||||
|
||||
71
README.md
71
README.md
@@ -6,37 +6,51 @@
|
||||
[](https://github.com/claude-code-best/claude-code/blob/main/LICENSE)
|
||||
[](https://github.com/claude-code-best/claude-code/commits/main)
|
||||
[](https://bun.sh/)
|
||||
[](https://discord.gg/qZU6zS7Q)
|
||||
[](https://discord.gg/uApuzJWGKX)
|
||||
|
||||
> Which Claude do you like? The open source one is the best.
|
||||
|
||||
牢 A (Anthropic) 官方 [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI 工具的源码反编译/逆向还原项目。目标是将 Claude Code 大部分功能及工程化能力复现 (问就是老佛爷已经付过钱了)。虽然很难绷, 但是它叫做 CCB(踩踩背)...
|
||||
牢 A (Anthropic) 官方 [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI 工具的源码反编译/逆向还原项目。目标是将 Claude Code 大部分功能及工程化能力复现 (问就是老佛爷已经付过钱了)。虽然很难绷, 但是它叫做 CCB(踩踩背)... 而且, 我们实现了企业版或者需要登陆 Claude 账号才能使用的特性, 实现技术普惠
|
||||
|
||||
[文档在这里, 支持投稿 PR](https://ccb.agent-aura.top/) | [留影文档在这里](./Friends.md) | [Discord 群组](https://discord.gg/qZU6zS7Q)
|
||||
[文档在这里, 支持投稿 PR](https://ccb.agent-aura.top/) | [留影文档在这里](./Friends.md) | [Discord 群组](https://discord.gg/uApuzJWGKX)
|
||||
|
||||
- ✅ [x] V4 — 测试补全、[Buddy](https://ccb.agent-aura.top/docs/features/buddy)、[Auto Mode](https://ccb.agent-aura.top/docs/safety/auto-mode)、环境变量 Feature 开关
|
||||
- ✅ [x] V5 — [Sentry](https://ccb.agent-aura.top/docs/internals/sentry-setup) / [GrowthBook](https://ccb.agent-aura.top/docs/internals/growthbook-adapter) 企业监控、[自定义 Login](https://ccb.agent-aura.top/docs/features/custom-platform-login)、[OpenAI 兼容](https://ccb.agent-aura.top/docs/plans/openai-compatibility)、[Web Search](https://ccb.agent-aura.top/docs/features/web-browser-tool)、[Computer Use](https://ccb.agent-aura.top/docs/features/computer-use) / [Chrome Use](https://ccb.agent-aura.top/docs/features/claude-in-chrome-mcp)、[Voice Mode](https://ccb.agent-aura.top/docs/features/voice-mode)、[Bridge Mode](https://ccb.agent-aura.top/docs/features/bridge-mode)、[/dream 记忆整理](https://ccb.agent-aura.top/docs/features/auto-dream)
|
||||
- 🔮 [ ] V6 — 大规模重构石山代码,全面模块分包(全新分支,main 封存为历史版本)
|
||||
|
||||
| 特性 | 说明 | 文档 |
|
||||
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Claude 群控技术** | Pipe IPC 多实例协作:同机 main/sub 自动编排 + LAN 跨机器零配置发现与通讯,`/pipes` 选择面板 + `Shift+↓` 交互 + 消息广播路由 | [Pipe IPC](https://ccb.agent-aura.top/docs/features/pipes-and-lan) / [LAN](https://ccb.agent-aura.top/docs/features/lan-pipes) |
|
||||
| **ACP 协议一等一支持** | 支持接入 Zed、Cursor 等 IDE,支持会话恢复、Skills、权限桥接 | [文档](https://ccb.agent-aura.top/docs/features/acp-zed) |
|
||||
| **Remote Control 私有部署** | Docker 自托管远程界面, 可以手机上看 CC | [文档](https://ccb.agent-aura.top/docs/features/remote-control-self-hosting) |
|
||||
| **Langfuse 监控** | 企业级 Agent 监控, 可以清晰看到每次 agent loop 细节, 可以一键转化为数据集 | [文档](https://ccb.agent-aura.top/docs/features/langfuse-monitoring) |
|
||||
| **Web Search** | 内置网页搜索工具, 支持 bing 和 brave 搜索 | [文档](https://ccb.agent-aura.top/docs/features/web-browser-tool) |
|
||||
| **Poor Mode** | 穷鬼模式,关闭记忆提取和键入建议,大幅度减少并发请求 | /poor 可以开关 |
|
||||
| **Channels 频道通知** | MCP 服务器推送外部消息到会话(飞书/Slack/Discord/微信等),`--channels plugin:name@marketplace` 启用 | [文档](https://ccb.agent-aura.top/docs/features/channels) |
|
||||
| **自定义模型供应商** | OpenAI/Anthropic/Gemini/Grok 兼容 | [文档](https://ccb.agent-aura.top/docs/features/custom-platform-login) |
|
||||
| Voice Mode | Push-to-Talk 语音输入 | [文档](https://ccb.agent-aura.top/docs/features/voice-mode) |
|
||||
| Computer Use | 屏幕截图、键鼠控制 | [文档](https://ccb.agent-aura.top/docs/features/computer-use) |
|
||||
| Chrome Use | 浏览器自动化、表单填写、数据抓取 | [自托管](https://ccb.agent-aura.top/docs/features/chrome-use-mcp) [原生版](https://ccb.agent-aura.top/docs/features/claude-in-chrome-mcp) |
|
||||
| Sentry | 企业级错误追踪 | [文档](https://ccb.agent-aura.top/docs/internals/sentry-setup) |
|
||||
| GrowthBook | 企业级特性开关 | [文档](https://ccb.agent-aura.top/docs/internals/growthbook-adapter) |
|
||||
| /dream 记忆整理 | 自动整理和优化记忆文件 | [文档](https://ccb.agent-aura.top/docs/features/auto-dream) |
|
||||
|
||||
- 🚀 [想要启动项目](#快速开始源码版)
|
||||
- 🐛 [想要调试项目](#vs-code-调试)
|
||||
- 📖 [想要学习项目](#teach-me-学习项目)
|
||||
|
||||
|
||||
## ⚡ 快速开始(安装版)
|
||||
|
||||
不用克隆仓库, 从 NPM 下载后, 直接使用
|
||||
|
||||
```sh
|
||||
bun i -g claude-code-best
|
||||
bun pm -g trust claude-code-best
|
||||
ccb # 直接打开 claude code
|
||||
```
|
||||
npm i -g claude-code-best
|
||||
|
||||
⚠️ 国内对 github 网络较差的, 需要先设置这个环境变量
|
||||
# bun 安装比较多问题, 推荐 npm 装
|
||||
# bun i -g claude-code-best
|
||||
# bun pm -g trust claude-code-best @claude-code-best/mcp-chrome-bridge
|
||||
|
||||
```bash
|
||||
DEFAULT_RELEASE_BASE=https://ghproxy.net/https://github.com/microsoft/ripgrep-prebuilt/releases/download/v15.0.1
|
||||
ccb # 以 nodejs 打开 claude code
|
||||
ccb-bun # 以 bun 形态打开
|
||||
ccb update # 更新到最新版本
|
||||
CLAUDE_BRIDGE_BASE_URL=https://remote-control.claude-code-best.win/ CLAUDE_BRIDGE_OAUTH_TOKEN=test-my-key ccb --remote-control # 我们有自部署的远程控制
|
||||
```
|
||||
|
||||
## ⚡ 快速开始(源码版)
|
||||
@@ -54,12 +68,6 @@ DEFAULT_RELEASE_BASE=https://ghproxy.net/https://github.com/microsoft/ripgrep-pr
|
||||
bun install
|
||||
```
|
||||
|
||||
⚠️ 国内对 github 网络较差的,可以使用这个环境变量
|
||||
|
||||
```bash
|
||||
DEFAULT_RELEASE_BASE=https://ghproxy.net/https://github.com/microsoft/ripgrep-prebuilt/releases/download/v15.0.1
|
||||
```
|
||||
|
||||
### ▶️ 运行
|
||||
|
||||
```bash
|
||||
@@ -83,17 +91,17 @@ bun run build
|
||||
|
||||
需要填写的字段:
|
||||
|
||||
| 📌 字段 | 📝 说明 | 💡 示例 |
|
||||
|------|------|------|
|
||||
| Base URL | API 服务地址 | `https://api.example.com/v1` |
|
||||
| API Key | 认证密钥 | `sk-xxx` |
|
||||
| Haiku Model | 快速模型 ID | `claude-haiku-4-5-20251001` |
|
||||
| Sonnet Model | 均衡模型 ID | `claude-sonnet-4-6` |
|
||||
| Opus Model | 高性能模型 ID | `claude-opus-4-6` |
|
||||
|
||||
| 📌 字段 | 📝 说明 | 💡 示例 |
|
||||
| ------------ | ------------- | ---------------------------- |
|
||||
| Base URL | API 服务地址 | `https://api.example.com/v1` |
|
||||
| API Key | 认证密钥 | `sk-xxx` |
|
||||
| Haiku Model | 快速模型 ID | `claude-haiku-4-5-20251001` |
|
||||
| Sonnet Model | 均衡模型 ID | `claude-sonnet-4-6` |
|
||||
| Opus Model | 高性能模型 ID | `claude-opus-4-6` |
|
||||
|
||||
- ⌨️ **Tab / Shift+Tab** 切换字段,**Enter** 确认并跳到下一个,最后一个字段按 Enter 保存
|
||||
|
||||
|
||||
> ℹ️ 支持所有 Anthropic API 兼容服务(如 OpenRouter、AWS Bedrock 代理等),只要接口兼容 Messages API 即可。
|
||||
|
||||
## Feature Flags
|
||||
@@ -113,16 +121,17 @@ TUI (REPL) 模式需要真实终端,无法直接通过 VS Code launch 启动
|
||||
### 步骤
|
||||
|
||||
1. **终端启动 inspect 服务**:
|
||||
|
||||
```bash
|
||||
bun run dev:inspect
|
||||
```
|
||||
会输出类似 `ws://localhost:8888/xxxxxxxx` 的地址。
|
||||
|
||||
会输出类似 `ws://localhost:8888/xxxxxxxx` 的地址。
|
||||
2. **VS Code 附着调试器**:
|
||||
|
||||
- 在 `src/` 文件中打断点
|
||||
- F5 → 选择 **"Attach to Bun (TUI debug)"**
|
||||
|
||||
|
||||
## Teach Me 学习项目
|
||||
|
||||
我们新加了一个 teach-me skills, 通过问答式引导帮你理解这个项目的任何模块。(调整 [sigma skill 而来](https://github.com/sanyuan0704/sanyuan-skills))
|
||||
@@ -149,7 +158,7 @@ TUI (REPL) 模式需要真实终端,无法直接通过 VS Code launch 启动
|
||||
## 相关文档及网站
|
||||
|
||||
- **在线文档(Mintlify)**: [ccb.agent-aura.top](https://ccb.agent-aura.top/) — 文档源码位于 [`docs/`](docs/) 目录,欢迎投稿 PR
|
||||
- **DeepWiki**: <https://deepwiki.com/claude-code-best/claude-code>
|
||||
- **DeepWiki**: [https://deepwiki.com/claude-code-best/claude-code](https://deepwiki.com/claude-code-best/claude-code)
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
75
build.ts
75
build.ts
@@ -1,6 +1,7 @@
|
||||
import { readdir, readFile, writeFile, cp } from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { getMacroDefines } from './scripts/defines.ts'
|
||||
import { DEFAULT_BUILD_FEATURES } from './scripts/defines.ts'
|
||||
|
||||
const outdir = 'dist'
|
||||
|
||||
@@ -8,30 +9,6 @@ const outdir = 'dist'
|
||||
const { rmSync } = await import('fs')
|
||||
rmSync(outdir, { recursive: true, force: true })
|
||||
|
||||
// Default features that match the official CLI build.
|
||||
// Additional features can be enabled via FEATURE_<NAME>=1 env vars.
|
||||
const DEFAULT_BUILD_FEATURES = [
|
||||
'AGENT_TRIGGERS_REMOTE',
|
||||
'CHICAGO_MCP',
|
||||
'VOICE_MODE',
|
||||
'SHOT_STATS',
|
||||
'PROMPT_CACHE_BREAK_DETECTION',
|
||||
'TOKEN_BUDGET',
|
||||
// P0: local features
|
||||
'AGENT_TRIGGERS',
|
||||
'ULTRATHINK',
|
||||
'BUILTIN_EXPLORE_PLAN_AGENTS',
|
||||
'LODESTONE',
|
||||
// P1: API-dependent features
|
||||
'EXTRACT_MEMORIES',
|
||||
'VERIFICATION_AGENT',
|
||||
'KAIROS_BRIEF',
|
||||
'AWAY_SUMMARY',
|
||||
'ULTRAPLAN',
|
||||
// P2: daemon + remote control server
|
||||
'DAEMON',
|
||||
]
|
||||
|
||||
// Collect FEATURE_* env vars → Bun.build features
|
||||
const envFeatures = Object.keys(process.env)
|
||||
.filter(k => k.startsWith('FEATURE_'))
|
||||
@@ -75,8 +52,27 @@ for (const file of files) {
|
||||
}
|
||||
}
|
||||
|
||||
// Also patch unguarded globalThis.Bun destructuring from third-party deps
|
||||
// (e.g. @anthropic-ai/sandbox-runtime) so Node.js doesn't crash at import time.
|
||||
let bunPatched = 0
|
||||
const BUN_DESTRUCTURE = /var \{([^}]+)\} = globalThis\.Bun;?/g
|
||||
const BUN_DESTRUCTURE_SAFE = 'var {$1} = typeof globalThis.Bun !== "undefined" ? globalThis.Bun : {};'
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.js')) continue
|
||||
const filePath = join(outdir, file)
|
||||
const content = await readFile(filePath, 'utf-8')
|
||||
if (BUN_DESTRUCTURE.test(content)) {
|
||||
await writeFile(
|
||||
filePath,
|
||||
content.replace(BUN_DESTRUCTURE, BUN_DESTRUCTURE_SAFE),
|
||||
)
|
||||
bunPatched++
|
||||
}
|
||||
}
|
||||
BUN_DESTRUCTURE.lastIndex = 0
|
||||
|
||||
console.log(
|
||||
`Bundled ${result.outputs.length} files to ${outdir}/ (patched ${patched} for Node.js compat)`,
|
||||
`Bundled ${result.outputs.length} files to ${outdir}/ (patched ${patched} for import.meta.require, ${bunPatched} for Bun destructure)`,
|
||||
)
|
||||
|
||||
// Step 4: Copy native .node addon files (audio-capture)
|
||||
@@ -84,18 +80,17 @@ const vendorDir = join(outdir, 'vendor', 'audio-capture')
|
||||
await cp('vendor/audio-capture', vendorDir, { recursive: true })
|
||||
console.log(`Copied vendor/audio-capture/ → ${vendorDir}/`)
|
||||
|
||||
// Step 5: Bundle download-ripgrep script as standalone JS for postinstall
|
||||
const rgScript = await Bun.build({
|
||||
entrypoints: ['scripts/download-ripgrep.ts'],
|
||||
outdir,
|
||||
target: 'node',
|
||||
})
|
||||
if (!rgScript.success) {
|
||||
console.error('Failed to bundle download-ripgrep script:')
|
||||
for (const log of rgScript.logs) {
|
||||
console.error(log)
|
||||
}
|
||||
// Non-fatal — postinstall fallback to bun run scripts/download-ripgrep.ts
|
||||
} else {
|
||||
console.log(`Bundled download-ripgrep script to ${outdir}/`)
|
||||
}
|
||||
// Step 5: Generate cli-bun and cli-node executable entry points
|
||||
const cliBun = join(outdir, 'cli-bun.js')
|
||||
const cliNode = join(outdir, 'cli-node.js')
|
||||
|
||||
await writeFile(cliBun, '#!/usr/bin/env bun\nimport "./cli.js"\n')
|
||||
|
||||
await writeFile(cliNode, '#!/usr/bin/env node\nimport "./cli.js"\n')
|
||||
|
||||
// Make both executable
|
||||
const { chmodSync } = await import('fs')
|
||||
chmodSync(cliBun, 0o755)
|
||||
chmodSync(cliNode, 0o755)
|
||||
|
||||
console.log(`Generated ${cliBun} (shebang: bun) and ${cliNode} (shebang: node)`)
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.6 MiB |
188
docs.json
Normal file
188
docs.json
Normal file
@@ -0,0 +1,188 @@
|
||||
{
|
||||
"$schema": "https://mintlify.com/docs.json",
|
||||
"theme": "mint",
|
||||
"name": "Claude Code Architecture",
|
||||
"colors": {
|
||||
"primary": "#D97706",
|
||||
"light": "#F59E0B",
|
||||
"dark": "#B45309"
|
||||
},
|
||||
"favicon": "/docs/favicon.svg",
|
||||
"navigation": {
|
||||
"groups": [
|
||||
{
|
||||
"group": "开始",
|
||||
"pages": [
|
||||
{
|
||||
"group": "介绍",
|
||||
"pages": [
|
||||
"docs/introduction/what-is-claude-code",
|
||||
"docs/introduction/why-this-whitepaper",
|
||||
"docs/introduction/architecture-overview"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "对话是如何运转的",
|
||||
"pages": [
|
||||
"docs/conversation/the-loop",
|
||||
"docs/conversation/streaming",
|
||||
"docs/conversation/multi-turn"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "工具:AI 的双手",
|
||||
"pages": [
|
||||
"docs/tools/what-are-tools",
|
||||
"docs/tools/file-operations",
|
||||
"docs/tools/shell-execution",
|
||||
"docs/tools/search-and-navigation",
|
||||
"docs/tools/task-management"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "上下文工程",
|
||||
"pages": [
|
||||
"docs/context/system-prompt",
|
||||
"docs/context/project-memory",
|
||||
"docs/context/compaction",
|
||||
"docs/context/token-budget"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "多 Agent 协作",
|
||||
"pages": [
|
||||
"docs/agent/sub-agents",
|
||||
"docs/agent/worktree-isolation",
|
||||
"docs/agent/coordinator-and-swarm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "可扩展性",
|
||||
"pages": [
|
||||
"docs/extensibility/mcp-protocol",
|
||||
"docs/extensibility/hooks",
|
||||
"docs/extensibility/skills",
|
||||
"docs/extensibility/custom-agents"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "安全与权限",
|
||||
"pages": [
|
||||
"docs/safety/why-safety-matters",
|
||||
"docs/safety/permission-model",
|
||||
"docs/safety/sandbox",
|
||||
"docs/safety/plan-mode",
|
||||
"docs/safety/auto-mode"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "揭秘:隐藏功能与内部机制",
|
||||
"pages": [
|
||||
"docs/internals/three-tier-gating",
|
||||
"docs/internals/feature-flags",
|
||||
"docs/internals/growthbook-ab-testing",
|
||||
"docs/internals/growthbook-adapter",
|
||||
"docs/internals/sentry-setup",
|
||||
"docs/internals/hidden-features",
|
||||
"docs/internals/ant-only-world",
|
||||
"docs/features/debug-mode",
|
||||
"docs/features/buddy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "隐藏功能详解",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent 与协作",
|
||||
"pages": [
|
||||
"docs/features/coordinator-mode",
|
||||
"docs/features/fork-subagent",
|
||||
"docs/features/daemon",
|
||||
"docs/features/teammem"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "运行模式",
|
||||
"pages": [
|
||||
"docs/features/kairos",
|
||||
"docs/features/voice-mode",
|
||||
"docs/features/bridge-mode",
|
||||
"docs/features/remote-control-self-hosting",
|
||||
"docs/features/proactive",
|
||||
"docs/features/ultraplan"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "工具增强",
|
||||
"pages": [
|
||||
"docs/features/mcp-skills",
|
||||
"docs/features/tree-sitter-bash",
|
||||
"docs/features/bash-classifier",
|
||||
"docs/features/web-browser-tool",
|
||||
"docs/features/experimental-skill-search"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "上下文与自动化",
|
||||
"pages": [
|
||||
"docs/features/token-budget",
|
||||
"docs/features/context-collapse",
|
||||
"docs/features/workflow-scripts",
|
||||
"docs/features/auto-dream"
|
||||
]
|
||||
},
|
||||
"docs/features/tier3-stubs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "基础设施与依赖",
|
||||
"pages": [
|
||||
"docs/auto-updater",
|
||||
"docs/lsp-integration",
|
||||
"docs/external-dependencies",
|
||||
"docs/telemetry-remote-config-audit"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"logo": {
|
||||
"light": "/docs/logo/light.svg",
|
||||
"dark": "/docs/logo/dark.svg"
|
||||
},
|
||||
"background": {
|
||||
"color": {
|
||||
"light": "#FFFFFF",
|
||||
"dark": "#0F172A"
|
||||
}
|
||||
},
|
||||
"navbar": {
|
||||
"primary": {
|
||||
"type": "github",
|
||||
"href": "https://github.com/claude-code-best/claude-code"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"prompt": "搜索 Claude Code 架构文档..."
|
||||
},
|
||||
"seo": {
|
||||
"metatags": {
|
||||
"og:image": "https://ccb.agent-aura.top/docs/images/og-cover.png",
|
||||
"twitter:image": "https://ccb.agent-aura.top/docs/images/og-cover.png",
|
||||
"twitter:card": "summary_large_image"
|
||||
},
|
||||
"indexing": "navigable"
|
||||
},
|
||||
"footer": {
|
||||
"socials": {
|
||||
"github": "https://github.com/anthropics/claude-code"
|
||||
}
|
||||
},
|
||||
"redirects": [
|
||||
{
|
||||
"source": "/docs/introduction",
|
||||
"destination": "/docs/introduction/what-is-claude-code"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
# 文档修正计划
|
||||
|
||||
> 目标:补充源码级洞察,让每篇文档从"概念科普"升级为"逆向工程白皮书"水准。
|
||||
|
||||
---
|
||||
|
||||
## 第一梯队:空壳页,需要大幅重写
|
||||
|
||||
### 1. `safety/sandbox.mdx` — 沙箱机制 ✅ DONE
|
||||
|
||||
**现状**:35 行,只列了"文件系统/网络/进程/时间"四个维度,没有任何实现细节。
|
||||
|
||||
**修正方向**:
|
||||
- 补充 macOS `sandbox-exec` 的实际调用方式,展示沙箱 profile 的关键片段
|
||||
- 说明 `getSandboxConfig()` 的判定逻辑:哪些命令走沙箱、哪些跳过
|
||||
- 补充 `dangerouslyDisableSandbox` 参数的设计权衡
|
||||
- 加入 Linux 平台的沙箱差异对比(seatbelt vs namespace)
|
||||
- 展示一次命令执行从权限检查→沙箱包裹→实际执行的完整链路
|
||||
|
||||
---
|
||||
|
||||
### 2. `introduction/what-is-claude-code.mdx` — 什么是 Claude Code ✅ DONE
|
||||
|
||||
**现状**:39 行,纯营销文案,和"普通聊天 AI"的对比表太低级。
|
||||
|
||||
**修正方向**:
|
||||
- 砍掉"能做什么"的泛泛列表,改为一个具体的端到端示例(从用户输入→系统处理→最终输出)
|
||||
- 用一张简化架构图替代文字描述,让读者 30 秒建立直觉
|
||||
- 补充 Claude Code 的技术定位:不是 IDE 插件、不是 Web Chat,而是 terminal-native agentic system
|
||||
- 加入与 Cursor / Copilot / Aider 等工具的定位差异(架构层面而非功能清单)
|
||||
|
||||
---
|
||||
|
||||
### 3. `introduction/why-this-whitepaper.mdx` — 为什么写这份白皮书 ✅ DONE
|
||||
|
||||
**现状**:40 行,全是空话,四张 Card 只是后续章节标题的预告。
|
||||
|
||||
**修正方向**:
|
||||
- 明确定位:这是对 Anthropic 官方 CLI 的逆向工程分析,不是官方文档
|
||||
- 列出逆向过程中发现的 3-5 个最意外/最精妙的设计决策(吊住读者胃口)
|
||||
- 说明白皮书的阅读路线图:推荐的阅读顺序和每个章节解决什么问题
|
||||
- 补充"这份白皮书不是什么"——不是使用教程,不是 API 文档
|
||||
|
||||
---
|
||||
|
||||
### 4. `safety/why-safety-matters.mdx` — 为什么安全至关重要 ✅ DONE
|
||||
|
||||
**现状**:40 行,只列了显而易见的风险,"安全 vs 效率的平衡"只有 3 个 bullet。
|
||||
|
||||
**修正方向**:
|
||||
- 从源码角度展示安全体系的全景图:权限规则 → 沙箱 → Plan Mode → 预算上限 → Hooks 的纵深防御链
|
||||
- 补充 Claude 自身 System Prompt 中的安全指令("执行前确认"、"优先可逆操作"等),展示 AI 端的安全约束
|
||||
- 用真实场景说明"安全 vs 效率"的工程权衡:比如 Read 工具为什么免审批、Bash 工具为什么要逐条确认
|
||||
- 加入 Prompt Injection 防御的简要说明(tool result 中的恶意内容如何被系统标记)
|
||||
|
||||
---
|
||||
|
||||
## 第二梯队:有骨架但太浅,需要补肉
|
||||
|
||||
### 5. `conversation/streaming.mdx` — 流式响应 ✅ DONE
|
||||
|
||||
**现状**:43 行,只说了"流式好"和 3 行 provider 表。
|
||||
|
||||
**修正方向**:
|
||||
- 补充 `BetaRawMessageStreamEvent` 的核心事件类型及其含义
|
||||
- 展示文本 chunk 和 tool_use block 交织的状态机流转
|
||||
- 说明流式中的错误处理:网络断开、API 限流、token 超限时的重试/降级策略
|
||||
- 补充 `processStreamEvents()` 的核心逻辑:如何从事件流中分离出文本、工具调用、usage 统计
|
||||
|
||||
---
|
||||
|
||||
### 6. `tools/search-and-navigation.mdx` — 搜索与导航 ✅ DONE
|
||||
|
||||
**现状**:43 行,只说 Glob 和 Grep 存在。
|
||||
|
||||
**修正方向**:
|
||||
- 补充 ripgrep 二进制的内嵌方式(vendor 目录、平台适配)
|
||||
- 说明搜索结果的 head_limit 默认 250 的设计原因(token 预算)
|
||||
- 展示 ToolSearch 的实现:如何用语义匹配在 50+ 工具(含 MCP)中找到最相关的
|
||||
- 补充 Glob 按修改时间排序的意义:最近修改的文件最可能与当前任务相关
|
||||
|
||||
---
|
||||
|
||||
### 7. `tools/task-management.mdx` — 任务管理 ✅ DONE
|
||||
|
||||
**现状**:50 行,只有流程 Steps 和状态展示的 4 个 bullet。
|
||||
|
||||
**修正方向**:
|
||||
- 补充任务的数据模型:id / subject / description / status / blockedBy / blocks / owner
|
||||
- 说明依赖管理的实现:blockedBy 如何阻止任务被认领、完成一个任务后如何自动解锁下游
|
||||
- 展示任务与 Agent 工具的联动:子 Agent 如何认领任务、报告进度
|
||||
- 补充 activeForm 字段的 UX 设计:进行中任务的 spinner 动画文案
|
||||
|
||||
---
|
||||
|
||||
### 8. `context/token-budget.mdx` — Token 预算管理 ✅ DONE
|
||||
|
||||
**现状**:55 行,预算控制只有 3 张 Card 各一句话。
|
||||
|
||||
**修正方向**:
|
||||
- 补充 `contextWindowTokens` 和 `maxOutputTokens` 的动态计算逻辑
|
||||
- 说明缓存 breakpoint 的放置策略:System Prompt 中不变内容在前、变化内容在后的原因
|
||||
- 展示工具输出截断的具体机制:超长结果如何被 truncate、何时触发 micro-compact
|
||||
- 补充 token 计数的实现:`countTokens` 的调用时机和近似 vs 精确计数的权衡
|
||||
|
||||
---
|
||||
|
||||
### 9. `agent/worktree-isolation.mdx` — Worktree 隔离 ✅ DONE
|
||||
|
||||
**现状**:55 行,只描述了 git worktree 的概念。
|
||||
|
||||
**修正方向**:
|
||||
- 展示 `.claude/worktrees/` 的目录结构和分支命名规则
|
||||
- 说明 worktree 的生命周期:创建时机(`isolation: "worktree"`)→ 子 Agent 执行 → 完成/放弃 → 自动清理
|
||||
- 补充 worktree 与子 Agent 的绑定关系:Agent 结束时如何判断 keep or remove
|
||||
- 加入 EnterWorktree / ExitWorktree 工具的交互设计
|
||||
|
||||
---
|
||||
|
||||
### 10. `extensibility/custom-agents.mdx` — 自定义 Agent ✅ DONE
|
||||
|
||||
**现状**:56 行,只有配置表和示例表。
|
||||
|
||||
**修正方向**:
|
||||
- 展示 agent markdown 文件的完整 frontmatter 格式(name / description / model / allowedTools 等)
|
||||
- 说明 agent 如何被加载和注入 System Prompt:`loadAgentDefinitions()` 的发现和合并逻辑
|
||||
- 展示工具限制的实现:allowedTools 如何过滤工具列表
|
||||
- 补充 agent 与 subagent_type 参数的关联:Agent 工具如何指定使用自定义 Agent
|
||||
@@ -10,13 +10,13 @@ keywords: ["协调者模式", "蜂群模式", "Agent Swarm", "多 Agent 协作",
|
||||
|
||||
| 维度 | Coordinator Mode | Agent Swarms |
|
||||
|------|-----------------|--------------|
|
||||
| **门控** | `feature('COORDINATOR_MODE')` + `CLAUDE_CODE_COORDINATOR_MODE=1` | 任务系统 V2(默认启用) |
|
||||
| **拓扑** | 星型:Coordinator 居中,Worker 外围 | 网状:对等 Agent 共享任务列表 |
|
||||
| **角色** | 明确分工:Coordinator 编排、Worker 执行 | 模糊:每个 Agent 自主认领任务 |
|
||||
| **通信** | `SendMessage` 定向通信 + `<task-notification>` | 任务文件系统 + 邮箱广播 |
|
||||
| **适用** | 需要集中决策的复杂任务 | 并行度高的独立子任务 |
|
||||
| **门控** | `feature('COORDINATOR_MODE')` + `CLAUDE_CODE_COORDINATOR_MODE=1` | `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` 环境变量 |
|
||||
| **拓扑** | 星型:Coordinator 居中,Worker 外围 | 星型+P2P 混合:Team Lead 协调,Teammate 间可直接通信 |
|
||||
| **角色** | 明确分工:Coordinator 编排、Worker 执行 | Team Lead 协调 + Teammate 自主认领任务 |
|
||||
| **通信** | `SendMessage` 定向通信 + `<task-notification>` | Mailbox 消息系统(message / broadcast) |
|
||||
| **适用** | 需要集中决策的复杂任务 | 并行度高、需要 Teammate 间直接协作的任务 |
|
||||
|
||||
两者不是互斥的——Coordinator Mode 可以在 Swarm 架构之上运行,将 Coordinator 作为特殊的 Leader Agent。
|
||||
两者不是互斥的——理论上 Coordinator Mode 可以在 Agent Teams 架构之上运行(概念层叠加,非嵌套团队),将 Coordinator 作为特殊的 Team Lead,但这部分集成(`workerAgent.ts` 中的 `getCoordinatorAgents`)目前为 stub 实现,尚未完整落地。
|
||||
|
||||
## Coordinator Mode:星型编排架构
|
||||
|
||||
@@ -45,7 +45,7 @@ Coordinator 被剥夺了所有"动手"工具,只保留编排能力:
|
||||
| **TaskStop** | 中途停止走错方向的 Worker |
|
||||
| **subscribe_pr_activity** | 订阅 GitHub PR 事件(review comments、CI 结果) |
|
||||
|
||||
Coordinator **不写代码、不读文件、不执行命令**——它只做三件事:理解需求、分配任务、综合结果。
|
||||
Coordinator **不写代码、不读文件、不执行命令**——它的核心职责是:理解需求、分配任务、综合结果,以及在无需工具时直接回答用户问题。
|
||||
|
||||
### Worker 的工具权限
|
||||
|
||||
@@ -53,7 +53,7 @@ Worker 的可用工具由 `getCoordinatorUserContext()`(`coordinatorMode.ts:80
|
||||
|
||||
```typescript
|
||||
// 简化模式下:只有 Bash + Read + Edit
|
||||
const workerTools = isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE')
|
||||
const workerTools = isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)
|
||||
? [BASH_TOOL_NAME, FILE_READ_TOOL_NAME, FILE_EDIT_TOOL_NAME]
|
||||
: Array.from(ASYNC_AGENT_ALLOWED_TOOLS)
|
||||
.filter(name => !INTERNAL_WORKER_TOOLS.has(name))
|
||||
@@ -63,7 +63,7 @@ const workerTools = isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE')
|
||||
|
||||
### Scratchpad:跨 Worker 的共享知识库
|
||||
|
||||
当 `tengu_scratch` feature flag 启用时,Coordinator 拥有一个 Scratchpad 目录:
|
||||
当 `isScratchpadGateEnabled()`(内部检查 `tengu_scratch` feature gate)启用时,Workers 获得一个 Scratchpad 目录,Coordinator 通过其系统上下文知晓该目录的存在:
|
||||
|
||||
```
|
||||
Scratchpad 目录:
|
||||
@@ -113,32 +113,84 @@ Coordinator System Prompt(`coordinatorMode.ts:111-369`,约 260 行)明确
|
||||
|
||||
这是 Coordinator Mode 最核心的设计约束:Coordinator 必须先理解,再分配。
|
||||
|
||||
## Agent Swarms:蜂群式协作
|
||||
## Agent Teams (Swarm):蜂群式协作
|
||||
|
||||
Swarm 模式基于任务系统 V2(详见[任务管理](../tools/task-management.mdx)),核心机制是**共享任务列表 + 竞争认领**:
|
||||
Swarm 模式基于任务系统 V2(详见[任务管理](../tools/task-management.mdx)),核心机制是**共享任务列表 + 竞争认领 + Mailbox 消息系统**:
|
||||
|
||||
### 团队初始化
|
||||
|
||||
```
|
||||
Leader 创建团队(TeamCreateTool)
|
||||
Team Lead 创建团队(TeamCreateTool)
|
||||
↓
|
||||
设置 teamName → setLeaderTeamName()
|
||||
↓
|
||||
所有 teammate 自动获得相同的 taskListId
|
||||
所有 Teammate 自动获得相同的 taskListId
|
||||
↓
|
||||
teammate 启动时:
|
||||
Teammate 启动时:
|
||||
1. CLAUDE_CODE_TASK_LIST_ID 环境变量(显式覆盖)
|
||||
2. teammate 上下文的 teamName(共享 leader 的任务列表)
|
||||
2. Teammate 上下文的 teamName(共享 Lead 的任务列表)
|
||||
3. CLAUDE_CODE_TEAM_NAME 环境变量
|
||||
4. leader 设置的 teamName
|
||||
4. Lead 设置的 teamName
|
||||
5. getSessionId()(兜底)
|
||||
```
|
||||
|
||||
多级优先级确保了 Leader 和所有 Teammate 指向同一个任务列表,无需额外协调。
|
||||
多级优先级确保了 Team Lead 和所有 Teammate 指向同一个任务列表,无需额外协调。
|
||||
|
||||
### 架构组件
|
||||
|
||||
官方 Agent Teams 架构定义了四个核心组件:
|
||||
|
||||
| 组件 | 角色 |
|
||||
|------|------|
|
||||
| **Team Lead** | 创建团队、分配任务、综合结果的主 Claude Code 会话 |
|
||||
| **Teammate** | 独立的 Claude Code 实例,各自拥有独立的上下文窗口 |
|
||||
| **Task List** | 共享的任务列表,Teammate 竞争认领和完成 |
|
||||
| **Mailbox** | 消息系统,支持 Teammate 间直接通信 |
|
||||
|
||||
### Mailbox 消息系统
|
||||
|
||||
官方架构中的 Mailbox 是 Teammate 间通信的核心原语,支持两种消息模式(`broadcast` 模式来自源码推断,官方文档未明确细分):
|
||||
|
||||
| 模式 | 作用 | 场景 |
|
||||
|------|------|------|
|
||||
| **message** | 定向发送给指定 Teammate | 传递具体指令、请求协作 |
|
||||
| **broadcast** | 广播给所有 Teammate | 全局通知、状态同步 |
|
||||
|
||||
Mailbox 的关键特性:
|
||||
- **自动投递**:消息自动送达目标 Teammate 的对话上下文
|
||||
- **空闲通知**(TeammateIdle):Teammate 完成当前任务进入空闲时,自动通过 Mailbox 通知 Team Lead
|
||||
- **直接通信**:与 Coordinator Mode 不同,Teammate 之间可以直接通信,无需经过 Lead 中转
|
||||
|
||||
### Hook 事件
|
||||
|
||||
Agent Teams 提供三个关键 Hook 事件,用于在团队生命周期中注入自定义逻辑:
|
||||
|
||||
| Hook | 触发时机 | 典型用途 |
|
||||
|------|---------|---------|
|
||||
| **TaskCreated** | 新任务添加到任务列表时 | 自动分配、优先级排序 |
|
||||
| **TaskCompleted** | 任务标记为完成时 | 结果通知、依赖解锁 |
|
||||
| **TeammateIdle** | Teammate 完成所有任务进入空闲时 | Lead 重新分配、动态扩缩容 |
|
||||
|
||||
### 限制
|
||||
|
||||
当前 Agent Teams 实现的限制:
|
||||
- **不支持嵌套团队**:Teammate 不能再创建子团队
|
||||
- **每 session 一个团队**:一个会话只能属于一个团队
|
||||
- **Lead 固定**:Team Lead 创建后不可更换
|
||||
- **不支持 in-process Teammate 的会话恢复**:进程重启后 in-process 类型 Teammate 的状态丢失
|
||||
|
||||
### 持久化存储
|
||||
|
||||
团队状态通过文件系统持久化,确保进程重启后可恢复:
|
||||
|
||||
```
|
||||
~/.claude/teams/{team-name}/config.json ← 团队配置
|
||||
~/.claude/tasks/{team-name}/ ← 共享任务列表(文件锁保护)
|
||||
```
|
||||
|
||||
### 任务认领与竞争
|
||||
|
||||
`claimTask()` 是 Swarm 的核心并发原语:
|
||||
`claimTask()` 是 Agent Teams 的核心并发原语:
|
||||
|
||||
```
|
||||
Teammate A 调用 TaskList → 发现 task #3 是 pending
|
||||
@@ -146,7 +198,7 @@ Teammate B 同时发现 task #3 是 pending
|
||||
↓
|
||||
两者同时尝试 TaskUpdate(task #3, {status: "in_progress"})
|
||||
↓
|
||||
文件锁 + 高水位标记保证原子性:
|
||||
文件锁保证原子性:
|
||||
- 第一个写入者获得 owner 锁定
|
||||
- 第二个写入者收到 already_claimed 错误
|
||||
↓
|
||||
@@ -166,8 +218,11 @@ unassignTeammateTasks()
|
||||
→ 扫描任务列表,找到 owner === teammateName 的未完成任务
|
||||
→ 重置为 pending + owner=undefined
|
||||
↓
|
||||
Leader 通过 mailbox 收到通知
|
||||
→ 重新分配或创建新 Teammate
|
||||
Team Lead 感知途径:
|
||||
1. 任务状态变化(pending 重置)—— 通过共享任务列表
|
||||
2. Mailbox 空闲通知(TeammateIdle hook)—— Teammate 停止时自动通知 Lead
|
||||
↓
|
||||
Team Lead 重新分配任务或创建新 Teammate
|
||||
```
|
||||
|
||||
## 任务类型全景
|
||||
@@ -186,11 +241,11 @@ Leader 通过 mailbox 收到通知
|
||||
|
||||
`InProcessTeammateTask` 与 `LocalAgentTask` 的关键差异:前者共享进程的内存空间和基础设施状态(如 MCP 连接池),但有独立的对话上下文和工具权限;后者是完全隔离的子进程,启动开销更大但更安全。
|
||||
|
||||
## Coordinator vs Swarm 的选择
|
||||
## Coordinator vs Agent Teams 的选择
|
||||
|
||||
| 场景 | 推荐模式 | 原因 |
|
||||
|------|---------|------|
|
||||
| "重构认证系统,需要多模块协调" | Coordinator | 需要集中决策,Worker 间有依赖 |
|
||||
| "修复 10 个独立的 lint 警告" | Swarm | 任务独立,可完全并行 |
|
||||
| "修复 10 个独立的 lint 警告" | Agent Teams | 任务独立,Teammate 可完全并行 |
|
||||
| "研究方案 A 和方案 B,然后选一个实现" | Coordinator | 先并行研究,再集中决策 |
|
||||
| "在大仓库中搜索所有 TODO 并分类" | Swarm | 无依赖,各自领任务即可 |
|
||||
| "在大仓库中搜索所有 TODO 并分类" | Agent Teams | 无依赖,各自领任务即可 |
|
||||
|
||||
@@ -13,44 +13,48 @@ keywords: ["子 Agent", "AgentTool", "任务委派", "forkSubagent", "子进程
|
||||
```
|
||||
AI 生成 tool_use: { prompt: "修复 bug", subagent_type: "Explore" }
|
||||
↓
|
||||
AgentTool.call() ← 入口(AgentTool.tsx:239)
|
||||
├── 解析 effectiveType(fork vs 命名 agent)
|
||||
├── filterDeniedAgents() ← 权限过滤
|
||||
AgentTool.call() ← 入口(AgentTool.tsx:387)
|
||||
├── 解析 effectiveType(fork vs 命名 agent vs GP 回退)
|
||||
├── filterDeniedAgents() ← 仅命名 Agent 路径执行:权限过滤
|
||||
├── 检查 requiredMcpServers ← MCP 依赖验证(最长等 30s)
|
||||
├── assembleToolPool(workerPermissionContext) ← 独立组装工具池
|
||||
├── createAgentWorktree() ← 可选 worktree 隔离
|
||||
↓
|
||||
runAgent() ← 核心执行(runAgent.ts:248)
|
||||
runAgent() ← 核心执行(runAgent.ts)
|
||||
├── getAgentSystemPrompt() ← 构建 agent 专属 system prompt
|
||||
├── initializeAgentMcpServers() ← agent 级 MCP 服务器
|
||||
├── executeSubagentStartHooks() ← Hook 注入
|
||||
├── query() ← 进入标准 agentic loop
|
||||
│ ├── 消息流逐条 yield
|
||||
│ └── recordSidechainTranscript() ← JSONL 持久化
|
||||
│ └── recordSidechainTranscript() ← JSONL 持久化(~/.claude/projects/{project}/{session}/subagents/)
|
||||
↓
|
||||
finalizeAgentTool() ← 结果汇总
|
||||
├── 提取文本内容 + usage 统计
|
||||
└── mapToolResultToToolResultBlockParam() ← 格式化为 tool_result
|
||||
```
|
||||
|
||||
## 两种子 Agent 路径:命名 Agent vs Fork
|
||||
## 子 Agent 的三种路径
|
||||
|
||||
`AgentTool.call()` 根据是否提供 `subagent_type` 走两条完全不同的路径(`AgentTool.tsx:322-356`):
|
||||
`AgentTool.call()` 根据 `subagent_type` 参数和 Fork 实验开关,走三条不同的路径:
|
||||
|
||||
| 维度 | 命名 Agent(`subagent_type` 指定) | Fork 子进程(`subagent_type` 省略) |
|
||||
|------|-------------------------------------|--------------------------------------|
|
||||
| **触发条件** | `subagent_type` 有值 | `isForkSubagentEnabled()` && 未指定类型 |
|
||||
| **System Prompt** | Agent 自身的 `getSystemPrompt()` | 继承父 Agent 的完整 System Prompt |
|
||||
| **工具池** | `assembleToolPool()` 独立组装 | 父 Agent 的原始工具池(`useExactTools: true`) |
|
||||
| **上下文** | 仅任务描述 | 父 Agent 的完整对话历史(`forkContextMessages`) |
|
||||
| **模型** | 可独立指定 | 继承父模型(`model: 'inherit'`) |
|
||||
| **权限模式** | Agent 定义的 `permissionMode` | `'bubble'`(上浮到父终端) |
|
||||
| **目的** | 专业任务委派 | Prompt Cache 命中率优化 |
|
||||
| 维度 | 命名 Agent(`subagent_type` 指定) | Fork 子进程(Fork 启用 + 类型省略) | General-purpose 回退(Fork 关闭 + 类型省略) |
|
||||
|------|-------------------------------------|--------------------------------------|---------------------------------------------|
|
||||
| **触发条件** | `subagent_type` 有值 | `isForkSubagentEnabled() === true` 且未指定类型 | `isForkSubagentEnabled() === false` 且未指定类型 |
|
||||
| **System Prompt** | Agent 自身的 `getSystemPrompt()` | 继承父 Agent 的完整 System Prompt | General-purpose Agent 的 `getSystemPrompt()` |
|
||||
| **工具池** | `assembleToolPool()` 独立组装 | 父 Agent 的原始工具池(`useExactTools: true`) | `assembleToolPool()` 独立组装 |
|
||||
| **上下文** | 仅任务描述 | 父 Agent 的完整对话历史(`forkContextMessages`) | 仅任务描述 |
|
||||
| **模型** | 可独立指定 | 继承父模型(`model: 'inherit'`) | 可独立指定 |
|
||||
| **权限模式** | Agent 定义的 `permissionMode` | `'bubble'`(上浮到父终端) | Agent 定义的 `permissionMode` |
|
||||
| **目的** | 专业任务委派 | Prompt Cache 命中率优化 | 通用任务处理 |
|
||||
|
||||
<Note>
|
||||
Fork 实验的门控函数 `isForkSubagentEnabled()` 需要同时满足三个前提:`FORK_SUBAGENT` feature flag 已启用、当前不在 Coordinator 模式中、且不是非交互式会话。任一条件不满足时,省略 `subagent_type` 会静默降级为 General-purpose Agent,而非触发 Fork。
|
||||
</Note>
|
||||
|
||||
Fork 路径的设计核心是 **Prompt Cache 共享**:所有 fork 子进程共享父 Agent 的完整 `assistant` 消息(所有 `tool_use` 块),用相同的占位符 `tool_result` 填充,只有最后一个 `text` 块包含各自的指令。这使得 API 请求前缀字节完全一致,最大化缓存命中。
|
||||
|
||||
```typescript
|
||||
// forkSubagent.ts:142 — 所有 fork 子进程的占位结果
|
||||
// forkSubagent.ts:93 — 所有 fork 子进程的占位结果
|
||||
const FORK_PLACEHOLDER_RESULT = 'Fork started — processing in background'
|
||||
|
||||
// buildForkedMessages() 构建:
|
||||
@@ -59,14 +63,46 @@ const FORK_PLACEHOLDER_RESULT = 'Fork started — processing in background'
|
||||
|
||||
### Fork 递归防护
|
||||
|
||||
Fork 子进程保留 Agent 工具(为了 cache-identical tool defs),但通过两道防线防止递归 fork(`AgentTool.tsx:332`):
|
||||
Fork 子进程保留 Agent 工具(为了 cache-identical tool defs),但通过两道防线防止递归 fork:
|
||||
|
||||
1. **`querySource` 检查**(压缩安全):`context.options.querySource === 'agent:builtin:fork'`
|
||||
2. **消息扫描**(降级兜底):检测 `<fork-boilerplate>` 标签
|
||||
|
||||
## 工具池的独立组装
|
||||
### 模型解析优先级
|
||||
|
||||
子 Agent 不继承父 Agent 的工具限制——它的工具池完全独立组装(`AgentTool.tsx:573-577`):
|
||||
子 Agent 的模型选择遵循严格的优先级链(`src/utils/model/agent.ts`):
|
||||
|
||||
```
|
||||
1. CLAUDE_CODE_SUBAGENT_MODEL 环境变量 ← 全局覆盖
|
||||
↓(未设置时)
|
||||
2. 每次调用的 model 参数 ← AgentTool 入参
|
||||
↓(未指定时)
|
||||
3. Agent 定义的 model frontmatter ← 如 "sonnet", "haiku", "inherit"
|
||||
↓(未定义时)
|
||||
4. 继承父对话模型(conversation model) ← getDefaultSubagentModel() 返回 "inherit"
|
||||
```
|
||||
|
||||
其中 `inherit` 不是简单的模型传递——它经过 `getRuntimeMainLoopModel()` 解析,确保 plan mode 下的 `opusplan→Opus` 等运行时映射正确生效。当 Agent 指定的模型族(如 `haiku`)与父模型同族时,直接复用父模型的精确 ID,避免跨 provider 降级。
|
||||
|
||||
## 命名 Agent 的工具池独立组装
|
||||
|
||||
### 内置 Agent
|
||||
|
||||
系统预定义了几个内置 Agent(`packages/builtin-tools/src/tools/AgentTool/builtInAgents.ts`),各有明确的职责和模型配置:
|
||||
|
||||
| Agent | 模型 | 权限 | 用途 |
|
||||
|-------|------|------|------|
|
||||
| **Explore** | Haiku(轻量快速) | 只读(Read/Grep/Glob) | 代码库搜索与探索 |
|
||||
| **Plan** | 继承父模型 | 只读 | 为 Plan Mode 收集研究信息 |
|
||||
| **General-purpose** | 继承父模型 | 全部工具 | 复杂的通用任务处理 |
|
||||
| **statusline-setup** | 继承父模型 | 受限 | 配置状态栏设置 |
|
||||
| **claude-code-guide** | 继承父模型 | 受限 | 解答 Claude Code 使用问题 |
|
||||
|
||||
用户还可通过 `.claude/agents/` 目录或 settings 定义自定义 Agent,作用域优先级为:managed settings > CLI `--agents` > 项目级 `.claude/agents/` > 用户级 `~/.claude/agents/` > plugin。
|
||||
|
||||
命名 Agent(包括 General-purpose 回退)不继承父 Agent 的工具限制——它的工具池完全独立组装。Fork 子进程则通过 `useExactTools: true` 直接继承父 Agent 的原始工具池,以保持 Prompt Cache 中工具定义的字节一致性。
|
||||
|
||||
命名 Agent 的工具池组装逻辑:
|
||||
|
||||
```typescript
|
||||
const workerPermissionContext = {
|
||||
@@ -83,7 +119,7 @@ const workerTools = assembleToolPool(workerPermissionContext, appState.mcp.tools
|
||||
|
||||
### 工具过滤的 resolveAgentTools
|
||||
|
||||
`runAgent.ts:500-502` 在工具组装后进一步过滤:
|
||||
`runAgent.ts:508` 在工具组装后进一步过滤:
|
||||
|
||||
```typescript
|
||||
const resolvedTools = useExactTools
|
||||
@@ -93,9 +129,20 @@ const resolvedTools = useExactTools
|
||||
|
||||
`resolveAgentTools()` 会根据 Agent 定义中的 `tools` 字段过滤可用工具,将 `['*']` 映射为全量工具。
|
||||
|
||||
### Hook 事件
|
||||
|
||||
子 Agent 支持 Agent 定义 frontmatter 和全局 settings.json 两种级别的 Hook:
|
||||
|
||||
| 来源 | 事件 | 说明 |
|
||||
|------|------|------|
|
||||
| Agent frontmatter `hooks` | `PreToolUse` / `PostToolUse` | 工具调用前后拦截 |
|
||||
| Agent frontmatter `hooks` | `Stop` | 自动转换为 `SubagentStop`(`registerFrontmatterHooks` 传入 `isAgent=true`) |
|
||||
| settings.json | `SubagentStart` | 子 Agent 启动时触发(`executeSubagentStartHooks()`) |
|
||||
| settings.json | `SubagentStop` | 子 Agent 停止时触发 |
|
||||
|
||||
## Worktree 隔离机制
|
||||
|
||||
`isolation: "worktree"` 参数让子 Agent 在独立的 git worktree 中工作(`AgentTool.tsx:590-593`):
|
||||
`isolation: "worktree"` 参数让子 Agent 在独立的 git worktree 中工作(`AgentTool.tsx:863`):
|
||||
|
||||
```typescript
|
||||
const slug = `agent-${earlyAgentId.slice(0, 8)}`
|
||||
@@ -115,24 +162,28 @@ Worktree 生命周期:
|
||||
|
||||
### 异步 Agent(后台运行)
|
||||
|
||||
当 `run_in_background=true` 或 `selectedAgent.background=true` 时,Agent 立即返回 `async_launched` 状态(`AgentTool.tsx:686-764`):
|
||||
当 `run_in_background=true`、`selectedAgent.background=true`、或系统判定应强制异步(如 `assistantForceAsync`、`proactiveModule` 激活)时,Agent 立即返回 `async_launched` 状态:
|
||||
|
||||
```
|
||||
registerAsyncAgent(agentId, ...) ← 注册到 AppState.tasks
|
||||
↓ (void — 火后不管)
|
||||
runAsyncAgentLifecycle() ← 后台执行
|
||||
runAsyncAgentLifecycle() ← 后台执行(agentToolUtils.ts)
|
||||
├── runAgent().onCacheSafeParams ← 进度摘要初始化
|
||||
├── 消息流迭代
|
||||
├── completeAsyncAgent() ← 标记完成
|
||||
├── classifyHandoffIfNeeded() ← 安全检查
|
||||
├── finalizeAgentTool() ← 结果汇总(提取文本 + usage 统计)
|
||||
├── completeAsyncAgent() ← 标记完成(先于通知,确保 TaskOutput 尽快解除阻塞)
|
||||
├── classifyHandoffIfNeeded() ← 安全分类(需 TRANSCRIPT_CLASSIFIER feature)
|
||||
├── getWorktreeResult() ← Worktree 清理(如有隔离)
|
||||
└── enqueueAgentNotification() ← 通知主 Agent
|
||||
```
|
||||
|
||||
如果异步 Agent 提供了 `name` 参数,还会注册到 `agentNameRegistry`,支撑 `SendMessage` 工具通过名称路由到该 Agent。
|
||||
|
||||
异步 Agent 获得独立的 `AbortController`,不与父 Agent 共享——用户按 ESC 取消主线程不会杀掉后台 Agent。
|
||||
|
||||
### 同步 Agent(前台运行)
|
||||
|
||||
同步 Agent 的关键特性是 **可后台化**(`AgentTool.tsx:818-833`):
|
||||
同步 Agent 的关键特性是 **可后台化**(`AgentTool.tsx:1107`):
|
||||
|
||||
```typescript
|
||||
const registration = registerAgentForeground({
|
||||
@@ -154,27 +205,27 @@ const raceResult = await Promise.race([
|
||||
|
||||
## 结果回传格式
|
||||
|
||||
`mapToolResultToToolResultBlockParam()` 根据状态返回不同格式(`AgentTool.tsx:1298-1375`):
|
||||
`mapToolResultToToolResultBlockParam()` 根据状态返回不同格式:
|
||||
|
||||
| 状态 | 返回内容 |
|
||||
|------|---------|
|
||||
| `completed` | 内容 + `<usage>` 块(token/tool_calls/duration) |
|
||||
| `async_launched` | agentId + outputFile 路径 + 操作指引 |
|
||||
| `completed` | 内容 + `<usage>` 块(token/tool_calls/duration);无内容时插入占位文本 `"(Subagent completed but returned no output.)"` 防止模型误判为空 |
|
||||
| `async_launched` | agentId + outputFile 路径 + 操作指引(指引内容取决于 `canReadOutputFile`:有读取权限时提示通过 Read/Bash 查看进度,否则仅告知已启动) |
|
||||
| `teammate_spawned` | agent_id + name + team_name |
|
||||
| `remote_launched` | taskId + sessionUrl + outputFile |
|
||||
|
||||
对于一次性内置 Agent(Explore、Plan),`<usage>` 块被省略——每周节省约 1-2 Gtok 的上下文窗口。
|
||||
对于一次性内置 Agent(Explore、Plan),当**不存在** worktree 隔离时,`<usage>` 块和 agentId 尾部被省略——每周节省约 1-2 Gtok 的上下文窗口。存在 worktree 时仍需返回 `worktreePath` 和 `worktreeBranch` 信息。
|
||||
|
||||
## MCP 依赖的等待机制
|
||||
|
||||
如果 Agent 声明了 `requiredMcpServers`,`call()` 会等待这些服务器连接完成(`AgentTool.tsx:371-410`):
|
||||
如果 Agent 声明了 `requiredMcpServers`,`call()` 会等待这些服务器连接完成(`AgentTool.tsx:576`):
|
||||
|
||||
```typescript
|
||||
const MAX_WAIT_MS = 30_000 // 最长等 30 秒
|
||||
const POLL_INTERVAL_MS = 500 // 每 500ms 轮询
|
||||
```
|
||||
|
||||
早期退出条件:任何必需服务器进入 `failed` 状态时立即停止等待。工具可用性通过 `mcp__` 前缀工具名解析(`mcp__serverName__toolName`)判断。
|
||||
早期退出条件:任何必需服务器进入 `failed` 状态时立即停止等待。工具可用性通过 `mcp__` 前缀工具名解析(`mcp__serverName__toolName`)判断。等待结束后如果仍有必需服务器未就绪,`call()` 会抛出错误并明确列出缺失的服务器名称。
|
||||
|
||||
## 适用场景
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ Worktree 文件统一存放在仓库根目录下的 `.claude/worktrees/`:
|
||||
|
||||
## 创建流程:EnterWorktreeTool
|
||||
|
||||
`EnterWorktreeTool`(`src/tools/EnterWorktreeTool/EnterWorktreeTool.ts`)的执行链路:
|
||||
`EnterWorktreeTool`(`packages/builtin-tools/src/tools/EnterWorktreeTool/EnterWorktreeTool.ts`)的执行链路:
|
||||
|
||||
```
|
||||
EnterWorktreeTool.call({ name? })
|
||||
@@ -83,7 +83,7 @@ EnterWorktreeTool.call({ name? })
|
||||
|
||||
## 退出流程:ExitWorktreeTool
|
||||
|
||||
`ExitWorktreeTool`(`src/tools/ExitWorktreeTool/ExitWorktreeTool.ts`)支持两种退出策略:
|
||||
`ExitWorktreeTool`(`packages/builtin-tools/src/tools/ExitWorktreeTool/ExitWorktreeTool.ts`)支持两种退出策略:
|
||||
|
||||
### keep:保留 worktree
|
||||
|
||||
@@ -143,14 +143,18 @@ call() — 实际执行
|
||||
|
||||
## 与 Agent 工具的联动
|
||||
|
||||
Agent 工具(`AgentTool`)的 `isolation` 参数决定子 Agent 是否在 worktree 中运行:
|
||||
Agent 工具(`AgentTool`)的 `isolation` 参数决定子 Agent 是否在 worktree 中运行。注意 Agent 工具使用**专用的** `createAgentWorktree()`(`src/utils/worktree.ts`),而非用户会话用的 `createWorktreeForSession()`,两者有关键差异:
|
||||
|
||||
- `isolation: "worktree"` → 调用 `createWorktreeForSession()`,子 Agent 在独立 worktree 中执行
|
||||
- 无 isolation → 子 Agent 共享主工作目录
|
||||
| 维度 | `createWorktreeForSession`(用户会话) | `createAgentWorktree`(子 Agent) |
|
||||
|------|---------------------------------------|----------------------------------|
|
||||
| 调用者 | EnterWorktreeTool | AgentTool |
|
||||
| Session 管理 | 设置 `currentWorktreeSession` | **不设置** `currentWorktreeSession` |
|
||||
| 恢复已有 worktree | 直接复用 | 复用并 bump mtime(防止被周期性清理误删) |
|
||||
|
||||
子 Agent 结束时的处理:
|
||||
- **成功**:主 Agent 通过 `ExitWorktreeTool(action: "keep")` 保留 worktree,然后手动合并
|
||||
- **失败/放弃**:主 Agent 通过 `ExitWorktreeTool(action: "remove", discard_changes: true)` 清理
|
||||
子 Agent 结束时的处理由 `cleanupWorktreeIfNeeded()` 自动完成——它不走 `ExitWorktreeTool`(因为 Agent worktree 没有会话状态,`ExitWorktreeTool` 的 `validateInput` 会拒绝):
|
||||
- **有变更** → 保留 worktree,返回 `worktreePath` 供主 Agent 后续合并
|
||||
- **无变更** → 自动删除
|
||||
- **Hook-based** → 始终保留
|
||||
|
||||
## Session 状态持久化
|
||||
|
||||
@@ -168,6 +172,7 @@ Agent 工具(`AgentTool`)的 `isolation` 参数决定子 Agent 是否在 wor
|
||||
tmuxSessionName?: string, // 关联的 tmux session
|
||||
hookBased?: boolean, // 是否由 hook 创建
|
||||
creationDurationMs?: number, // 创建耗时(分析用)
|
||||
usedSparsePaths?: boolean, // 是否使用了 sparse checkout
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ useInterval(checkForUpdates, 30 * 60 * 1000); // 每 30 分钟
|
||||
|
||||
任何更新尝试之前,系统会依次检查:
|
||||
|
||||
1. **自动更新是否被禁用?** — `getAutoUpdaterDisabledReason()`(`src/utils/config.ts:1735`)
|
||||
1. **自动更新是否被禁用?** — `getAutoUpdaterDisabledReason()`(`src/utils/config.ts:1737`)
|
||||
- `NODE_ENV === 'development'`
|
||||
- 设置了 `DISABLE_AUTOUPDATER` 环境变量
|
||||
- 仅限必要流量模式
|
||||
@@ -81,7 +81,7 @@ useInterval(checkForUpdates, 30 * 60 * 1000); // 每 30 分钟
|
||||
|
||||
`src/utils/autoUpdater.ts:70` — `assertMinVersion()`
|
||||
|
||||
从 `src/main.tsx:1775` 在启动时调用:
|
||||
定义于 `src/utils/autoUpdater.ts:70`,设计上在启动时调用(当前未接入启动流程):
|
||||
|
||||
```typescript
|
||||
void assertMinVersion();
|
||||
@@ -200,7 +200,7 @@ Windows 系统使用文件复制而非符号链接。
|
||||
|
||||
**文件**: `src/migrations/migrateAutoUpdatesToSettings.ts`
|
||||
|
||||
一次性将旧版 `globalConfig.autoUpdates = false` 迁移为 settings 中的 `DISABLE_AUTOUPDATER=1` 环境变量。从 `src/main.tsx:325` 在启动时调用。
|
||||
一次性将旧版 `globalConfig.autoUpdates = false` 迁移为 settings 中的 `DISABLE_AUTOUPDATER=1` 环境变量。定义于 `src/migrations/migrateAutoUpdatesToSettings.ts`(当前未接入启动流程)。
|
||||
|
||||
---
|
||||
|
||||
@@ -270,7 +270,7 @@ React hook `useUpdateNotification(updatedVersion)` — 确保每次 semver 变
|
||||
| `src/utils/releaseNotes.ts` | Changelog 获取、缓存与展示 |
|
||||
| `src/utils/semver.ts` | Semver 版本比较(Bun 原生 + npm 回退) |
|
||||
| `src/utils/doctorDiagnostic.ts` | 安装类型检测与健康诊断 |
|
||||
| `src/utils/config.ts:1735` | `getAutoUpdaterDisabledReason()` — 禁用检查逻辑 |
|
||||
| `src/utils/config.ts:1737` | `getAutoUpdaterDisabledReason()` — 禁用检查逻辑 |
|
||||
| `src/migrations/migrateAutoUpdatesToSettings.ts` | 旧版配置迁移 |
|
||||
| `src/screens/Doctor.tsx` | Doctor 命令 UI,展示自动更新状态 |
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ const messagesForCompact = microcompactResult.messages
|
||||
MicroCompact 不压缩整个对话,而是**清除旧工具输出的内容**。它维护一个白名单:
|
||||
|
||||
```typescript
|
||||
// src/services/compact/microCompact.ts:41-48
|
||||
// src/services/compact/microCompact.ts:41-50
|
||||
const COMPACTABLE_TOOLS = new Set([
|
||||
FILE_READ_TOOL_NAME, // 'Read' - 文件读取
|
||||
...SHELL_TOOL_NAMES, // 'Bash' - 命令输出
|
||||
@@ -143,7 +143,7 @@ const stripped2 = stripReinjectedAttachments(stripped) // 移除会被重新注
|
||||
压缩后,系统会从摘要中**重新注入关键上下文**:
|
||||
|
||||
```typescript
|
||||
// compact.ts:124-132
|
||||
// compact.ts:126-134
|
||||
export const POST_COMPACT_TOKEN_BUDGET = 50_000 // 总预算
|
||||
export const POST_COMPACT_MAX_FILES_TO_RESTORE = 5 // 最多恢复 5 个文件
|
||||
export const POST_COMPACT_MAX_TOKENS_PER_FILE = 5_000 // 每文件 5K token
|
||||
|
||||
@@ -39,7 +39,7 @@ Claude Code 的记忆系统是**纯文件**的——没有数据库、没有向
|
||||
`MEMORY.md` 是记忆的入口索引,每次对话都完整加载到上下文中:
|
||||
|
||||
```typescript
|
||||
// memdir.ts:35-38
|
||||
// memdir.ts:34-38
|
||||
export const ENTRYPOINT_NAME = 'MEMORY.md'
|
||||
export const MAX_ENTRYPOINT_LINES = 200
|
||||
export const MAX_ENTRYPOINT_BYTES = 25_000
|
||||
|
||||
@@ -20,12 +20,12 @@ buildSystemPromptBlocks() → TextBlockParam[] (分块 + cache_control 标
|
||||
|
||||
1. **`getSystemPrompt()`**(`src/constants/prompts.ts:444`)—— 收集静态段 + 动态段,插入 `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 分界标记
|
||||
2. **`buildEffectiveSystemPrompt()`**(`src/utils/systemPrompt.ts:41`)—— 按 Override > Coordinator > Agent > Custom > Default 优先级选择
|
||||
3. **`buildSystemPromptBlocks()`**(`src/services/api/claude.ts:3214`)—— 调用 `splitSysPromptPrefix()` 分块,为每个块附加 `cache_control`
|
||||
3. **`buildSystemPromptBlocks()`**(`src/services/api/claude.ts:3279`)—— 调用 `splitSysPromptPrefix()` 分块,为每个块附加 `cache_control`
|
||||
|
||||
## SystemPrompt 品牌类型
|
||||
|
||||
```typescript
|
||||
// src/utils/systemPromptType.ts:8
|
||||
// packages/@ant/model-provider/src/types/systemPrompt.ts:4
|
||||
export type SystemPrompt = readonly string[] & {
|
||||
readonly __brand: 'SystemPrompt'
|
||||
}
|
||||
@@ -43,9 +43,11 @@ export function asSystemPrompt(value: readonly string[]): SystemPrompt {
|
||||
| 阶段 | 内容 | 缓存策略 |
|
||||
|------|------|----------|
|
||||
| **静态区** | Intro Section、System Rules、Doing Tasks、Actions、Using Tools、Tone & Style、Output Efficiency | 可跨组织缓存(`scope: 'global'`) |
|
||||
| **BOUNDARY** | `SYSTEM_PROMPT_DYNAMIC_BOUNDARY = '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'` | 分界标记(不发送给 API) |
|
||||
| **BOUNDARY** | `SYSTEM_PROMPT_DYNAMIC_BOUNDARY = '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'` | 分界标记(不发送给 API,仅用于分割静态区与动态区以实现全局缓存) |
|
||||
| **动态区** | Session Guidance、Memory、Model Override、Env Info、Language、Output Style、MCP Instructions、Scratchpad、FRC、Summarize Tool Results、Token Budget、Brief | 每次会话不同(`scope: 'org'` 或无缓存) |
|
||||
|
||||
> **Boundary 是什么**:它把 System Prompt 分成"不变的静态区"和"因用户/会话而异的动态区"。静态区对所有用户相同,可获得 `scope: 'global'` 跨组织缓存;动态区每次不同,只能 `scope: 'org'` 或不缓存。它本身是一个特殊字符串,在发送给 API 前被移除,AI 永远看不到。
|
||||
|
||||
### 动态区的 Section 注册表
|
||||
|
||||
动态区通过 `systemPromptSection()` / `DANGEROUS_uncachedSystemPromptSection()` 注册,这两个工厂函数定义于 `src/constants/systemPromptSections.ts`:
|
||||
@@ -151,9 +153,7 @@ MCP 工具列表在会话中可能变化(连接/断开),破坏了跨组织
|
||||
|
||||
这是缓存效率最高的模式。`SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 之前的静态内容(Intro、Rules、Tone & Style 等)对所有用户相同,可跨组织缓存。
|
||||
|
||||
### Boundary 插入条件
|
||||
|
||||
`SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 标记**仅在特定条件**下插入:
|
||||
> **Boundary 插入条件**:`SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 标记**仅在特定条件**下插入:
|
||||
|
||||
```typescript
|
||||
// src/utils/betas.ts:226-229
|
||||
@@ -185,7 +185,7 @@ export function shouldUseGlobalCacheScope(): boolean {
|
||||
|
||||
### `getCacheControl()`:TTL 决策
|
||||
|
||||
`src/services/api/claude.ts:359` 生成的 `cache_control` 对象:
|
||||
`src/services/api/claude.ts:348` 生成的 `cache_control` 对象:
|
||||
|
||||
```typescript
|
||||
{
|
||||
@@ -195,14 +195,14 @@ export function shouldUseGlobalCacheScope(): boolean {
|
||||
}
|
||||
```
|
||||
|
||||
1 小时 TTL 的判定逻辑(`should1hCacheTTL()`,第 394 行):
|
||||
1 小时 TTL 的判定逻辑(`should1hCacheTTL()`,第 383 行):
|
||||
- **Bedrock 用户**:通过环境变量 `ENABLE_PROMPT_CACHING_1H_BEDROCK` 启用
|
||||
- **1P 用户**:通过 GrowthBook 配置的 `allowlist` 数组匹配 `querySource`,支持前缀通配符(如 `"repl_main_thread*"`)
|
||||
- **会话级锁定**:资格判定结果在 bootstrap state 中缓存,防止 GrowthBook 配置中途变化导致同一会话内 TTL 不一致
|
||||
|
||||
### 缓存破坏:Session-Specific Guidance 的放置
|
||||
|
||||
`getSessionSpecificGuidanceSection()`(`src/constants/prompts.ts:352`)的内容必须放在 `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` **之后**。因为它包含:
|
||||
`getSessionSpecificGuidanceSection()`(`src/constants/prompts.ts:354`)的内容必须放在 `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` **之后**。因为它包含:
|
||||
- 当前会话的 enabledTools 集合
|
||||
- `isForkSubagentEnabled()` 的运行时判定
|
||||
- `getIsNonInteractiveSession()` 的结果
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
title: "多轮对话管理 - QueryEngine 会话编排与持久化"
|
||||
description: "从源码角度解析 Claude Code 多轮对话管理:QueryEngine 的会话状态机、JSONL transcript 持久化、成本追踪模型和模型热切换机制。"
|
||||
keywords: ["多轮对话", "会话管理", "QueryEngine", "transcript", "成本追踪"]
|
||||
sourceRef: "3ec5675 (2026-04-08)"
|
||||
---
|
||||
|
||||
{/* 本章目标:从源码角度揭示会话编排、持久化存储、成本追踪和模型切换的完整链路 */}
|
||||
@@ -11,15 +12,17 @@ keywords: ["多轮对话", "会话管理", "QueryEngine", "transcript", "成本
|
||||
- **单轮**(一次 Agentic Loop):`query()` 函数的一次完整执行——组装上下文 → 调 API → 处理工具调用 → 循环直到结束
|
||||
- **多轮**(一个 Session):`QueryEngine` 类管理的一次会话——跨越数十轮 `submitMessage()` 调用,持续数小时
|
||||
|
||||
`QueryEngine`(`src/QueryEngine.ts:186`)是单轮 Agentic Loop 之上的**会话编排器**,它管理的状态远不止消息列表:
|
||||
`QueryEngine`(`src/QueryEngine.ts`,类定义)是单轮 Agentic Loop 之上的**会话编排器**,它管理的状态远不止消息列表:
|
||||
|
||||
```
|
||||
QueryEngine 内部状态
|
||||
QueryEngine 内部状态(src/QueryEngine.ts 构造函数)
|
||||
├── mutableMessages: Message[] ← 完整对话历史,跨 turn 累积
|
||||
├── readFileState: FileStateCache ← 已读文件内容缓存,避免重复读取
|
||||
├── totalUsage: NonNullableUsage ← 累计 token 消耗(input/output/cache)
|
||||
├── permissionDenials: SDKPermissionDenial[] ← 权限拒绝记录
|
||||
├── discoveredSkillNames: Set<string> ← 当前 turn 已发现的 skill
|
||||
├── loadedNestedMemoryPaths: Set<string> ← 已加载的嵌套 memory 路径(防重复)
|
||||
├── hasHandledOrphanedPermission: boolean ← 是否已处理孤立权限请求
|
||||
└── abortController: AbortController ← 会话级中断控制
|
||||
```
|
||||
|
||||
@@ -28,29 +31,37 @@ QueryEngine 内部状态
|
||||
每次用户输入一条消息,REPL 或 SDK 调用 `submitMessage()`,它会执行完整的 turn 初始化链路:
|
||||
|
||||
```typescript
|
||||
// src/QueryEngine.ts:211 — 简化的 submitMessage 流程
|
||||
async *submitMessage(prompt, options?): AsyncGenerator<SDKMessage> {
|
||||
// src/QueryEngine.ts — QueryEngine.submitMessage() 简化流程
|
||||
async *submitMessage(
|
||||
prompt: string | ContentBlockParam[],
|
||||
options?: { uuid?: string; isMeta?: boolean },
|
||||
): AsyncGenerator<SDKMessage> {
|
||||
// 1. 清除 turn 级追踪状态
|
||||
this.discoveredSkillNames.clear()
|
||||
|
||||
// 2. 解析模型(用户可能中途切换了模型)
|
||||
const mainLoopModel = userSpecifiedModel
|
||||
? parseUserSpecifiedModel(userSpecifiedModel)
|
||||
|
||||
// 2. 解析模型(用户可能中途通过 setModel() 切换了模型)
|
||||
const mainLoopModel = this.config.userSpecifiedModel
|
||||
? parseUserSpecifiedModel(this.config.userSpecifiedModel)
|
||||
: getMainLoopModel()
|
||||
|
||||
|
||||
// 3. 动态组装 System Prompt(每次 turn 都重新构建)
|
||||
const { defaultSystemPrompt, userContext, systemContext } =
|
||||
await fetchSystemPromptParts({ tools, mainLoopModel, mcpClients })
|
||||
|
||||
|
||||
// 4. 包装权限检查(追踪每次拒绝)
|
||||
const wrappedCanUseTool = async (tool, input, ...) => {
|
||||
const result = await canUseTool(tool, input, ...)
|
||||
if (result.behavior !== 'allow') {
|
||||
this.permissionDenials.push({ tool_name: tool.name, ... })
|
||||
this.permissionDenials.push({
|
||||
type: 'permission_denial',
|
||||
tool_name: sdkCompatToolName(tool.name),
|
||||
tool_use_id: toolUseID,
|
||||
tool_input: input,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
// 5. 调用核心 query() 函数执行 agentic loop
|
||||
yield* query({
|
||||
systemPrompt, messages: this.mutableMessages,
|
||||
@@ -68,36 +79,43 @@ async *submitMessage(prompt, options?): AsyncGenerator<SDKMessage> {
|
||||
### 存储路径
|
||||
|
||||
```
|
||||
~/.claude/projects/<project-hash>/<session-id>.jsonl
|
||||
~/.claude/projects/<sanitized-cwd>/<session-uuid>.jsonl
|
||||
```
|
||||
|
||||
- `project-hash` 由 `getProjectDir(originalCwd)` 生成,同一项目目录的会话归入同一子目录
|
||||
- 路径由 `getProjectDir(originalCwd)` 生成,使用 `sanitizePath()` 将项目目录路径转换为安全的目录名(非 hash),同一项目目录的会话归入同一子目录
|
||||
- 每条记录是一行 JSON(JSONL 格式),支持追加写入而不需要读取-修改-写入整个文件
|
||||
- 读取上限为 50MB(`MAX_TRANSCRIPT_READ_BYTES`),防止超大会话导致 OOM
|
||||
- 读取上限为 50MB(`MAX_TRANSCRIPT_READ_BYTES` 常量,`src/utils/sessionStorage.ts`),防止超大会话导致 OOM
|
||||
|
||||
### Transcript 写入器
|
||||
|
||||
`TranscriptWriter`(`src/utils/sessionStorage.ts:1200+`)是一个写队列,确保并发的消息追加不会互相覆盖:
|
||||
`Project` 类(`src/utils/sessionStorage.ts`,私有类)管理 transcript 的写入。它通过 `writeQueues`(按文件分组的写队列)和 `drainWriteQueue()`(定时批量刷写)确保并发消息追加不会互相覆盖:
|
||||
|
||||
```
|
||||
写入流程:
|
||||
appendEntryToFile(sessionId, entry)
|
||||
写入流程(异步排队路径):
|
||||
recordTranscript(sessionId, entry)
|
||||
↓
|
||||
ensureCurrentSessionFile() ← 懒初始化:首次写入时才创建文件
|
||||
project.enqueueWrite(filePath, entry) ← 入列到 writeQueues
|
||||
↓
|
||||
序列化为 JSON + 换行符
|
||||
scheduleDrain() ← 设置定时器(FLUSH_INTERVAL_MS)
|
||||
↓
|
||||
appendFile(path, line) ← 原子追加
|
||||
drainWriteQueue() ← 按 MAX_CHUNK_BYTES 分批
|
||||
↓ 写入每批
|
||||
appendToFile(path, batchContent) ← 批量追加
|
||||
↓
|
||||
如果配置了远程持久化:
|
||||
persistToRemote(sessionId, entry)
|
||||
├── CCR v2: internalEventWriter('transcript', entry)
|
||||
└── v1 Ingress: sessionIngress.appendSessionLog(...)
|
||||
|
||||
同步直写路径(用于元数据重写等场景):
|
||||
appendEntryToFile(fullPath, entry) ← 同步 appendFileSync
|
||||
↓
|
||||
失败时 mkdir + 重试
|
||||
```
|
||||
|
||||
### 会话恢复链路
|
||||
|
||||
`--resume` 参数触发的恢复流程(`src/main.tsx:3620+`):
|
||||
`--resume` 参数触发的恢复流程(`src/main.tsx` 中 `--resume` 分支):
|
||||
|
||||
```
|
||||
1. 解析 resume 参数:
|
||||
@@ -130,7 +148,7 @@ async *submitMessage(prompt, options?): AsyncGenerator<SDKMessage> {
|
||||
### 累计层:cost-tracker.ts
|
||||
|
||||
```typescript
|
||||
// src/cost-tracker.ts — StoredCostState 数据模型
|
||||
// src/cost-tracker.ts — StoredCostState 类型定义
|
||||
type StoredCostState = {
|
||||
totalCostUSD: number // 累计美元花费
|
||||
totalAPIDuration: number // API 调用总时长(含重试)
|
||||
@@ -138,7 +156,8 @@ type StoredCostState = {
|
||||
totalToolDuration: number // 工具执行总时长
|
||||
totalLinesAdded: number // 代码增加行数
|
||||
totalLinesRemoved: number // 代码删除行数
|
||||
modelUsage: { [modelName: string]: ModelUsage } // 按模型分拆的用量
|
||||
lastDuration: number | undefined // 最近一次会话时长
|
||||
modelUsage: { [modelName: string]: ModelUsage } | undefined // 按模型分拆的用量
|
||||
}
|
||||
```
|
||||
|
||||
@@ -156,18 +175,18 @@ saveCurrentSessionCosts(sessionId)
|
||||
|
||||
### 预算熔断
|
||||
|
||||
`QueryEngineConfig.maxBudgetUsd` 提供了会话级的硬性预算上限。在 REPL 中,当累计费用超过 $5 时(`src/screens/REPL.tsx:2208`),弹出费用提醒对话框——这不是硬性阻断,而是"软提醒"。
|
||||
`QueryEngineConfig.maxBudgetUsd` 提供了会话级的硬性预算上限。在 REPL 中,当累计费用超过 $5 时(`src/screens/REPL.tsx` 中费用阈值 `useEffect`),弹出费用提醒对话框——这不是硬性阻断,而是"软提醒",且仅在 `hasConsoleBillingAccess()` 为 true 时显示。
|
||||
|
||||
## 模型热切换
|
||||
|
||||
在一个会话中切换模型不会丢失对话历史——因为 `mutableMessages` 与模型选择是解耦的:
|
||||
|
||||
```
|
||||
/model sonnet → setMainLoopModelOverride('claude-sonnet-4-20250514')
|
||||
↓
|
||||
/model sonnet → QueryEngine.setModel('claude-sonnet-4-20250514')
|
||||
↓ 实际操作:this.config.userSpecifiedModel = model(QueryEngine.setModel() 方法)
|
||||
下一次 submitMessage() 开始时:
|
||||
↓
|
||||
parseUserSpecifiedModel(userSpecifiedModel)
|
||||
parseUserSpecifiedModel(this.config.userSpecifiedModel)
|
||||
→ 返回新的模型配置
|
||||
↓
|
||||
fetchSystemPromptParts({ mainLoopModel: newModel })
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
title: "流式响应机制 - Claude Code 打字机效果原理"
|
||||
description: "解析 Claude Code 流式响应实现:如何通过 SSE 逐 token 接收 AI 输出,实现实时打字机效果,提升用户等待体验。"
|
||||
keywords: ["流式响应", "SSE", "streaming", "实时输出", "API streaming"]
|
||||
sourceRef: "3ec5675 (2026-04-08)"
|
||||
---
|
||||
|
||||
## 为什么需要流式
|
||||
@@ -31,7 +32,7 @@ message_stop ← 消息结束
|
||||
|
||||
### 事件处理状态机
|
||||
|
||||
`src/services/api/claude.ts:1980-2298` 实现了一个基于 `switch(part.type)` 的状态机:
|
||||
`src/services/api/claude.ts` 中 `queryModelWithStreaming()` 函数的事件处理循环实现了一个基于 `switch(part.type)` 的状态机:
|
||||
|
||||
| 事件类型 | 处理逻辑 | 状态变更 |
|
||||
|----------|----------|----------|
|
||||
@@ -76,7 +77,7 @@ content_block_stop (index=2)
|
||||
`stop_reason` 要等到 `message_delta` 才确定(可能是 `end_turn`、`tool_use`、`max_tokens` 等),所以最后一条消息的 `stop_reason` 是**回写**的:
|
||||
|
||||
```typescript
|
||||
// claude.ts:2246 — 直接属性修改,不用对象替换
|
||||
// claude.ts — stop_reason 回写逻辑(直接属性修改,不用对象替换)
|
||||
// 因为 transcript 写队列持有 message.message 的引用
|
||||
const lastMsg = newMessages.at(-1)
|
||||
if (lastMsg) {
|
||||
@@ -89,16 +90,21 @@ if (lastMsg) {
|
||||
|
||||
### 网络断开
|
||||
|
||||
流式连接依赖 SSE(Server-Sent Events)。当连接中断时:
|
||||
流式连接依赖 SSE(Server-Sent Events)。当连接中断时,系统有两层检测机制:
|
||||
|
||||
1. **Stream idle watchdog**:定时检测事件间隔,超过阈值(stall)触发告警和重试
|
||||
2. **Stream abort**:如果 watchdog 检测到长时间无事件,抛出错误进入重试流程
|
||||
3. **非流式降级**:作为最后手段,回退到非流式请求(一次性获取完整响应)
|
||||
1. **被动停滞检测**(`src/services/api/claude.ts` 中 stall 检测逻辑):当下一个事件到达时,计算与上一个事件的时间间隔。超过阈值(30 秒,`STALL_THRESHOLD_MS = 30_000`)记录为一次 stall,累积计数并写入遥测日志。这是被动检测——仅在下一个 chunk 到达时才触发,不会主动中断流。
|
||||
2. **主动空闲超时看门狗**(`src/services/api/claude.ts` 中 `STREAM_IDLE_TIMEOUT_MS` 看门狗逻辑):使用 `setTimeout` 设置 90 秒(可通过 `CLAUDE_STREAM_IDLE_TIMEOUT_MS` 环境变量覆盖)的硬性超时。如果在此期间没有收到任何事件,主动终止流并抛出错误进入重试流程。
|
||||
3. **非流式降级**:作为最后手段,设置 `didFallBackToNonStreaming` 标志,通过 `executeNonStreamingRequest()` 回退到非流式请求(一次性获取完整响应)。
|
||||
|
||||
```typescript
|
||||
// claude.ts:2338-2355 — 检测空流
|
||||
// 1. 完全没有事件 → 代理返回了非 SSE 响应
|
||||
// 2. 有 message_start 但没有 content_block_stop → 流被截断
|
||||
// claude.ts — 被动停滞检测
|
||||
const STALL_THRESHOLD_MS = 30_000 // 30 秒无事件视为停滞
|
||||
let totalStallTime = 0
|
||||
let stallCount = 0
|
||||
|
||||
// claude.ts — 主动空闲超时
|
||||
const STREAM_IDLE_TIMEOUT_MS =
|
||||
parseInt(process.env.CLAUDE_STREAM_IDLE_TIMEOUT_MS || '', 10) || 90_000
|
||||
```
|
||||
|
||||
### API 限流
|
||||
@@ -118,7 +124,7 @@ if (lastMsg) {
|
||||
| **上下文窗口超限** | `model_context_window_exceeded` | 触发 compaction 压缩对话历史后重试 |
|
||||
|
||||
```typescript
|
||||
// claude.ts:2267-2293
|
||||
// claude.ts — stop_reason 处理
|
||||
if (stopReason === 'max_tokens') {
|
||||
yield createAssistantAPIErrorMessage({ error: 'max_output_tokens', ... })
|
||||
}
|
||||
@@ -133,8 +139,8 @@ if (stopReason === 'model_context_window_exceeded') {
|
||||
系统持续监控事件到达间隔,检测"停滞"(stall):
|
||||
|
||||
```typescript
|
||||
// claude.ts:1940-1966
|
||||
const STALL_THRESHOLD_MS = 10_000 // 10 秒无事件视为停滞
|
||||
// claude.ts — stall 检测逻辑
|
||||
const STALL_THRESHOLD_MS = 30_000 // 30 秒无事件视为停滞
|
||||
if (timeSinceLastEvent > STALL_THRESHOLD_MS) {
|
||||
stallCount++
|
||||
totalStallTime += timeSinceLastEvent
|
||||
@@ -142,7 +148,7 @@ if (timeSinceLastEvent > STALL_THRESHOLD_MS) {
|
||||
}
|
||||
```
|
||||
|
||||
多个 stall 累积后,watchdog 可能决定中断流并触发重试。
|
||||
这是**被动检测**——仅在下一个 chunk 到达时才触发比较。与之互补的是 90 秒主动空闲超时看门狗(`STREAM_IDLE_TIMEOUT_MS`),会直接中断长时间无响应的流。
|
||||
|
||||
## 工具执行的流式反馈
|
||||
|
||||
@@ -161,10 +167,13 @@ UI 层通过 `useToolCallProgress` hook 实时展示命令输出,而不是等
|
||||
|
||||
| Provider | 流式协议 | 特殊处理 |
|
||||
|----------|----------|----------|
|
||||
| **Anthropic Direct** | 原生 SSE | 延迟最低,TTFT 最快 |
|
||||
| **firstParty** (Anthropic Direct) | 原生 SSE | 延迟最低,TTFT 最快 |
|
||||
| **AWS Bedrock** | AWS SDK 流式接口 | 需要额外的 beta header 和认证 |
|
||||
| **Google Vertex** | gRPC → 事件流 | 通过 `getMergedBetas()` 适配 |
|
||||
| **Azure** | Anthropic 兼容 API | 自定义 base URL |
|
||||
| **foundry** | Anthropic 兼容 API | 内部部署 |
|
||||
| **openai** | OpenAI 流式适配器 | 转换为 Anthropic 内部格式 |
|
||||
| **gemini** | Gemini 流式适配器 | 转换为 Anthropic 内部格式 |
|
||||
| **grok** (xAI) | Grok 流式适配器 | 转换为 Anthropic 内部格式 |
|
||||
|
||||
所有 Provider 通过统一的 `Stream<BetaRawMessageStreamEvent>` 抽象层屏蔽差异。上层代码(QueryEngine、REPL)不需要关心底层用的是哪个 Provider。
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
title: "Agentic Loop:AI 自主循环的核心机制"
|
||||
description: "深入解析 Claude Code 的 query() 异步生成器循环——从流式 API 调用、工具并行执行、上下文压缩、错误恢复到终止条件的完整状态机,基于 src/query.ts 的源码级分析。"
|
||||
keywords: ["Agentic Loop", "query loop", "tool_use", "状态机", "auto-compact", "streaming", "recovery"]
|
||||
sourceRef: "3ec5675 (2026-04-08)"
|
||||
---
|
||||
|
||||
{/* 本章目标:基于 src/query.ts 揭示 Agentic Loop 的完整状态机 */}
|
||||
@@ -11,7 +12,7 @@ keywords: ["Agentic Loop", "query loop", "tool_use", "状态机", "auto-compact"
|
||||
传统聊天机器人:你问一句,它答一句。
|
||||
Claude Code 不一样:你说一个需求,它可能连续执行十几步操作才给你最终结果。
|
||||
|
||||
这背后的机制叫做 **Agentic Loop**(智能体循环),核心实现在 `src/query.ts` 的 `queryLoop()` 异步生成器函数(第 241 行)。它是一个 `while(true)` 无限循环,每次迭代代表一次"思考→行动→观察"周期。
|
||||
这背后的机制叫做 **Agentic Loop**(智能体循环),核心实现在 `src/query.ts` 的 `queryLoop()` 异步生成器函数。它是一个 `while(true)` 无限循环,每次迭代代表一次"思考→行动→观察"周期。
|
||||
|
||||
<Frame caption="Agentic Loop 循环示意">
|
||||
<img src="/docs/images/agentic-loop.png" alt="Agentic Loop 循环图" />
|
||||
@@ -19,7 +20,7 @@ Claude Code 不一样:你说一个需求,它可能连续执行十几步操
|
||||
|
||||
## 循环的完整结构
|
||||
|
||||
`queryLoop()` 的每次迭代(`src/query.ts:307` `while(true)`)包含以下阶段:
|
||||
`queryLoop()` 的每次迭代(`src/query.ts` 中 `while(true)` 主循环)包含以下阶段:
|
||||
|
||||
### 阶段 1:上下文预处理(Pre-Processing Pipeline)
|
||||
|
||||
@@ -39,7 +40,7 @@ messagesForQuery(处理后的消息)→ 发往 API
|
||||
|
||||
### 阶段 2:流式 API 调用(Streaming Loop)
|
||||
|
||||
`deps.callModel()` 发起流式请求(第 659 行),返回一个 AsyncGenerator。在流式过程中:
|
||||
`deps.callModel()` 发起流式请求(`src/query.ts` 中 `attemptWithFallback` 循环内),返回一个 AsyncGenerator。在流式过程中:
|
||||
|
||||
- **AssistantMessage** 被收集到 `assistantMessages[]` 数组
|
||||
- **tool_use 块** 被提取到 `toolUseBlocks[]`,设置 `needsFollowUp = true`
|
||||
@@ -47,8 +48,8 @@ messagesForQuery(处理后的消息)→ 发往 API
|
||||
- 可恢复的错误(prompt-too-long、max-output-tokens)被**暂扣**(withheld),先尝试恢复
|
||||
|
||||
流式回调中的关键守卫:
|
||||
- `backfillObservableInput()`(第 763 行)—— 为 tool_use 块回填可观察字段(如文件路径展开),但只在添加了新字段时才克隆消息,避免破坏 prompt cache 的字节一致性
|
||||
- 流式降级检测——如果 `streamingFallbackOccured`,已收集的消息被标记为 tombstone(第 717 行),清空后重试
|
||||
- `backfillObservableInput()` —— 为 tool_use 块回填可观察字段(如文件路径展开),但只在添加了新字段时才克隆消息,避免破坏 prompt cache 的字节一致性
|
||||
- 流式降级检测——如果 `streamingFallbackOccured`,已收集的消息被标记为 tombstone,清空后重试
|
||||
|
||||
### 阶段 3:工具执行(Tool Execution)
|
||||
|
||||
@@ -67,42 +68,50 @@ const toolUpdates = streamingToolExecutor
|
||||
|
||||
每次迭代结束时,根据条件决定 `return`(终止)或 `continue`(继续):
|
||||
|
||||
## 7 种终止条件(源码级)
|
||||
## 终止条件(源码级)
|
||||
|
||||
循环有多种终止路径,按触发时机排列:
|
||||
|
||||
| 终止原因 | 触发位置 | 机制 |
|
||||
|----------|---------|------|
|
||||
| **completed** | 第 1360 行 | AI 未发出 tool_use → `needsFollowUp = false` → 经过 stop hooks → 返回 |
|
||||
| **blocking_limit** | 第 646 行 | Token 计数超过硬限制(非 autocompact 模式)→ 生成 PTL 错误消息 → 返回 |
|
||||
| **aborted_streaming** | 第 1054 行 | `abortController.signal.aborted` → 为未完成的 tool_use 生成合成 tool_result → 返回 |
|
||||
| **model_error** | 第 999 行 | `callModel()` 抛出异常 → 生成错误消息 → 返回 |
|
||||
| **prompt_too_long** | 第 1178 行 | 413 错误且 reactive compact 无法恢复 → 暂扣的错误消息被释放 → 返回 |
|
||||
| **image_error** | 第 980/1178 行 | 图片尺寸/大小错误 → 直接返回 |
|
||||
| **stop_hook_prevented** | 第 1282 行 | Stop hook 返回 `preventContinuation: true` → 返回 |
|
||||
| **blocking_limit** | 第 686 行 | Token 计数超过硬限制(非 autocompact 模式)→ 生成 PTL 错误消息 → 返回 |
|
||||
| **image_error** | 第 1021 行 | `ImageSizeError` / `ImageResizeError` 异常 → 直接返回 |
|
||||
| **model_error** | 第 1040 行 | `callModel()` 抛出不可恢复异常 → 生成错误消息 → 返回 |
|
||||
| **aborted_streaming** | 第 1095 行 | `abortController.signal.aborted`(流式阶段)→ 为未完成的 tool_use 生成合成 tool_result → 返回 |
|
||||
| **prompt_too_long** | 第 1219/1226 行 | 413 错误且 reactive compact 无法恢复 → 暂扣的错误消息被释放 → 返回 |
|
||||
| **completed** | 第 1308 行 | API 错误(限流、认证失败等)导致无法继续 → 返回 |
|
||||
| **stop_hook_prevented** | 第 1323 行 | Stop hook 返回 `preventContinuation: true` → 返回 |
|
||||
| **completed** | 第 1401 行 | 正常完成:AI 未发出 tool_use → `needsFollowUp = false` → 经过 stop hooks → 返回 |
|
||||
| **aborted_tools** | 第 1559 行 | `abortController.signal.aborted`(工具执行阶段)→ 返回 |
|
||||
| **hook_stopped** | 第 1564 行 | 工具执行期间 hook 返回 `shouldPreventContinuation` → 返回 |
|
||||
| **max_turns** | 第 1755 行 | 轮次计数超过 `maxTurns` 限制 → 返回 |
|
||||
|
||||
## 4 种继续条件(恢复路径)
|
||||
## 继续条件(恢复路径)
|
||||
|
||||
循环不仅是一个简单的"有 tool_use 就继续",它还包含多种恢复/重试路径:
|
||||
|
||||
### 1. 正常工具循环
|
||||
`needsFollowUp = true` → 执行工具 → 新消息追加到 `messagesForQuery` → `continue`
|
||||
### 1. 正常工具循环(`next_turn`)
|
||||
`needsFollowUp = true` → 执行工具 → 新消息追加到 `messagesForQuery` → state 重新赋值 → `continue`
|
||||
|
||||
### 2. max_output_tokens 恢复(第 1191-1255 行)
|
||||
当 AI 输出被截断时(`apiError === 'max_output_tokens'`):
|
||||
- **首次**:尝试将 `maxOutputTokens` 从默认值提升到 `ESCALATED_MAX_TOKENS`(64K),无 meta 消息,静默重试
|
||||
- **后续**:注入恢复消息"Output token limit hit. Resume directly...",最多重试 `MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3` 次
|
||||
- 恢复耗尽后,暂扣的错误消息被释放
|
||||
### 2. max_output_tokens 恢复(`max_output_tokens_escalate` / `max_output_tokens_recovery`)
|
||||
当 AI 输出被截断时(`apiError === 'max_output_tokens'`),分两阶段恢复:
|
||||
- **提升阶段**(`max_output_tokens_escalate`):首次截断时,将 `maxOutputTokens` 从默认值提升到 `ESCALATED_MAX_TOKENS`(64K)。静默重试,不注入 meta 消息。
|
||||
- **恢复阶段**(`max_output_tokens_recovery`):提升后仍然截断时,注入恢复消息"Output token limit hit. Resume directly...",最多重试 `MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3` 次。恢复耗尽后,暂扣的错误消息被释放。
|
||||
|
||||
### 3. Prompt-Too-Long 恢复(第 1088-1186 行)
|
||||
当遇到 413 错误时,有两个恢复阶段:
|
||||
- **Context Collapse Drain**(第 1097 行):提交所有已暂存的折叠,释放空间后重试。如果上一轮已经是 collapse_drain_retry 则跳过
|
||||
- **Reactive Compact**(第 1123 行):触发即时压缩,生成摘要后重试。`hasAttemptedReactiveCompact` 防止无限循环
|
||||
### 3. Prompt-Too-Long 恢复(`collapse_drain_retry` / `reactive_compact_retry`)
|
||||
当遇到 413 错误时,按优先级尝试两种压缩策略:
|
||||
- **Context Collapse Drain**(`collapse_drain_retry`):提交所有已暂存的折叠(collapse),释放空间后重试。如果上一轮已经是 `collapse_drain_retry` 则跳过,避免无限循环。
|
||||
- **Reactive Compact**(`reactive_compact_retry`):如果 collapse drain 无法恢复,触发即时压缩(reactive compact),生成摘要后重试。`hasAttemptedReactiveCompact` 标志防止无限循环。
|
||||
|
||||
### 4. Stop Hook 阻塞重试(第 1285-1308 行)
|
||||
### 4. Stop Hook 阻塞重试(`stop_hook_blocking`)
|
||||
Stop hook 可以注入阻塞错误消息,强制 AI 重新思考。新的消息(包含阻塞错误)被追加到对话中,`stopHookActive = true`,进入下一轮迭代。
|
||||
|
||||
### 5. Token Budget 继续提示(`token_budget_continuation`)
|
||||
当 `TOKEN_BUDGET` feature 启用时,如果 token 消耗达到阈值但未超出预算,注入 nudge 消息让 AI 加速收尾,然后继续。
|
||||
|
||||
## 模型降级(Fallback)
|
||||
|
||||
当主模型不可用时(`FallbackTriggeredError`,第 897 行):
|
||||
当主模型不可用时(`FallbackTriggeredError`,`src/query.ts` 中 `attemptWithFallback` 循环的 catch 分支):
|
||||
|
||||
1. 已收集的 `assistantMessages` 被清空,tool_use 块收到合成 tool_result:"Model fallback triggered"
|
||||
2. 思维签名块被移除(`stripSignatureBlocks`)—— 因为思维签名与模型绑定,跨模型回放会 400
|
||||
@@ -112,13 +121,14 @@ Stop hook 可以注入阻塞错误消息,强制 AI 重新思考。新的消息
|
||||
|
||||
## 状态机:State 对象
|
||||
|
||||
每次迭代的状态通过 `State` 类型(第 204 行)传递:
|
||||
每次迭代的状态通过 `State` 类型(`src/query.ts`,类型定义)传递:
|
||||
|
||||
```typescript
|
||||
// src/query.ts — State 类型定义
|
||||
type State = {
|
||||
messages: Message[] // 当前对话消息
|
||||
toolUseContext: ToolUseContext // 工具上下文(含权限)
|
||||
autoCompactTracking: AutoCompactTrackingState // 压缩跟踪
|
||||
autoCompactTracking: AutoCompactTrackingState | undefined // 压缩跟踪
|
||||
maxOutputTokensRecoveryCount: number // 输出截断恢复计数
|
||||
hasAttemptedReactiveCompact: boolean // 是否已尝试即时压缩
|
||||
maxOutputTokensOverride: number | undefined // 输出 token 上限覆盖
|
||||
@@ -133,7 +143,7 @@ type State = {
|
||||
|
||||
## Token Budget(实验性)
|
||||
|
||||
当 `TOKEN_BUDGET` feature 启用时(第 1311 行),循环在终止前会检查 token 消耗:
|
||||
当 `TOKEN_BUDGET` feature 启用时(`src/query.ts` 中 `!needsFollowUp` 分支内的预算检查逻辑),循环在终止前会检查 token 消耗:
|
||||
|
||||
- **continuation**:未达到预算但超过阈值 → 注入 nudge 消息,让 AI 加速收尾
|
||||
- **diminishing_returns**:检测到收益递减 → 提前终止
|
||||
@@ -148,7 +158,7 @@ type State = {
|
||||
- **每一步都产生真实信息**:`runTools()` 返回的 `toolResults` 是 API 不可能预知的——命令输出、文件内容、错误信息
|
||||
- **动态上下文管理**:每轮迭代前都重新评估压缩需求(autocompact → microcompact → snip),基于最新的 token 计数
|
||||
- **错误即时恢复**:工具失败不需要推倒重来——stop hook 可以注入阻塞错误让 AI 修正策略
|
||||
- **用户可控**:`abortController.signal` 在循环的多个检查点被检测(第 1018、1048、1488 行),用户按 ESC 可以优雅中断
|
||||
- **用户可控**:`abortController.signal` 在循环的多个检查点被检测(第 1059、1095、1529 行),用户按 ESC 可以优雅中断
|
||||
- **成本控制**:Token Budget 在每轮终止前检查,防止 AI 无效循环
|
||||
|
||||
## 一个完整的迭代示例
|
||||
@@ -157,26 +167,31 @@ type State = {
|
||||
|
||||
```
|
||||
迭代 1: 思考→行动
|
||||
预处理: 无需压缩(上下文很短)
|
||||
预处理管道: applyToolResultBudget → snipCompact(HISTORY_SNIP feature) → microcompact → applyCollapses(CONTEXT_COLLAPSE feature) → autocompact
|
||||
→ 上下文很短,无需压缩
|
||||
API 调用: 返回 tool_use(Glob, "**/*.ts")
|
||||
工具执行: 返回 42 个文件路径
|
||||
→ needsFollowUp = true, continue
|
||||
→ needsFollowUp = true
|
||||
→ transition: { reason: 'next_turn' }, continue
|
||||
|
||||
迭代 2: 思考→行动
|
||||
预处理: 42 个文件结果仍在预算内
|
||||
预处理管道: 42 个文件结果仍在预算内
|
||||
API 调用: 返回 tool_use(Grep, "import.*from")
|
||||
工具执行: 在 15 个文件中找到 120 条 import
|
||||
→ needsFollowUp = true, continue
|
||||
→ needsFollowUp = true
|
||||
→ transition: { reason: 'next_turn' }, continue
|
||||
|
||||
迭代 3: 思考→行动(多轮)
|
||||
预处理: 120 条 Grep 结果触发 microcompact → 摘要化
|
||||
预处理管道: 120 条 Grep 结果触发 microcompact → 摘要化
|
||||
API 调用: 返回 3 个 tool_use(FileEdit, ...)
|
||||
工具执行: 删除 5 条未使用导入
|
||||
→ needsFollowUp = true, continue
|
||||
→ needsFollowUp = true
|
||||
→ transition: { reason: 'next_turn' }, continue
|
||||
|
||||
迭代 4: 总结
|
||||
API 调用: 返回纯文本"已清理 3 个文件中的 5 条未使用导入"
|
||||
→ needsFollowUp = false
|
||||
→ Stop hooks 通过
|
||||
→ Token Budget 检查通过(如果启用)
|
||||
→ return { reason: 'completed' }
|
||||
```
|
||||
|
||||
17
docs/diagrams/agent-loop-simple.mmd
Normal file
17
docs/diagrams/agent-loop-simple.mmd
Normal file
@@ -0,0 +1,17 @@
|
||||
flowchart TB
|
||||
START((输入)) --> CTX["Context 管理"]
|
||||
CTX --> LLM["LLM 流式输出"]
|
||||
LLM --> TC{tool_use?}
|
||||
|
||||
TC --> |是| EXEC["执行工具"]
|
||||
EXEC --> CTX
|
||||
|
||||
TC --> |否| DONE((完成))
|
||||
|
||||
classDef proc fill:#eef,stroke:#66c,color:#224
|
||||
classDef decision fill:#fee,stroke:#c66,color:#422
|
||||
classDef io fill:#eff,stroke:#6cc,color:#244
|
||||
|
||||
class CTX,LLM,EXEC proc
|
||||
class TC decision
|
||||
class START,DONE io
|
||||
40
docs/diagrams/agent-loop.mmd
Normal file
40
docs/diagrams/agent-loop.mmd
Normal file
@@ -0,0 +1,40 @@
|
||||
flowchart TB
|
||||
START((输入)) --> CTX["Context 管理"]
|
||||
CTX --> PRE["Pre-sampling Hook"]
|
||||
PRE --> LLM["LLM 流式输出"]
|
||||
LLM --> TC{tool_use?}
|
||||
|
||||
TC --> |是| PERM{需权限?}
|
||||
PERM --> |是| USER["👤 用户审批"]
|
||||
USER --> |allow| TOOL_PRE
|
||||
USER --> |deny| DENIED["拒绝"]
|
||||
PERM --> |否| TOOL_PRE["Pre-tool Hook"]
|
||||
TOOL_PRE --> EXEC["并发执行工具"]
|
||||
EXEC --> TOOL_POST["Post-tool Hook"]
|
||||
TOOL_POST --> CTX
|
||||
DENIED --> CTX
|
||||
|
||||
TC --> |否| POST["Post-sampling Hook"]
|
||||
POST --> STOP{"Stop Hook"}
|
||||
STOP --> |不通过| CTX
|
||||
STOP --> |通过| BUDGET{"Token Budget"}
|
||||
BUDGET --> |继续| CTX
|
||||
BUDGET --> |完成| DONE((完成))
|
||||
|
||||
subgraph SUB["子 Agent"]
|
||||
FORK["AgentTool"] --> RECURSE["递归调用"]
|
||||
end
|
||||
|
||||
EXEC -.-> FORK
|
||||
|
||||
classDef proc fill:#eef,stroke:#66c,color:#224
|
||||
classDef decision fill:#fee,stroke:#c66,color:#422
|
||||
classDef hook fill:#ffe,stroke:#cc6,color:#442
|
||||
classDef io fill:#eff,stroke:#6cc,color:#244
|
||||
classDef sub fill:#efe,stroke:#6a6,color:#242
|
||||
|
||||
class CTX,LLM,EXEC proc
|
||||
class TC,PERM,STOP,BUDGET decision
|
||||
class PRE,TOOL_PRE,TOOL_POST,POST hook
|
||||
class START,DONE,USER,DENIED io
|
||||
class FORK,RECURSE sub
|
||||
@@ -12,7 +12,7 @@ Claude Code 的 Agent 不仅仅来自用户自定义——系统有三类来源
|
||||
|
||||
| 来源 | 位置 | 优先级 |
|
||||
|------|------|--------|
|
||||
| **Built-in** | `src/tools/AgentTool/built-in/` 硬编码 | 最低(可被覆盖) |
|
||||
| **Built-in** | `packages/builtin-tools/src/tools/AgentTool/built-in/` 硬编码 | 最低(可被覆盖) |
|
||||
| **Plugin** | 通过插件系统注册 | 中 |
|
||||
| **User/Project/Policy** | `.claude/agents/*.md` 或 settings.json | 最高 |
|
||||
|
||||
@@ -127,7 +127,7 @@ color: "blue" # 终端中的 Agent 颜色标识
|
||||
以内置 Explore Agent 为例:
|
||||
|
||||
```typescript
|
||||
// src/tools/AgentTool/built-in/exploreAgent.ts
|
||||
// packages/builtin-tools/src/tools/AgentTool/built-in/exploreAgent.ts
|
||||
disallowedTools: [
|
||||
'Agent', // 不能嵌套调用 Agent
|
||||
'ExitPlanMode', // 不需要 plan mode
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
title: "Hooks 生命周期钩子 - 执行引擎与拦截协议"
|
||||
description: "从源码角度解析 Claude Code Hooks 系统:22 种 Hook 事件、6 种 Hook 类型、同步/异步执行协议、JSON 输出 schema、if 条件匹配、以及 Hook 如何注入上下文和拦截工具调用。"
|
||||
description: "从源码角度解析 Claude Code Hooks 系统:27 种 Hook 事件、6 种 Hook 类型、同步/异步执行协议、JSON 输出 schema、if 条件匹配、以及 Hook 如何注入上下文和拦截工具调用。"
|
||||
keywords: ["Hooks", "生命周期钩子", "拦截器", "PreToolUse", "Hook 协议"]
|
||||
---
|
||||
|
||||
{/* 本章目标:从源码角度揭示 Hook 的执行引擎、匹配机制、返回值协议和生命周期管理 */}
|
||||
|
||||
## 22 种 Hook 事件
|
||||
## 27 种 Hook 事件
|
||||
|
||||
Claude Code 定义了 22 种 Hook 事件(`coreTypes.ts:25-53`),覆盖完整的 Agent 生命周期:
|
||||
Claude Code 定义了 27 种 Hook 事件(`HOOK_EVENTS` 数组,`src/entrypoints/sdk/coreTypes.ts`),覆盖完整的 Agent 生命周期:
|
||||
|
||||
| 阶段 | 事件 | 触发时机 | 匹配字段 |
|
||||
|------|------|---------|---------|
|
||||
@@ -32,6 +32,7 @@ Claude Code 定义了 22 种 Hook 事件(`coreTypes.ts:25-53`),覆盖完
|
||||
| | `TaskCompleted` | 任务完成 | — |
|
||||
| **MCP** | `Elicitation` | MCP 服务器请求用户输入 | `mcp_server_name` |
|
||||
| | `ElicitationResult` | Elicitation 结果返回 | `mcp_server_name` |
|
||||
| **通知** | `Notification` | 系统通知事件 | `notification_type` |
|
||||
| **环境** | `ConfigChange` | 配置变更 | `source` |
|
||||
| | `CwdChanged` | 工作目录变更 | — |
|
||||
| | `FileChanged` | 文件变更 | `file_path` |
|
||||
@@ -40,7 +41,11 @@ Claude Code 定义了 22 种 Hook 事件(`coreTypes.ts:25-53`),覆盖完
|
||||
|
||||
## 6 种 Hook 类型
|
||||
|
||||
Hooks 配置支持 6 种执行方式(`src/types/hooks.ts`):
|
||||
Hooks 配置支持 6 种执行方式,类型定义分布在 3 个文件中:
|
||||
|
||||
- **可持久化类型**(`command`、`prompt`、`agent`、`http`)— Zod schema 定义在 `src/schemas/hooks.ts`,通过 `z.discriminatedUnion('type', [...])` 声明
|
||||
- **callback 类型** — TypeScript 接口定义在 `src/types/hooks.ts`,用于 SDK 注册的内部 JS 函数
|
||||
- **function 类型** — 定义在 `src/utils/hooks/sessionHooks.ts`,用于运行时动态注册的函数 Hook
|
||||
|
||||
| 类型 | 执行方式 | 适用场景 |
|
||||
|------|---------|---------|
|
||||
@@ -53,7 +58,7 @@ Hooks 配置支持 6 种执行方式(`src/types/hooks.ts`):
|
||||
|
||||
## 执行引擎:execCommandHook
|
||||
|
||||
`execCommandHook()`(`src/utils/hooks.ts:829-1417`)是命令型 Hook 的执行核心:
|
||||
`execCommandHook()`(`src/utils/hooks.ts`,`execCommandHook` 函数)是命令型 Hook 的执行核心:
|
||||
|
||||
```
|
||||
execCommandHook(hook, hookEvent, hookName, jsonInput, signal)
|
||||
@@ -75,7 +80,7 @@ execCommandHook(hook, hookEvent, hookName, jsonInput, signal)
|
||||
|
||||
### 异步 Hook 的检测协议
|
||||
|
||||
Hook 进程的 stdout 第一行如果是 `{"async":true}`,系统将其转为后台任务(`hooks.ts:1199-1246`):
|
||||
Hook 进程的 stdout 第一行如果是 `{"async":true}`,系统将其转为后台任务(`isAsyncHookJSONOutput` 检测 + `executeInBackground` 调用):
|
||||
|
||||
```typescript
|
||||
const firstLine = firstLineOf(stdout).trim()
|
||||
@@ -96,7 +101,7 @@ if (isAsyncHookJSONOutput(parsed)) {
|
||||
|
||||
## Hook 输出的 JSON Schema
|
||||
|
||||
同步 Hook 的输出遵循严格的 Zod schema(`src/types/hooks.ts:49-567`):
|
||||
同步 Hook 的输出遵循严格的 Zod schema(`syncHookResponseSchema`,定义在 `src/types/hooks.ts`,`hookJSONOutputSchema` 定义在 `src/schemas/hooks.ts`):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -120,16 +125,25 @@ if (isAsyncHookJSONOutput(parsed)) {
|
||||
|
||||
| 事件 | 专有字段 | 作用 |
|
||||
|------|---------|------|
|
||||
| `PreToolUse` | `permissionDecision`, `updatedInput`, `additionalContext` | 拦截/修改工具输入 |
|
||||
| `UserPromptSubmit` | `additionalContext` | 注入额外上下文 |
|
||||
| `PreToolUse` | `permissionDecision`, `permissionDecisionReason`, `updatedInput`, `additionalContext` | 拦截/修改工具输入 |
|
||||
| `PostToolUse` | `additionalContext`, `updatedMCPToolOutput` | 修改 MCP 工具输出 |
|
||||
| `SessionStart` | `initialUserMessage`, `watchPaths` | 设置初始消息和文件监控 |
|
||||
| `PostToolUseFailure` | `additionalContext` | 失败后注入上下文 |
|
||||
| `UserPromptSubmit` | `additionalContext` | 注入额外上下文 |
|
||||
| `SessionStart` | `additionalContext`, `initialUserMessage`, `watchPaths` | 设置初始消息和文件监控 |
|
||||
| `PermissionRequest` | `decision`(含 `allow`/`deny` 子字段) | 权限请求的 Hook 决策 |
|
||||
| `PermissionDenied` | `retry` | 指示是否重试 |
|
||||
| `SubagentStart` | `additionalContext` | 子 Agent 启动时注入上下文 |
|
||||
| `Elicitation` | `action`, `content` | 控制用户输入对话框 |
|
||||
| `ElicitationResult` | `action`, `content` | Elicitation 结果处理 |
|
||||
| `Notification` | `additionalContext` | 通知事件注入上下文 |
|
||||
| `Setup` | `additionalContext` | 初始化时注入上下文 |
|
||||
| `CwdChanged` | `watchPaths` | 目录变更后更新监控路径 |
|
||||
| `FileChanged` | `watchPaths` | 文件变更后更新监控路径 |
|
||||
| `WorktreeCreate` | `worktreePath` | Worktree 创建通知 |
|
||||
|
||||
## Hook 匹配机制:getMatchingHooks
|
||||
|
||||
`getMatchingHooks()`(`hooks.ts:1685-1956`)负责从所有来源中查找匹配的 Hook:
|
||||
`getMatchingHooks()`(`src/utils/hooks.ts`,`getMatchingHooks` 函数)负责从所有来源中查找匹配的 Hook:
|
||||
|
||||
### 多来源合并
|
||||
|
||||
@@ -143,7 +157,7 @@ getHooksConfig()
|
||||
|
||||
### 匹配规则
|
||||
|
||||
`matcher` 字段支持三种模式(`matchesPattern()`, `hooks.ts:1428-1463`):
|
||||
`matcher` 字段支持三种模式(`matchesPattern()` 函数,`src/utils/hooks.ts`):
|
||||
|
||||
```
|
||||
"Write" → 精确匹配
|
||||
@@ -154,7 +168,7 @@ getHooksConfig()
|
||||
|
||||
### if 条件过滤
|
||||
|
||||
Hook 可以指定 `if` 条件,只在特定输入时触发。`prepareIfConditionMatcher()`(`hooks.ts:1472-1503`)预编译匹配器:
|
||||
Hook 可以指定 `if` 条件,只在特定输入时触发。`prepareIfConditionMatcher()`(`src/utils/hooks.ts`,`prepareIfConditionMatcher` 函数)预编译匹配器:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -169,11 +183,11 @@ Hook 可以指定 `if` 条件,只在特定输入时触发。`prepareIfConditio
|
||||
|
||||
### Hook 去重
|
||||
|
||||
同一个 Hook 命令在不同配置层级(user/project/local)可能重复。系统按 `pluginRoot\0command` 做 Map 去重,保留**最后合并的层级**。
|
||||
同一个 Hook 命令在不同配置层级(user/project/local)可能重复。系统按四部分复合键做 Map 去重:`${pluginRoot}\0${shell}\0${command}\0${ifCondition}`(由 `hookDedupKey()` 函数构建),保留**最后合并的层级**。
|
||||
|
||||
## 工作区信任检查
|
||||
|
||||
**所有 Hook 都要求工作区信任**(`shouldSkipHookDueToTrust()`, `hooks.ts:286-296`)。这是纵深防御措施——防止恶意仓库的 `.claude/settings.json` 在未信任的情况下执行任意命令。
|
||||
**所有 Hook 都要求工作区信任**(`shouldSkipHookDueToTrust()` 函数,`src/utils/hooks.ts`)。这是纵深防御措施——防止恶意仓库的 `.claude/settings.json` 在未信任的情况下执行任意命令。
|
||||
|
||||
```typescript
|
||||
// 交互模式下,所有 Hook 要求信任
|
||||
@@ -226,13 +240,13 @@ SDK 非交互模式下信任是隐式的(`getIsNonInteractiveSession()` 为 tr
|
||||
|
||||
## Session Hook 的生命周期
|
||||
|
||||
Agent 和 Skill 的前置 Hook 通过 `registerFrontmatterHooks()` 注册(`runAgent.ts:567-575`),绑定到 agent 的 session ID。Agent 结束时通过 `clearSessionHooks()` 清理。
|
||||
Agent 和 Skill 的前置 Hook 通过 `registerFrontmatterHooks()` 注册(调用位置:`packages/builtin-tools/src/tools/AgentTool/runAgent.ts`;定义位置:`src/utils/hooks/registerFrontmatterHooks.ts`),绑定到 agent 的 session ID。Agent 结束时通过 `clearSessionHooks()`(定义位置:`src/utils/hooks/sessionHooks.ts`)清理。
|
||||
|
||||
```typescript
|
||||
// runAgent.ts:567 — 注册 agent 的前置 Hook
|
||||
// runAgent.ts — 注册 agent 的前置 Hook
|
||||
registerFrontmatterHooks(rootSetAppState, agentId, agentDefinition.hooks, ...)
|
||||
|
||||
// runAgent.ts:820 — finally 块清理
|
||||
// runAgent.ts — finally 块清理
|
||||
clearSessionHooks(rootSetAppState, agentId)
|
||||
```
|
||||
|
||||
|
||||
346
docs/extensibility/mcp-configuration.mdx
Normal file
346
docs/extensibility/mcp-configuration.mdx
Normal file
@@ -0,0 +1,346 @@
|
||||
---
|
||||
title: "MCP 配置 - 多来源合并、作用域与策略管控"
|
||||
description: "详细说明 Claude Code MCP 配置的来源层次、合并优先级、传输类型、企业策略管控、插件集成和保留名称机制。"
|
||||
keywords: ["MCP", "配置", "settings.json", ".mcp.json", "企业策略", "插件"]
|
||||
---
|
||||
|
||||
## 配置来源与作用域
|
||||
|
||||
Claude Code 的 MCP 配置来自多个来源,每个来源对应一个 `scope`(作用域)。配置按优先级合并,高优先级来源的同名配置覆盖低优先级。
|
||||
|
||||
### 来源列表
|
||||
|
||||
| 来源 | Scope | 文件/接口 | 说明 |
|
||||
|------|-------|----------|------|
|
||||
| 企业管控 | `enterprise` | 系统管理路径 `managed-mcp.json` | **排他模式**:存在时忽略所有其他来源 |
|
||||
| 本地项目 | `local` | `<project>/.claude/settings.local.json` | 项目级私有配置(不提交到 VCS) |
|
||||
| 项目配置 | `project` | `<project>/.mcp.json` | 项目级共享配置(可提交到 VCS) |
|
||||
| 用户全局 | `user` | `~/.claude/settings.json` | 用户级配置,所有项目共享 |
|
||||
| 插件 | `dynamic` | 插件 manifest 中 `.mcp.json` / `.mcpb` | 插件提供的 MCP 服务器 |
|
||||
| claude.ai | `claudeai` | 通过 API 获取 | claude.ai 网页端配置的连接器 |
|
||||
| 内置动态 | `dynamic` | 代码中注册 | Computer Use / Chrome 等内置服务器 |
|
||||
| IDE SDK | `sdk` | IDE 传入 | VS Code / JetBrains 嵌入模式 |
|
||||
|
||||
### 合并优先级(从低到高)
|
||||
|
||||
```
|
||||
claude.ai 连接器 ← 最低优先级
|
||||
↓ 去重
|
||||
插件服务器
|
||||
↓ 去重
|
||||
用户全局配置
|
||||
↓
|
||||
项目配置(.mcp.json) ← 需要用户审批
|
||||
↓
|
||||
本地项目配置
|
||||
↓
|
||||
动态配置(内置 MCP) ← 最高优先级
|
||||
```
|
||||
|
||||
`Object.assign({}, dedupedPluginServers, userServers, approvedProjectServers, localServers)` 实现合并——后出现的同名键覆盖前者。
|
||||
|
||||
## 企业管控模式
|
||||
|
||||
当 `managed-mcp.json` 文件存在时,进入 **排他模式**:
|
||||
|
||||
```typescript
|
||||
// config.ts:1084
|
||||
if (doesEnterpriseMcpConfigExist()) {
|
||||
// 只返回企业配置,忽略所有用户/项目/插件/claude.ai 配置
|
||||
return { servers: filtered, errors: [] }
|
||||
}
|
||||
```
|
||||
|
||||
特性:
|
||||
- 路径由系统管理决定(`getManagedFilePath()` + `managed-mcp.json`)
|
||||
- 覆盖所有用户级、项目级、插件和 claude.ai 配置
|
||||
- 仍然应用策略过滤(allowlist/denylist)
|
||||
- 无法通过 CLI 添加新服务器(`addMcpConfig` 会拒绝)
|
||||
|
||||
## 传输类型与配置 Schema
|
||||
|
||||
### stdio(默认)
|
||||
|
||||
启动子进程,通过 stdin/stdout JSON-RPC 通信。
|
||||
|
||||
```json
|
||||
{
|
||||
"my-server": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@my-org/mcp-server"],
|
||||
"env": { "API_KEY": "..." }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`type` 字段可省略(默认为 `stdio`)。环境变量通过 `env` 传递给子进程,会与当前进程环境合并。
|
||||
|
||||
**Windows 注意**:使用 `npx` 需要包装为 `cmd /c npx`,否则会报错。
|
||||
|
||||
### SSE(Server-Sent Events)
|
||||
|
||||
通过 HTTP SSE 连接远程 MCP 服务器。
|
||||
|
||||
```json
|
||||
{
|
||||
"my-remote": {
|
||||
"type": "sse",
|
||||
"url": "https://mcp.example.com/sse",
|
||||
"headers": { "Authorization": "Bearer ..." },
|
||||
"oauth": {
|
||||
"clientId": "...",
|
||||
"authServerMetadataUrl": "https://auth.example.com/.well-known/oauth-authorization-server"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
支持 OAuth 认证流程。认证失败时进入 `needs-auth` 状态,15 分钟 TTL 缓存避免重复提示。
|
||||
|
||||
### HTTP(Streamable HTTP)
|
||||
|
||||
HTTP 流式传输。
|
||||
|
||||
```json
|
||||
{
|
||||
"my-http": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.example.com/mcp",
|
||||
"headers": { "X-API-Key": "..." }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
支持与 SSE 相同的 OAuth 配置。
|
||||
|
||||
### WebSocket
|
||||
|
||||
```json
|
||||
{
|
||||
"my-ws": {
|
||||
"type": "ws",
|
||||
"url": "wss://mcp.example.com/ws"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### IDE 专用类型(内部)
|
||||
|
||||
`sse-ide` 和 `ws-ide` 是 IDE 扩展专用类型,不由用户直接配置。
|
||||
|
||||
- `sse-ide`:使用 lockfile token 认证
|
||||
- `ws-ide`:使用 `X-Claude-Code-Ide-Authorization` header
|
||||
|
||||
### SDK 类型(内部)
|
||||
|
||||
`type: "sdk"` 由 IDE 嵌入模式传入,不经过保留名称检查和企业管控排他限制。
|
||||
|
||||
### claude.ai 代理类型(内部)
|
||||
|
||||
`type: "claudeai-proxy"` 由 claude.ai 网页端配置的连接器使用,通过 OAuth bearer token 认证并支持 401 重试。
|
||||
|
||||
## 配置操作
|
||||
|
||||
### 添加 MCP 服务器
|
||||
|
||||
通过 CLI 命令 `claude mcp add` 或 API 调用 `addMcpConfig()`:
|
||||
|
||||
```bash
|
||||
# 添加到用户配置
|
||||
claude mcp add my-server -s user -- npx @my-org/mcp-server
|
||||
|
||||
# 添加到项目配置
|
||||
claude mcp add my-server -s project -- npx @my-org/mcp-server
|
||||
|
||||
# 添加 HTTP 类型
|
||||
claude mcp add my-remote -s user -t http -u https://mcp.example.com/mcp
|
||||
```
|
||||
|
||||
添加时的验证流程:
|
||||
|
||||
1. **名称校验**:只允许字母、数字、连字符和下划线
|
||||
2. **保留名检查**:`claude-in-chrome` 和 `computer-use` 被保留
|
||||
3. **企业管控检查**:企业模式下拒绝添加
|
||||
4. **Schema 验证**:Zod 校验配置格式
|
||||
5. **策略检查**:denylist 拒绝、allowlist 验证
|
||||
|
||||
### 移除 MCP 服务器
|
||||
|
||||
```bash
|
||||
claude mcp remove my-server -s user
|
||||
```
|
||||
|
||||
### 列出 MCP 服务器
|
||||
|
||||
```bash
|
||||
claude mcp list
|
||||
```
|
||||
|
||||
## 项目配置审批
|
||||
|
||||
`.mcp.json` 中的项目配置需要用户显式审批才能生效:
|
||||
|
||||
```typescript
|
||||
// config.ts:1166
|
||||
const approvedProjectServers: Record<string, ScopedMcpServerConfig> = {}
|
||||
for (const [name, config] of Object.entries(projectServers)) {
|
||||
if (getProjectMcpServerStatus(name) === 'approved') {
|
||||
approvedProjectServers[name] = config
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
首次打开项目时,Claude Code 会提示用户审批 `.mcp.json` 中的每个服务器。审批状态持久化在本地配置中。
|
||||
|
||||
## 插件 MCP 集成
|
||||
|
||||
插件通过 manifest 中的 `.mcp.json` 或 `.mcpb` 文件声明 MCP 服务器:
|
||||
|
||||
```typescript
|
||||
// 插件 MCP 加载流程
|
||||
const pluginResult = await loadAllPluginsCacheOnly()
|
||||
const pluginServerResults = await Promise.all(
|
||||
pluginResult.enabled.map(plugin => getPluginMcpServers(plugin, mcpErrors))
|
||||
)
|
||||
```
|
||||
|
||||
### 插件命名空间
|
||||
|
||||
插件 MCP 服务器名格式为 `plugin:<pluginName>:<serverName>`,不会与手动配置的名称冲突。
|
||||
|
||||
### 去重机制
|
||||
|
||||
插件服务器通过内容签名去重(`dedupPluginMcpServers`):
|
||||
|
||||
- **stdio 类型**:签名 = `stdio:` + JSON.stringify([command, ...args])
|
||||
- **URL 类型**:签名 = `url:` + 原始 URL(unwrap CCR proxy URL)
|
||||
- **sdk 类型**:签名为 null,不去重
|
||||
|
||||
去重规则:
|
||||
1. 手动配置优先于插件配置
|
||||
2. 先加载的插件优先于后加载的
|
||||
3. 被抑制的插件服务器在 `/plugin` UI 中显示提示
|
||||
|
||||
### claude.ai 连接器去重
|
||||
|
||||
claude.ai 连接器使用相同的内容签名机制去重(`dedupClaudeAiMcpServers`):
|
||||
- 仅启用的手动配置参与去重(禁用的手动配置不应抑制连接器)
|
||||
- 连接器名格式为 `claude.ai <DisplayName>`
|
||||
|
||||
## 策略管控
|
||||
|
||||
### Allowlist / Denylist
|
||||
|
||||
企业策略通过 allowlist 和 denylist 控制可用的 MCP 服务器:
|
||||
|
||||
```typescript
|
||||
// config.ts:1243 - 最终策略过滤
|
||||
for (const [name, serverConfig] of Object.entries(configs)) {
|
||||
if (!isMcpServerAllowedByPolicy(name, serverConfig)) {
|
||||
continue // 跳过策略禁止的服务器
|
||||
}
|
||||
filtered[name] = serverConfig
|
||||
}
|
||||
```
|
||||
|
||||
策略检查考虑:
|
||||
- 服务器名称匹配
|
||||
- stdio 类型的 command + args 匹配
|
||||
- URL 类型的 URL 模式匹配(支持通配符)
|
||||
|
||||
### 插件专用模式
|
||||
|
||||
`isRestrictedToPluginOnly('mcp')` 启用时,只允许插件提供的 MCP 服务器——用户/项目级配置被忽略。
|
||||
|
||||
## 环境变量展开
|
||||
|
||||
MCP 配置中的环境变量支持 `$VAR` 和 `${VAR}` 语法展开:
|
||||
|
||||
```json
|
||||
{
|
||||
"my-server": {
|
||||
"command": "npx",
|
||||
"args": ["@my-org/mcp-server"],
|
||||
"env": {
|
||||
"API_KEY": "$MY_API_KEY",
|
||||
"DB_URL": "${DATABASE_URL}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
展开时缺失的变量会生成警告信息,但不阻止配置加载。
|
||||
|
||||
## 内置 MCP 动态注册
|
||||
|
||||
内置 MCP 服务器在 `main.tsx` 启动流程中动态注入配置:
|
||||
|
||||
### Computer Use MCP
|
||||
|
||||
```typescript
|
||||
// src/utils/computerUse/setup.ts
|
||||
export function setupComputerUseMCP(): {
|
||||
mcpConfig: Record<string, ScopedMcpServerConfig>
|
||||
allowedTools: string[]
|
||||
} {
|
||||
return {
|
||||
mcpConfig: {
|
||||
"computer-use": {
|
||||
type: "stdio",
|
||||
command: process.execPath,
|
||||
args: ["--computer-use-mcp"],
|
||||
scope: "dynamic",
|
||||
}
|
||||
},
|
||||
allowedTools: ["mcp__computer-use__screenshot", ...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
启用条件:
|
||||
- Feature flag `CHICAGO_MCP` 开启
|
||||
- `getPlatform() !== "unknown"`(macOS/Windows/Linux)
|
||||
- 非非交互式会话
|
||||
- GrowthBook gate `getChicagoEnabled()` 返回 true
|
||||
|
||||
### Claude in Chrome MCP
|
||||
|
||||
```typescript
|
||||
// 类似 Computer Use,在 main.tsx 中注册
|
||||
const { mcpConfig, allowedTools, systemPrompt } = setupClaudeInChrome()
|
||||
dynamicMcpConfig = { ...dynamicMcpConfig, ...mcpConfig }
|
||||
```
|
||||
|
||||
启用条件:
|
||||
- `--chrome` 参数或 `claudeInChromeDefaultEnabled` 配置
|
||||
- Chrome 扩展已安装
|
||||
|
||||
### VSCode SDK MCP
|
||||
|
||||
IDE 嵌入模式通过初始化消息传入 `type:'sdk'` 的配置,由 `setupVscodeSdkMcp()` 设置双向通知。
|
||||
|
||||
## 保留名称
|
||||
|
||||
以下 MCP 服务器名称被保留,用户无法手动配置同名服务器:
|
||||
|
||||
| 名称 | 用途 | 检查条件 |
|
||||
|------|------|---------|
|
||||
| `claude-in-chrome` | Chrome 浏览器控制 | 始终检查 |
|
||||
| `computer-use` | 桌面自动化 | `CHICAGO_MCP` feature flag 开启时检查 |
|
||||
| `claude-vscode` | VSCode IDE 集成 | 由 SDK 传入,不经过名称检查 |
|
||||
|
||||
保留名检查在两个位置:
|
||||
1. `addMcpConfig()`(`config.ts:636-648`)— 运行时拒绝
|
||||
2. `main.tsx` 启动检查(`main.tsx:2351-2368`)— 启动时退出
|
||||
|
||||
## 关键源文件索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/services/mcp/config.ts` | 配置管理核心:合并、去重、策略、添加/删除 |
|
||||
| `src/services/mcp/types.ts` | Zod Schema 定义、类型声明 |
|
||||
| `src/services/mcp/client.ts` | 连接管理、传输层选择 |
|
||||
| `src/utils/plugins/mcpPluginIntegration.ts` | 插件 MCP 配置加载 |
|
||||
| `src/utils/computerUse/setup.ts` | Computer Use 动态注册 |
|
||||
| `src/utils/claudeInChrome/common.ts` | Chrome MCP 保留名与工具名 |
|
||||
| `src/services/mcp/vscodeSdkMcp.ts` | VSCode SDK 双向通知 |
|
||||
@@ -1,25 +1,32 @@
|
||||
---
|
||||
title: "MCP 协议 - 连接管理、工具发现与执行链路"
|
||||
description: "从源码角度解析 Claude Code 的 MCP 集成:7 种传输层实现、connectToServer 的 memoize 缓存、工具发现的 LRU 策略、认证状态机、以及 MCP 工具如何进入权限检查链路。"
|
||||
keywords: ["MCP", "Model Context Protocol", "工具扩展", "MCP 客户端", "工具发现"]
|
||||
description: "从源码角度解析 Claude Code 的 MCP 集成:内置 MCP 与外部 MCP 的区别、7 种传输层实现、connectToServer 的 memoize 缓存、工具发现的 LRU 策略、认证状态机、以及 MCP 工具如何进入权限检查链路。"
|
||||
keywords: ["MCP", "Model Context Protocol", "工具扩展", "MCP 客户端", "工具发现", "内置 MCP", "外部 MCP"]
|
||||
---
|
||||
|
||||
{/* 本章目标:从源码角度揭示 MCP 客户端的连接管理、工具发现协议和执行链路 */}
|
||||
{/* 本章目标:从源码角度揭示 MCP 客户端的两种运行模式(内置/外部)、连接管理、工具发现协议和执行链路 */}
|
||||
|
||||
## 架构总览:从配置到可用工具
|
||||
|
||||
```
|
||||
settings.json: { mcpServers: { "my-db": { command: "npx", args: [...] } } }
|
||||
配置层(多来源合并)
|
||||
├── settings.json: { mcpServers: { "my-db": { command: "npx", args: [...] } } } ← 外部
|
||||
├── .mcp.json: 项目级 MCP 配置 ← 外部
|
||||
├── 插件 manifest (.mcp.json / .mcpb) ← 外部(插件)
|
||||
├── claude.ai connectors ← 外部(远程)
|
||||
├── enterprise managed-mcp.json ← 外部(企业管控)
|
||||
├── setupComputerUseMCP() / setupClaudeInChrome() ← 内置(动态注册)
|
||||
└── SDK 传入 (type:'sdk') ← 内置(IDE 嵌入)
|
||||
↓
|
||||
getAllMcpConfigs() ← 合并 user/project/local 三级配置
|
||||
getAllMcpConfigs() ← enterprise 独占 或 合并 user/project/local + plugin + claude.ai
|
||||
↓
|
||||
useManageMCPConnections() ← React Hook 管理连接生命周期
|
||||
↓
|
||||
connectToServer(name, config) ← memoize 缓存(lodash memoize)
|
||||
├── 创建 Transport(stdio/sse/http/...)
|
||||
├── new Client() ← @modelcontextprotocol/sdk
|
||||
├── client.connect(transport) ← 超时控制(MCP_TIMEOUT, 默认 30s)
|
||||
└── 返回 MCPServerConnection ← { connected | failed | needs-auth | pending }
|
||||
├── 判断:内置 MCP → InProcessTransport(同进程)
|
||||
├── 判断:外部 stdio → StdioClientTransport(子进程)
|
||||
├── 判断:远程 SSE/HTTP/WS → 网络传输
|
||||
└── 返回 MCPServerConnection ← { connected | failed | needs-auth | pending | disabled }
|
||||
↓
|
||||
fetchToolsForClient(client) ← LRU(20) 缓存
|
||||
├── client.request({ method: 'tools/list' })
|
||||
@@ -30,19 +37,208 @@ assembleToolPool() ← 合并内置工具 + MCP 工具
|
||||
工具名格式: mcp__<serverName>__<toolName> ← buildMcpToolName()
|
||||
```
|
||||
|
||||
## 两种 MCP 模式:内置 vs 外部
|
||||
|
||||
Claude Code 的 MCP 实现区分 **内置 MCP 服务器** 和 **外部 MCP 服务器**。两者使用相同的客户端协议和工具发现机制,但在连接方式、生命周期管理和配置来源上完全不同。
|
||||
|
||||
### 内置 MCP 服务器
|
||||
|
||||
内置 MCP 服务器由 Claude Code 自身提供,无需用户手动配置。它们在启动时自动注册为 `dynamic` scope 的配置,并在同进程内运行。
|
||||
|
||||
| 服务器 | 名称 | 包路径 | Feature Flag | 启用方式 |
|
||||
|--------|------|--------|-------------|---------|
|
||||
| Computer Use | `computer-use` | `@ant/computer-use-mcp` | `CHICAGO_MCP` | GrowthBook gate + macOS + interactive |
|
||||
| Claude in Chrome | `claude-in-chrome` | `@ant/claude-for-chrome-mcp` | — | `--chrome` 参数或 `claudeInChromeDefaultEnabled` 配置 |
|
||||
| VSCode SDK | `claude-vscode` | — | — | IDE 嵌入模式 (type:`sdk`) |
|
||||
|
||||
#### InProcessTransport:零开销同进程通信
|
||||
|
||||
内置服务器通过 `InProcessTransport`(`src/services/mcp/InProcessTransport.ts`)运行,**不启动子进程**:
|
||||
|
||||
```typescript
|
||||
// 创建一对 linked transport —— 消息在两端之间直接传递
|
||||
const [clientTransport, serverTransport] = createLinkedTransportPair()
|
||||
|
||||
// server 端连接到 serverTransport
|
||||
inProcessServer = createComputerUseMcpServerForCli()
|
||||
await inProcessServer.connect(serverTransport)
|
||||
|
||||
// client 端使用 clientTransport(与外部 MCP 的 Client 相同接口)
|
||||
transport = clientTransport
|
||||
```
|
||||
|
||||
`InProcessTransport` 的核心设计:
|
||||
- `send()` 通过 `queueMicrotask()` 异步投递消息到对端,避免同步请求/响应的栈深度问题
|
||||
- `close()` 双向关闭,任一端关闭都会触发两端的 `onclose` 回调
|
||||
- 无网络开销、无 IPC 序列化、无进程启动时间
|
||||
|
||||
#### 动态注册流程
|
||||
|
||||
内置服务器在 `main.tsx` 的启动流程中注册,注入 `dynamicMcpConfig`:
|
||||
|
||||
```typescript
|
||||
// main.tsx: Computer Use MCP 动态注册
|
||||
if (feature("CHICAGO_MCP") && getPlatform() !== "unknown" && !getIsNonInteractiveSession()) {
|
||||
const { getChicagoEnabled } = await import("src/utils/computerUse/gates.js")
|
||||
if (getChicagoEnabled()) {
|
||||
const { setupComputerUseMCP } = await import("src/utils/computerUse/setup.js")
|
||||
const { mcpConfig, allowedTools } = setupComputerUseMCP()
|
||||
dynamicMcpConfig = { ...dynamicMcpConfig, ...mcpConfig }
|
||||
allowedTools.push(...cuTools)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`setupComputerUseMCP()` 返回的配置(`src/utils/computerUse/setup.ts`):
|
||||
|
||||
```typescript
|
||||
{
|
||||
"computer-use": {
|
||||
type: "stdio", // 类型标记为 stdio(但 client.ts 会拦截为 InProcessTransport)
|
||||
command: process.execPath,
|
||||
args: ["--computer-use-mcp"],
|
||||
scope: "dynamic", // 动态作用域,不持久化
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 连接时拦截
|
||||
|
||||
`connectToServer()` 在 `client.ts:906-944` 中根据服务器名拦截内置服务器:
|
||||
|
||||
```typescript
|
||||
// Chrome MCP — 在 process 内运行,避免 ~325MB 子进程
|
||||
if (isClaudeInChromeMCPServer(name)) {
|
||||
const { createChromeContext } = await import('../../utils/claudeInChrome/mcpServer.js')
|
||||
const { createClaudeForChromeMcpServer } = await import('@ant/claude-for-chrome-mcp')
|
||||
const { createLinkedTransportPair } = await import('./InProcessTransport.js')
|
||||
const context = createChromeContext(config.env)
|
||||
inProcessServer = createClaudeForChromeMcpServer(context)
|
||||
const [clientTransport, serverTransport] = createLinkedTransportPair()
|
||||
await inProcessServer.connect(serverTransport)
|
||||
transport = clientTransport
|
||||
}
|
||||
|
||||
// Computer Use MCP — 同理
|
||||
if (feature('CHICAGO_MCP') && isComputerUseMCPServer(name)) {
|
||||
const { createComputerUseMcpServerForCli } = await import('../../utils/computerUse/mcpServer.js')
|
||||
const { createLinkedTransportPair } = await import('./InProcessTransport.js')
|
||||
inProcessServer = await createComputerUseMcpServerForCli()
|
||||
const [clientTransport, serverTransport] = createLinkedTransportPair()
|
||||
await inProcessServer.connect(serverTransport)
|
||||
transport = clientTransport
|
||||
}
|
||||
```
|
||||
|
||||
#### 保留名称保护
|
||||
|
||||
内置服务器的名称被保留,用户无法手动添加同名配置(`config.ts:636-648`):
|
||||
|
||||
```typescript
|
||||
// 添加 MCP 配置时检查保留名
|
||||
if (isClaudeInChromeMCPServer(name)) {
|
||||
throw new Error(`Cannot add MCP server "${name}": this name is reserved.`)
|
||||
}
|
||||
if (feature('CHICAGO_MCP') && isComputerUseMCPServer(name)) {
|
||||
throw new Error(`Cannot add MCP server "${name}": this name is reserved.`)
|
||||
}
|
||||
```
|
||||
|
||||
启动时也有全局检查(`main.tsx:2351-2368`):如果用户配置中包含保留名(非 `type:'sdk'`),直接 `process.exit(1)`。
|
||||
|
||||
#### VSCode SDK MCP
|
||||
|
||||
VSCode SDK MCP 是特殊的内置模式。IDE(如 VS Code、JetBrains)通过嵌入方式启动 Claude Code,并传入 `type:'sdk'` 的 MCP 配置。这类配置:
|
||||
- 不经过保留名称检查(IDE 可以使用任意名称)
|
||||
- 不参与 enterprise MCP 的排他控制
|
||||
- 通过 VSCode SDK transport 连接
|
||||
- 支持双向通知(如 `file_updated`、`experiment_gates`)
|
||||
|
||||
```typescript
|
||||
// src/services/mcp/vscodeSdkMcp.ts
|
||||
export function setupVscodeSdkMcp(sdkClients: MCPServerConnection[]): void {
|
||||
const client = sdkClients.find(client => client.name === 'claude-vscode')
|
||||
if (client && client.type === 'connected') {
|
||||
// 注册 log_event 通知处理器
|
||||
client.client.setNotificationHandler(LogEventNotificationSchema(), ...)
|
||||
// 发送实验门控到 VSCode
|
||||
client.client.notification({ method: 'experiment_gates', params: { gates } })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 外部 MCP 服务器
|
||||
|
||||
外部 MCP 服务器由用户在配置文件中声明,通过子进程或网络连接运行。
|
||||
|
||||
#### 配置来源
|
||||
|
||||
| 来源 | Scope | 文件位置 | 优先级 |
|
||||
|------|-------|---------|--------|
|
||||
| 项目配置 | `project` | `<project>/.mcp.json` | 最高(同名覆盖) |
|
||||
| 本地配置 | `local` | `<project>/.claude/settings.local.json` | 高 |
|
||||
| 用户配置 | `user` | `~/.claude/settings.json` | 中 |
|
||||
| 插件 | `dynamic` | 插件 manifest 中 `.mcp.json` | 中 |
|
||||
| claude.ai | `claudeai` | 通过 API 获取 | 低 |
|
||||
| 企业管控 | `enterprise` | 系统管理路径 `managed-mcp.json` | 排他(存在时覆盖全部) |
|
||||
|
||||
#### 配置示例
|
||||
|
||||
```json
|
||||
// settings.json / .mcp.json 中的 MCP 配置
|
||||
{
|
||||
"mcpServers": {
|
||||
// stdio 类型 — 启动子进程
|
||||
"my-database": {
|
||||
"command": "npx",
|
||||
"args": ["@my-org/db-mcp-server"],
|
||||
"env": { "DB_URL": "postgres://..." }
|
||||
},
|
||||
|
||||
// HTTP 流类型 — 远程服务器
|
||||
"remote-api": {
|
||||
"type": "http",
|
||||
"url": "https://api.example.com/mcp"
|
||||
},
|
||||
|
||||
// SSE 类型 — Server-Sent Events
|
||||
"realtime-feed": {
|
||||
"type": "sse",
|
||||
"url": "https://feed.example.com/sse"
|
||||
},
|
||||
|
||||
// WebSocket 类型
|
||||
"ws-service": {
|
||||
"type": "ws",
|
||||
"url": "wss://ws.example.com/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 配置合并与去重
|
||||
|
||||
`getAllMcpConfigs()`(`config.ts`)按优先级合并多个来源的配置:
|
||||
|
||||
1. 企业管控配置存在时,**独占返回**(忽略所有其他来源)
|
||||
2. 否则合并:user → project → local → plugin → claude.ai
|
||||
3. 插件与手动配置去重:通过 `getMcpServerSignature()` 生成内容签名(基于 command/args/url),插件配置被同名手动配置抑制
|
||||
4. `addScopeToServers()` 为每个配置项标注来源 scope
|
||||
|
||||
## 7 种传输层实现
|
||||
|
||||
`connectToServer()`(`client.ts:596-1643`)根据 `config.type` 分发到不同的 Transport 实现:
|
||||
|
||||
| 传输类型 | Transport 类 | 适用场景 | 认证方式 |
|
||||
|----------|-------------|---------|---------|
|
||||
| `stdio`(默认) | `StdioClientTransport` | 本地子进程 | 无 |
|
||||
| `stdio`(默认) | `StdioClientTransport` | 外部本地子进程 | 无 |
|
||||
| `sse` | `SSEClientTransport` | 远程 SSE 服务 | `ClaudeAuthProvider` + OAuth |
|
||||
| `http` | `StreamableHTTPClientTransport` | HTTP 流 | `ClaudeAuthProvider` + OAuth |
|
||||
| `sse-ide` | `SSEClientTransport` | IDE 集成 | lockfile token |
|
||||
| `ws-ide` | `WebSocketTransport` | IDE WebSocket | `X-Claude-Code-Ide-Authorization` |
|
||||
| `ws` | `WebSocketTransport` | WebSocket 服务 | session ingress token |
|
||||
| `claudeai-proxy` | `StreamableHTTPClientTransport` | claude.ai 代理 | OAuth bearer + 401 重试 |
|
||||
| InProcess(内置) | `InProcessTransport` | Computer Use / Chrome | 无(同进程) |
|
||||
|
||||
### stdio 传输的进程管理
|
||||
|
||||
@@ -108,13 +304,21 @@ timer.unref?.() // 不阻止进程退出
|
||||
|
||||
## 工具发现:从 MCP 到 Tool 接口
|
||||
|
||||
`fetchToolsForClient()`(`client.ts:1745-2000`)使用 `memoizeWithLRU` 缓存(上限 20),将 MCP 工具转换为 Claude Code 的统一 Tool 接口:
|
||||
`fetchToolsForClient()`(`client.ts:1744-2000`)使用 `memoizeWithLRU` 缓存(上限 100),将 MCP 工具转换为 Claude Code 的统一 Tool 接口:
|
||||
|
||||
```typescript
|
||||
const fullyQualifiedName = buildMcpToolName(client.name, tool.name)
|
||||
// 结果: "mcp__my-db__query"
|
||||
// 结果: "mcp__my-database__query"
|
||||
```
|
||||
|
||||
### 内置 MCP 的工具发现
|
||||
|
||||
内置 MCP 服务器虽然使用 InProcessTransport,但工具发现流程与外部服务器完全一致:
|
||||
|
||||
- **Computer Use**:`createComputerUseMcpServerForCli()` 在 `src/utils/computerUse/mcpServer.ts` 中构建 MCP Server 对象,注册 `ListToolsRequestSchema` handler。工具描述包含平台特定的已安装应用列表(1s 超时枚举)。
|
||||
- **Claude in Chrome**:`createClaudeForChromeMcpServer()` 在 `@ant/claude-for-chrome-mcp` 包中构建 Server,提供 17+ 个浏览器控制工具。
|
||||
- **VSCode SDK**:由 IDE 端提供工具列表,通过 SDK transport 传递。
|
||||
|
||||
### 工具描述截断
|
||||
|
||||
MCP 工具描述上限 2048 字符(`MAX_MCP_DESCRIPTION_LENGTH`)。OpenAPI 生成的 MCP 服务器曾观察到 15-60KB 的描述文档。
|
||||
@@ -134,6 +338,8 @@ MCP 工具描述上限 2048 字符(`MAX_MCP_DESCRIPTION_LENGTH`)。OpenAPI
|
||||
|
||||
MCP 工具默认返回 `{ behavior: 'passthrough' }`(`client.ts:1816-1834`),意味着它们始终进入权限确认流程。工具名使用 `mcp__` 前缀精确匹配权限规则。
|
||||
|
||||
内置 MCP 服务器的工具通过 `allowedTools` 列表自动授权——在 `main.tsx` 启动时加入,绕过普通权限提示。例如 Computer Use 工具的 `request_access` 自行处理会话级审批。
|
||||
|
||||
## MCP 工具的执行链路
|
||||
|
||||
```
|
||||
@@ -169,23 +375,33 @@ getRemoteMcpServerConnectionBatchSize() // 默认 20
|
||||
|
||||
本地 MCP 服务器(stdio)是重量级的子进程,默认限制 3 个并发连接。远程服务器是轻量级 HTTP 请求,允许 20 个并发。
|
||||
|
||||
## 实际配置示例
|
||||
## 内置 vs 外部 MCP 对比总结
|
||||
|
||||
```json
|
||||
// settings.json 中的 MCP 配置
|
||||
{
|
||||
"mcpServers": {
|
||||
"my-database": {
|
||||
"command": "npx",
|
||||
"args": ["@my-org/db-mcp-server"],
|
||||
"env": { "DB_URL": "postgres://..." }
|
||||
},
|
||||
"remote-api": {
|
||||
"type": "http",
|
||||
"url": "https://api.example.com/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
| 维度 | 内置 MCP | 外部 MCP |
|
||||
|------|---------|---------|
|
||||
| **Transport** | `InProcessTransport`(同进程) | stdio / SSE / HTTP / WebSocket |
|
||||
| **配置来源** | `setupComputerUseMCP()` / `setupClaudeInChrome()` 等动态注册 | settings.json / .mcp.json / 插件 / claude.ai |
|
||||
| **Scope** | `dynamic` | `user` / `project` / `local` / `enterprise` / `claudeai` |
|
||||
| **进程模型** | 同进程,零开销 | 子进程(stdio)或网络连接 |
|
||||
| **名称保护** | 保留名,用户不可添加同名 | 自由命名(字母数字 + `-_`) |
|
||||
| **生命周期** | 随 CLI 启停 | 连接缓存 + 按需重连 |
|
||||
| **权限** | `allowedTools` 自动授权 | `passthrough` 进入权限确认 |
|
||||
| **Feature Flag** | `CHICAGO_MCP`(Computer Use)等 | 无(始终可用) |
|
||||
| **工具发现** | 与外部相同(MCP 协议) | 标准 MCP `tools/list` |
|
||||
| **清理** | `inProcessServer.close()` | 信号升级策略 SIGINT→SIGTERM→SIGKILL |
|
||||
|
||||
配置后,AI 的工具列表中会出现 `mcp__my-database__query` 和 `mcp__remote-api__*` 工具——与内置工具使用相同的权限检查链路和 UI 渲染。
|
||||
## 关键源文件索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/services/mcp/client.ts` | 核心客户端:connectToServer、fetchToolsForClient、MCPTool.call |
|
||||
| `src/services/mcp/config.ts` | 配置管理:getAllMcpConfigs、addMcpConfig、removeMcpConfig |
|
||||
| `src/services/mcp/types.ts` | 类型定义:配置 Schema、连接状态类型 |
|
||||
| `src/services/mcp/InProcessTransport.ts` | 内置 MCP 传输层:linked transport pair |
|
||||
| `src/services/mcp/vscodeSdkMcp.ts` | VSCode SDK MCP:双向通知、实验门控 |
|
||||
| `src/services/mcp/useManageMCPConnections.ts` | React Hook:连接生命周期、重连 |
|
||||
| `src/utils/computerUse/mcpServer.ts` | Computer Use MCP Server 构建 |
|
||||
| `src/utils/computerUse/setup.ts` | Computer Use 动态注册 |
|
||||
| `src/utils/claudeInChrome/mcpServer.ts` | Chrome MCP Server 构建 + Bridge 配置 |
|
||||
| `src/tools/MCPTool/MCPTool.ts` | MCP 工具包装:统一 Tool 接口 |
|
||||
| `src/entrypoints/mcp.ts` | MCP server 入口(Claude Code 作为 MCP server) |
|
||||
|
||||
@@ -22,7 +22,7 @@ Skill 的核心洞见:**复杂任务的关键不在代码逻辑,而在 Promp
|
||||
|
||||
### 1. 内置命令(Built-in Commands)
|
||||
|
||||
硬编码在 `src/commands.ts:258` 的 `COMMANDS` memoize 数组中,包含 70+ 条命令(`/commit`、`/review`、`/compact` 等)。这些是 TypeScript 模块而非 Markdown,但实现了相同的 `Command` 接口(`src/types/command.ts`)。
|
||||
硬编码在 `src/commands.ts:299` 的 `COMMANDS` memoize 数组中,包含 70+ 条命令(`/commit`、`/review`、`/compact` 等)。这些是 TypeScript 模块而非 Markdown,但实现了相同的 `Command` 接口(`src/types/command.ts`)。
|
||||
|
||||
### 2. Bundled Skills(编译时打包)
|
||||
|
||||
@@ -48,7 +48,7 @@ Skill 的核心洞见:**复杂任务的关键不在代码逻辑,而在 Promp
|
||||
1. `readdir` 扫描目录 → 仅保留 `isDirectory()` 或 `isSymbolicLink()` 的条目
|
||||
2. 在每个子目录中查找 `SKILL.md`,未找到则跳过
|
||||
3. `parseFrontmatter()` 解析 YAML 头部,提取 `whenToUse`、`allowedTools`、`context` 等字段
|
||||
4. `parseSkillFrontmatterFields()`(第 185 行)统一解析 17 个 frontmatter 字段
|
||||
4. `parseSkillFrontmatterFields()`(第 185 行)统一解析 16 个 frontmatter 字段
|
||||
5. `createSkillCommand()`(第 270 行)构造 `Command` 对象
|
||||
|
||||
**去重机制**:使用 `realpath()` 解析符号链接获得规范路径(`getFileIdentity`,第 118 行),避免通过符号链接或重叠父目录导致的重复加载。
|
||||
@@ -94,11 +94,11 @@ shell: ["bash"] # Shell 执行环境
|
||||
---
|
||||
```
|
||||
|
||||
解析后有 17 个字段被提取,其中 `allowedTools`、`model`、`effort` 在执行时动态修改 `toolPermissionContext`。
|
||||
解析后有 16 个字段被提取,其中 `allowedTools`、`model`、`effort` 在执行时动态修改 `toolPermissionContext`。
|
||||
|
||||
## 两条执行路径:Inline vs Fork
|
||||
|
||||
SkillTool(`src/tools/SkillTool/SkillTool.ts:332`)在 `call()` 中根据 `command.context` 分流:
|
||||
SkillTool(`packages/builtin-tools/src/tools/SkillTool/SkillTool.ts:332`)在 `call()` 中根据 `command.context` 分流:
|
||||
|
||||
### Inline 模式(默认)
|
||||
|
||||
@@ -128,12 +128,12 @@ Fork 模式适用于需要强隔离的场景(如长时间运行的审查任务
|
||||
|
||||
## 权限模型:Safe Properties 白名单
|
||||
|
||||
`checkPermissions()`(第 433 行)实现了一个四层权限检查:
|
||||
`checkPermissions()`(第 433 行)实现了一个五层权限检查:
|
||||
|
||||
```
|
||||
1. Deny 规则匹配(支持精确匹配和 prefix:* 通配符)
|
||||
↓ 未命中
|
||||
2. 官方市场 Skill 自动放行(plugin + isOfficialMarketplaceName)
|
||||
2. 远程 canonical Skill 自动放行(EXPERIMENTAL_SKILL_SEARCH + USER_TYPE === 'ant')
|
||||
↓ 未命中
|
||||
3. Allow 规则匹配
|
||||
↓ 未命中
|
||||
@@ -142,7 +142,7 @@ Fork 模式适用于需要强隔离的场景(如长时间运行的审查任务
|
||||
5. Ask 用户确认(附带精确匹配和前缀匹配两条建议规则)
|
||||
```
|
||||
|
||||
**Safe Properties**(`SAFE_SKILL_PROPERTIES`,第 876 行)是一个包含 28 个属性名的白名单。任何不在白名单中的**有意义的属性值**(排除 `undefined`、`null`、空数组、空对象)都会触发权限请求。这是**正向安全**设计——未来新增的属性默认需要权限。
|
||||
**Safe Properties**(`SAFE_SKILL_PROPERTIES`,第 876 行)是一个包含 30 个属性名的白名单(覆盖 `PromptCommand` 和 `CommandBase` 两个类型的所有安全属性)。任何不在白名单中的**有意义的属性值**(排除 `undefined`、`null`、空数组、空对象)都会触发权限请求。这是**正向安全**设计——未来新增的属性默认需要权限。
|
||||
|
||||
## Prompt 预算:1% 上下文窗口的截断策略
|
||||
|
||||
@@ -205,7 +205,7 @@ score = usageCount × max(0.5^(daysSinceUse / 7), 0.1)
|
||||
```
|
||||
磁盘 SKILL.md
|
||||
↓ parseFrontmatter()
|
||||
↓ parseSkillFrontmatterFields() → 17 个字段
|
||||
↓ parseSkillFrontmatterFields() → 16 个字段
|
||||
↓ createSkillCommand() → Command 对象
|
||||
↓ 去重(realpath + seenFileIds)
|
||||
↓ 条件 Skill → conditionalSkills Map(等待路径匹配激活)
|
||||
@@ -214,7 +214,7 @@ score = usageCount × max(0.5^(daysSinceUse / 7), 0.1)
|
||||
↓ formatCommandsWithinBudget() → 截断后的 Skill 列表注入 System Prompt
|
||||
↓ AI 选择匹配的 Skill
|
||||
↓ SkillTool.validateInput() → 名称校验 + 存在性检查
|
||||
↓ SkillTool.checkPermissions() → 四层权限检查
|
||||
↓ SkillTool.checkPermissions() → 五层权限检查
|
||||
↓ SkillTool.call() → inline 或 fork 执行
|
||||
↓ contextModifier() → 注入 allowedTools + model + effort
|
||||
↓ recordSkillUsage() → 更新使用频率排名
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
| 11 | BigQuery Metrics | `api.anthropic.com/api/claude_code/metrics` | HTTPS | 默认启用 |
|
||||
| 12 | MCP Proxy | `mcp-proxy.anthropic.com` | HTTPS+WS | 使用 MCP 工具时 |
|
||||
| 13 | MCP Registry | `api.anthropic.com/mcp-registry` | HTTPS | 查询 MCP 服务器时 |
|
||||
| 14 | Bing Search | `www.bing.com` | HTTPS | WebSearch 工具 |
|
||||
| 14 | Web Search Pages | `www.bing.com`, `search.brave.com` | HTTPS | WebSearch 工具,可通过 `WEB_SEARCH_ADAPTER=bing|brave` 切换 |
|
||||
| 15 | Google Cloud Storage (更新) | `storage.googleapis.com` | HTTPS | 版本检查 |
|
||||
| 16 | GitHub Raw (Changelog/Stats) | `raw.githubusercontent.com` | HTTPS | 更新提示 |
|
||||
| 17 | Claude in Chrome Bridge | `bridge.claudeusercontent.com` | WSS | Chrome 集成 |
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
- **端点**: `{region}-aiplatform.googleapis.com`
|
||||
- **认证**: `GoogleAuth` + `cloud-platform` scope
|
||||
- **文件**: `src/services/api/client.ts:228-298`
|
||||
- **文件**: `src/services/api/client.ts:221-298`
|
||||
|
||||
### 4. Azure Foundry
|
||||
|
||||
@@ -121,16 +121,20 @@ Anthropic 托管的 MCP 服务器代理。
|
||||
- **端点**: `https://api.anthropic.com/mcp-registry/v0/servers?version=latest&visibility=commercial`
|
||||
- **文件**: `src/services/mcp/officialRegistry.ts`
|
||||
|
||||
### 14. Bing Search
|
||||
### 14. Web Search Pages
|
||||
|
||||
WebSearch 工具的默认适配器,抓取 Bing 搜索结果。
|
||||
WebSearch 工具支持直接抓取 Bing 搜索结果页面,也支持通过 Brave 的 LLM Context API
|
||||
获取搜索上下文;可通过 `WEB_SEARCH_ADAPTER=bing|brave` 显式切换后端。
|
||||
|
||||
- **端点**: `https://www.bing.com/search?q={query}&setmkt=en-US`
|
||||
- **文件**: `src/tools/WebSearchTool/adapters/bingAdapter.ts`
|
||||
- **Bing 端点**: `https://www.bing.com/search?q={query}&setmkt=en-US`
|
||||
- **Brave 端点**: `https://api.search.brave.com/res/v1/llm/context?q={query}`
|
||||
- **文件**:
|
||||
- `packages/builtin-tools/src/tools/WebSearchTool/adapters/bingAdapter.ts`
|
||||
- `packages/builtin-tools/src/tools/WebSearchTool/adapters/braveAdapter.ts`
|
||||
|
||||
另外还有 Domain Blocklist 查询:
|
||||
- **端点**: `https://api.anthropic.com/api/web/domain_info?domain={domain}`
|
||||
- **文件**: `src/tools/WebFetchTool/utils.ts`
|
||||
- **文件**: `packages/builtin-tools/src/tools/WebFetchTool/utils.ts`
|
||||
|
||||
### 15. Google Cloud Storage (自动更新)
|
||||
|
||||
@@ -201,6 +205,7 @@ WebSearch 工具的默认适配器,抓取 Bing 搜索结果。
|
||||
| `{region}-aiplatform.googleapis.com` | Google Vertex AI | HTTPS |
|
||||
| `{resource}.services.ai.azure.com` | Azure Foundry | HTTPS |
|
||||
| `www.bing.com` | Bing 搜索 | HTTPS |
|
||||
| `search.brave.com` | Brave 搜索 | HTTPS |
|
||||
| `storage.googleapis.com` | 自动更新 | HTTPS |
|
||||
| `raw.githubusercontent.com` | Changelog / 插件统计 | HTTPS |
|
||||
| `bridge.claudeusercontent.com` | Chrome Bridge | WSS |
|
||||
|
||||
@@ -1,457 +0,0 @@
|
||||
# Feature 探索计划书
|
||||
|
||||
> 生成日期:2026-04-02
|
||||
> 代码库中已识别 89 个 feature flag,本文档按实现完整度和探索价值分级,制定探索优先级和路线图。
|
||||
>
|
||||
> **已完成**:BUDDY(✅ 2026-04-02)、TRANSCRIPT_CLASSIFIER / Auto Mode(✅ 2026-04-02)
|
||||
|
||||
---
|
||||
|
||||
## 一、总览
|
||||
|
||||
### 按实现状态分类
|
||||
|
||||
| 状态 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| 已实现/可用 | 11 | 代码完整,开启 feature 后可运行(可能需要 OAuth 等外部依赖) |
|
||||
| 部分实现 | 8 | 核心逻辑存在但关键模块为 stub,需要补全 |
|
||||
| 纯 Stub | 15 | 所有函数/工具返回空值,需要从零实现 |
|
||||
| N/A | 55+ | 内部基础设施、低引用量辅助功能,或反编译丢失过多 |
|
||||
|
||||
### 启用方式
|
||||
|
||||
所有 feature 通过环境变量启用:
|
||||
|
||||
```bash
|
||||
# 单个 feature
|
||||
FEATURE_BUDDY=1 bun run dev
|
||||
|
||||
# 多个 feature 组合
|
||||
FEATURE_KAIROS=1 FEATURE_PROACTIVE=1 FEATURE_FORK_SUBAGENT=1 bun run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、Tier 1 — 已实现/可用(优先探索)
|
||||
|
||||
### 2.1 KAIROS(常驻助手模式)⭐ 最高优先级
|
||||
|
||||
- **引用数**:154(全库最大)
|
||||
- **功能**:将 CLI 变为常驻后台助手,支持:
|
||||
- 持久化 bridge 会话(跨重启复用 session)
|
||||
- 后台执行任务(用户离开终端时继续工作)
|
||||
- 推送通知到移动端(任务完成/需要输入时)
|
||||
- 每日记忆日志 + `/dream` 知识蒸馏
|
||||
- 外部频道消息接入(Slack/Discord/Telegram)
|
||||
- **子 Feature**:
|
||||
|
||||
| 子 Feature | 引用 | 功能 |
|
||||
|-----------|------|------|
|
||||
| `KAIROS_BRIEF` | 39 | Brief 工具(`SendUserMessage`),结构化消息输出 |
|
||||
| `KAIROS_CHANNELS` | 19 | 外部频道消息接入 |
|
||||
| `KAIROS_PUSH_NOTIFICATION` | 4 | 移动端推送通知 |
|
||||
| `KAIROS_GITHUB_WEBHOOKS` | 3 | GitHub PR webhook 订阅 |
|
||||
| `KAIROS_DREAM` | 1 | 夜间记忆蒸馏 |
|
||||
|
||||
- **关键文件**:`src/assistant/`、`src/tools/BriefTool/`、`src/services/mcp/channelNotification.ts`、`src/memdir/memdir.ts`
|
||||
- **外部依赖**:Anthropic OAuth(claude.ai 订阅)、GrowthBook 特性门控
|
||||
- **探索命令**:`FEATURE_KAIROS=1 FEATURE_KAIROS_BRIEF=1 FEATURE_PROACTIVE=1 bun run dev`
|
||||
|
||||
**探索步骤**:
|
||||
1. 开启 feature,观察启动行为变化
|
||||
2. 测试 `/assistant`、`/brief` 命令
|
||||
3. 验证 BriefTool 输出模式
|
||||
4. 尝试频道消息接入
|
||||
5. 测试 `/dream` 记忆蒸馏
|
||||
|
||||
---
|
||||
|
||||
### ~~2.2 TRANSCRIPT_CLASSIFIER(Auto Mode 分类器)~~ ✅ 已完成
|
||||
|
||||
- **引用数**:108
|
||||
- **功能**:使用 LLM 对用户意图进行分类,实现 auto mode(自动决定工具权限)
|
||||
- **状态**:✅ prompt 模板已重建,功能完整可用(2026-04-02 完成)
|
||||
|
||||
---
|
||||
|
||||
### 2.3 VOICE_MODE(语音输入)
|
||||
|
||||
- **引用数**:46
|
||||
- **功能**:按键说话(Push-to-Talk),音频流式传输到 Anthropic STT 端点(Nova 3),实时转录显示
|
||||
- **当前状态**:**完整实现**,包括录音、WebSocket 流、转录插入
|
||||
- **关键文件**:`src/voice/voiceModeEnabled.ts`、`src/hooks/useVoice.ts`、`src/services/voiceStreamSTT.ts`
|
||||
- **外部依赖**:Anthropic OAuth(非 API key)、macOS 原生音频或 SoX
|
||||
- **探索命令**:`FEATURE_VOICE_MODE=1 bun run dev`
|
||||
- **默认快捷键**:长按空格键录音
|
||||
|
||||
**探索步骤**:
|
||||
1. 确认 OAuth token 可用
|
||||
2. 测试按住空格录音 → 释放后转录
|
||||
3. 验证实时中间转录显示
|
||||
4. 测试 `/voice` 命令切换
|
||||
|
||||
---
|
||||
|
||||
### 2.4 TEAMMEM(团队共享记忆)
|
||||
|
||||
- **引用数**:51
|
||||
- **功能**:基于 GitHub 仓库的团队共享记忆系统,`memory/team/` 目录双向同步到 Anthropic 服务器
|
||||
- **当前状态**:**完整实现**,包括增量同步、冲突解决、密钥扫描、路径穿越防护
|
||||
- **关键文件**:`src/services/teamMemorySync/`(index、watcher、secretScanner)、`src/memdir/teamMemPaths.ts`
|
||||
- **外部依赖**:Anthropic OAuth + GitHub remote(`getGithubRepo()`)
|
||||
- **探索命令**:`FEATURE_TEAMMEM=1 bun run dev`
|
||||
|
||||
**探索步骤**:
|
||||
1. 确认项目有 GitHub remote
|
||||
2. 开启后观察 `memory/team/` 目录创建
|
||||
3. 测试团队记忆写入和同步
|
||||
4. 验证密钥扫描防护
|
||||
|
||||
---
|
||||
|
||||
### 2.5 COORDINATOR_MODE(多 Agent 编排)
|
||||
|
||||
- **引用数**:32
|
||||
- **功能**:CLI 变为编排者,通过 AgentTool 派发任务给多个 worker 并行执行
|
||||
- **当前状态**:核心逻辑实现,worker agent 模块为 stub
|
||||
- **关键文件**:`src/coordinator/coordinatorMode.ts`(系统 prompt 完整)、`src/coordinator/workerAgent.ts`(stub)
|
||||
- **限制**:编排者只能使用 AgentTool/TaskStop/SendMessage,不能直接操作文件
|
||||
- **探索命令**:`FEATURE_COORDINATOR_MODE=1 CLAUDE_CODE_COORDINATOR_MODE=1 bun run dev`
|
||||
|
||||
**探索步骤**:
|
||||
1. 补全 `workerAgent.ts` stub
|
||||
2. 测试多 worker 并行任务派发
|
||||
3. 验证 worker 结果汇总
|
||||
|
||||
---
|
||||
|
||||
### 2.6 BRIDGE_MODE(远程控制)
|
||||
|
||||
- **引用数**:28
|
||||
- **功能**:本地 CLI 注册为 bridge 环境,可从 claude.ai 或其他控制面远程驱动
|
||||
- **当前状态**:v1(env-based)和 v2(env-less)实现均存在
|
||||
- **关键文件**:`src/bridge/bridgeEnabled.ts`、`src/bridge/replBridge.ts`(v1)、`src/bridge/remoteBridgeCore.ts`(v2)
|
||||
- **外部依赖**:claude.ai OAuth、GrowthBook 门控 `tengu_ccr_bridge`
|
||||
- **探索命令**:`FEATURE_BRIDGE_MODE=1 bun run dev`
|
||||
|
||||
---
|
||||
|
||||
### 2.7 FORK_SUBAGENT(上下文继承子 Agent)
|
||||
|
||||
- **引用数**:4
|
||||
- **功能**:AgentTool 生成 fork 子 agent,继承父级完整对话上下文,优化 prompt cache
|
||||
- **当前状态**:**完整实现**(`forkSubagent.ts`),支持 worktree 隔离通知、递归防护
|
||||
- **关键文件**:`src/tools/AgentTool/forkSubagent.ts`
|
||||
- **探索命令**:`FEATURE_FORK_SUBAGENT=1 bun run dev`
|
||||
|
||||
---
|
||||
|
||||
### 2.8 TOKEN_BUDGET(Token 预算控制)
|
||||
|
||||
- **引用数**:9
|
||||
- **功能**:解析用户指定的 token 预算(如 "spend 2M tokens"),自动持续工作直到达到目标
|
||||
- **当前状态**:解析器**完整实现**,支持简写和详细语法;QueryEngine 中的周转逻辑已连接
|
||||
- **关键文件**:`src/utils/tokenBudget.ts`、`src/QueryEngine.ts`
|
||||
- **探索命令**:`FEATURE_TOKEN_BUDGET=1 bun run dev`
|
||||
|
||||
---
|
||||
|
||||
### 2.9 MCP_SKILLS(MCP 技能发现)
|
||||
|
||||
- **引用数**:9
|
||||
- **功能**:将 MCP 服务器提供的 prompt 类型命令筛选为可调用技能
|
||||
- **当前状态**:**功能性实现**(config 门控筛选器)
|
||||
- **关键文件**:`src/commands.ts`(`getMcpSkillCommands()`)
|
||||
- **探索命令**:`FEATURE_MCP_SKILLS=1 bun run dev`
|
||||
|
||||
---
|
||||
|
||||
### 2.10 TREE_SITTER_BASH(Bash AST 解析)
|
||||
|
||||
- **引用数**:3
|
||||
- **功能**:纯 TypeScript bash 命令 AST 解析器,用于 fail-closed 权限匹配
|
||||
- **当前状态**:**完整实现**(`bashParser.ts` ~2000行 + `ast.ts` ~400行)
|
||||
- **关键文件**:`src/utils/vendor/tree-sitter-bash/`
|
||||
- **探索命令**:`FEATURE_TREE_SITTER_BASH=1 bun run dev`
|
||||
|
||||
---
|
||||
|
||||
### ~~2.11 BUDDY(虚拟伙伴)~~ ✅ 已完成
|
||||
|
||||
- **引用数**:16
|
||||
- **功能**:`/buddy` 命令,支持 hatch/rehatch/pet/mute/unmute
|
||||
- **状态**:✅ 已合入,功能完整可用(2026-04-02 完成)
|
||||
|
||||
---
|
||||
|
||||
## 三、Tier 2 — 部分实现(需要补全)
|
||||
|
||||
### 3.1 PROACTIVE(主动模式)
|
||||
|
||||
- **引用数**:37
|
||||
- **功能**:Tick 驱动的自主代理,定时唤醒执行工作,配合 SleepTool 控制节奏
|
||||
- **当前状态**:核心模块 `src/proactive/index.ts` **全部 stub**(activate/deactivate/pause 返回 false 或空操作)
|
||||
- **依赖**:与 KAIROS 强绑定(所有检查都是 `feature('PROACTIVE') || feature('KAIROS')`)
|
||||
- **补全工作量**:中等 — 需要实现 tick 生成、SleepTool 集成、暂停/恢复逻辑
|
||||
|
||||
### 3.2 BASH_CLASSIFIER(Bash 命令分类器)
|
||||
|
||||
- **引用数**:45
|
||||
- **功能**:LLM 驱动的 bash 命令意图分类(允许/拒绝/询问)
|
||||
- **当前状态**:`bashClassifier.ts` **全部 stub**(`matches: false`)
|
||||
- **补全工作量**:大 — 需要 LLM 调用实现、prompt 设计
|
||||
|
||||
### 3.3 ULTRAPLAN(增强规划)
|
||||
|
||||
- **引用数**:10
|
||||
- **功能**:关键字触发增强计划模式,输入 "ultraplan" 自动转为 plan
|
||||
- **当前状态**:关键字检测**完整实现**,`/ultraplan` 命令**为 stub**
|
||||
- **补全工作量**:小 — 只需实现命令处理逻辑
|
||||
|
||||
### 3.4 EXPERIMENTAL_SKILL_SEARCH(技能语义搜索)
|
||||
|
||||
- **引用数**:21
|
||||
- **功能**:DiscoverSkills 工具,根据当前任务语义搜索可用技能
|
||||
- **当前状态**:布线完整,核心搜索逻辑 stub
|
||||
- **补全工作量**:中等 — 需要实现搜索引擎和索引
|
||||
|
||||
### 3.5 CONTEXT_COLLAPSE(上下文折叠)
|
||||
|
||||
- **引用数**:20
|
||||
- **功能**:CtxInspectTool 让模型内省上下文窗口大小,优化压缩决策
|
||||
- **当前状态**:工具 stub,HISTORY_SNIP 子功能也 stub
|
||||
- **补全工作量**:中等
|
||||
|
||||
### 3.6 WORKFLOW_SCRIPTS(工作流自动化)
|
||||
|
||||
- **引用数**:10
|
||||
- **功能**:基于文件的自动化工作流 + `/workflows` 命令
|
||||
- **当前状态**:WorkflowTool、命令、加载器全部 stub
|
||||
- **补全工作量**:大 — 需要从零设计工作流 DSL
|
||||
|
||||
### 3.7 WEB_BROWSER_TOOL(浏览器工具)
|
||||
|
||||
- **引用数**:4
|
||||
- **功能**:模型可调用浏览器工具导航和交互网页
|
||||
- **当前状态**:工具注册存在,实现 stub
|
||||
- **补全工作量**:大
|
||||
|
||||
### 3.8 DAEMON(后台守护进程)
|
||||
|
||||
- **引用数**:3
|
||||
- **功能**:后台守护进程 + 远程控制服务器
|
||||
- **当前状态**:只有条件导入布线,无实现
|
||||
- **补全工作量**:极大
|
||||
|
||||
---
|
||||
|
||||
## 四、Tier 3 — 纯 Stub / N/A(低优先级)
|
||||
|
||||
| Feature | 引用 | 状态 | 说明 |
|
||||
|---------|------|------|------|
|
||||
| CHICAGO_MCP | 16 | N/A | Anthropic 内部 MCP 基础设施 |
|
||||
| UDS_INBOX | 17 | Stub | Unix 域套接字对等消息 |
|
||||
| MONITOR_TOOL | 13 | Stub | 文件/进程监控工具 |
|
||||
| BG_SESSIONS | 11 | Stub | 后台会话管理 |
|
||||
| SHOT_STATS | 10 | 无实现 | 逐 prompt 统计 |
|
||||
| EXTRACT_MEMORIES | 7 | 无实现 | 自动记忆提取 |
|
||||
| TEMPLATES | 6 | Stub | 项目/提示模板 |
|
||||
| LODESTONE | 6 | N/A | 内部基础设施 |
|
||||
| STREAMLINED_OUTPUT | 1 | — | 精简输出模式 |
|
||||
| HOOK_PROMPTS | 1 | — | Hook 提示词 |
|
||||
| CCR_AUTO_CONNECT | 3 | — | CCR 自动连接 |
|
||||
| CCR_MIRROR | 4 | — | CCR 镜像模式 |
|
||||
| CCR_REMOTE_SETUP | 1 | — | CCR 远程设置 |
|
||||
| NATIVE_CLIPBOARD_IMAGE | 2 | — | 原生剪贴板图片 |
|
||||
| CONNECTOR_TEXT | 7 | — | 连接器文本 |
|
||||
|
||||
以及其余 40+ 个低引用量 feature。
|
||||
|
||||
---
|
||||
|
||||
## 五、探索路线图
|
||||
|
||||
### Phase 1:快速验证(无外部依赖)
|
||||
|
||||
> 目标:确认代码可以正常运行,体验基本功能
|
||||
|
||||
| 优先级 | Feature | 命令 | 预期效果 |
|
||||
|--------|---------|------|----------|
|
||||
| 1 | BUDDY | `FEATURE_BUDDY=1 bun run dev` | `/buddy hatch` 生成伙伴 |
|
||||
| 2 | FORK_SUBAGENT | `FEATURE_FORK_SUBAGENT=1 bun run dev` | Agent 可生成上下文继承的子任务 |
|
||||
| 3 | TOKEN_BUDGET | `FEATURE_TOKEN_BUDGET=1 bun run dev` | 输入 "spend 500k tokens" 测试自动持续 |
|
||||
| 4 | TREE_SITTER_BASH | `FEATURE_TREE_SITTER_BASH=1 bun run dev` | 更精确的 bash 权限匹配 |
|
||||
| 5 | MCP_SKILLS | `FEATURE_MCP_SKILLS=1 bun run dev` | MCP 服务器 prompt 提升为技能 |
|
||||
|
||||
### Phase 2:核心功能探索(需要 OAuth)
|
||||
|
||||
> 目标:体验 KAIROS 全套能力
|
||||
|
||||
| 优先级 | Feature | 命令 | 预期效果 |
|
||||
|--------|---------|------|----------|
|
||||
| 1 | TRANSCRIPT_CLASSIFIER | `FEATURE_TRANSCRIPT_CLASSIFIER=1 bun run dev` | Auto mode 自动激活 |
|
||||
| 2 | KAIROS 全套 | `FEATURE_KAIROS=1 FEATURE_KAIROS_BRIEF=1 FEATURE_KAIROS_CHANNELS=1 FEATURE_PROACTIVE=1 bun run dev` | 常驻助手 + Brief 输出 + 频道消息 |
|
||||
| 3 | VOICE_MODE | `FEATURE_VOICE_MODE=1 bun run dev` | 按空格说话 |
|
||||
| 4 | TEAMMEM | `FEATURE_TEAMMEM=1 bun run dev` | 团队记忆同步 |
|
||||
| 5 | COORDINATOR_MODE | `FEATURE_COORDINATOR_MODE=1 CLAUDE_CODE_COORDINATOR_MODE=1 bun run dev` | 多 agent 编排 |
|
||||
|
||||
### Phase 3:Stub 补全开发
|
||||
|
||||
> 目标:将高价值 stub 实现为可用功能
|
||||
|
||||
| 优先级 | Feature | 补全难度 | 价值 |
|
||||
|--------|---------|----------|------|
|
||||
| 1 | PROACTIVE | 中 | 自主工作能力 |
|
||||
| 2 | ULTRAPLAN | 小 | 增强规划 |
|
||||
| 3 | CONTEXT_COLLAPSE | 中 | 长对话优化 |
|
||||
| 4 | EXPERIMENTAL_SKILL_SEARCH | 中 | 技能发现 |
|
||||
| 5 | BASH_CLASSIFIER | 大 | 安全增强 |
|
||||
|
||||
---
|
||||
|
||||
## 六、推荐组合方案
|
||||
|
||||
### "全功能助手"组合
|
||||
|
||||
```bash
|
||||
FEATURE_KAIROS=1 \
|
||||
FEATURE_KAIROS_BRIEF=1 \
|
||||
FEATURE_KAIROS_CHANNELS=1 \
|
||||
FEATURE_KAIROS_PUSH_NOTIFICATION=1 \
|
||||
FEATURE_PROACTIVE=1 \
|
||||
FEATURE_FORK_SUBAGENT=1 \
|
||||
FEATURE_TOKEN_BUDGET=1 \
|
||||
FEATURE_TRANSCRIPT_CLASSIFIER=1 \
|
||||
FEATURE_BUDDY=1 \
|
||||
bun run dev
|
||||
```
|
||||
|
||||
### "多 Agent 协作"组合
|
||||
|
||||
```bash
|
||||
FEATURE_COORDINATOR_MODE=1 \
|
||||
FEATURE_FORK_SUBAGENT=1 \
|
||||
FEATURE_BRIDGE_MODE=1 \
|
||||
FEATURE_BG_SESSIONS=1 \
|
||||
CLAUDE_CODE_COORDINATOR_MODE=1 \
|
||||
bun run dev
|
||||
```
|
||||
|
||||
### "开发者增强"组合
|
||||
|
||||
```bash
|
||||
FEATURE_TRANSCRIPT_CLASSIFIER=1 \
|
||||
FEATURE_TREE_SITTER_BASH=1 \
|
||||
FEATURE_TOKEN_BUDGET=1 \
|
||||
FEATURE_MCP_SKILLS=1 \
|
||||
FEATURE_CONTEXT_COLLAPSE=1 \
|
||||
bun run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、风险与注意事项
|
||||
|
||||
1. **OAuth 依赖**:KAIROS、VOICE_MODE、TEAMMEM、BRIDGE_MODE 需要 Anthropic OAuth 认证(claude.ai 订阅),API key 用户无法使用
|
||||
2. **GrowthBook 门控**:部分功能(VOICE_MODE 的 `tengu_cobalt_frost`、TEAMMEM 的 `tengu_herring_clock`)即使 feature flag 开启,还需要服务端 GrowthBook 开关
|
||||
3. **反编译不完整**:所有"已实现"功能均为反编译产物,可能存在运行时错误,需要逐个验证
|
||||
4. **Proactive stub**:KAIROS 的自主工作能力依赖 PROACTIVE,但 PROACTIVE 核心是 stub,需先补全
|
||||
5. **tsc 错误**:代码库有 ~1341 个 TypeScript 编译错误(来自反编译),不影响 Bun 运行时但在 IDE 中会有大量红线
|
||||
|
||||
---
|
||||
|
||||
## 附录:Feature Flag 完整列表
|
||||
|
||||
共 89 个 feature flag(按引用数降序):
|
||||
|
||||
| Feature | 引用 | Tier |
|
||||
|---------|------|------|
|
||||
| KAIROS | 154 | 1 |
|
||||
| TRANSCRIPT_CLASSIFIER | 108 | 1 |
|
||||
| TEAMMEM | 51 | 1 |
|
||||
| VOICE_MODE | 46 | 1 |
|
||||
| BASH_CLASSIFIER | 45 | 2 |
|
||||
| KAIROS_BRIEF | 39 | 1 |
|
||||
| PROACTIVE | 37 | 2 |
|
||||
| COORDINATOR_MODE | 32 | 1 |
|
||||
| BRIDGE_MODE | 28 | 1 |
|
||||
| EXPERIMENTAL_SKILL_SEARCH | 21 | 2 |
|
||||
| CONTEXT_COLLAPSE | 20 | 2 |
|
||||
| KAIROS_CHANNELS | 19 | 1 |
|
||||
| UDS_INBOX | 17 | 3 |
|
||||
| CHICAGO_MCP | 16 | 3 |
|
||||
| BUDDY | 16 | 1 |
|
||||
| HISTORY_SNIP | 15 | 2 |
|
||||
| MONITOR_TOOL | 13 | 3 |
|
||||
| COMMIT_ATTRIBUTION | 12 | — |
|
||||
| CACHED_MICROCOMPACT | 12 | — |
|
||||
| BG_SESSIONS | 11 | 3 |
|
||||
| WORKFLOW_SCRIPTS | 10 | 2 |
|
||||
| ULTRAPLAN | 10 | 2 |
|
||||
| SHOT_STATS | 10 | 3 |
|
||||
| TOKEN_BUDGET | 9 | 1 |
|
||||
| PROMPT_CACHE_BREAK_DETECTION | 9 | — |
|
||||
| MCP_SKILLS | 9 | 1 |
|
||||
| EXTRACT_MEMORIES | 7 | 3 |
|
||||
| CONNECTOR_TEXT | 7 | — |
|
||||
| TEMPLATES | 6 | 3 |
|
||||
| LODESTONE | 6 | 3 |
|
||||
| TREE_SITTER_BASH_SHADOW | 5 | — |
|
||||
| QUICK_SEARCH | 5 | — |
|
||||
| MESSAGE_ACTIONS | 5 | — |
|
||||
| DOWNLOAD_USER_SETTINGS | 5 | — |
|
||||
| DIRECT_CONNECT | 5 | — |
|
||||
| WEB_BROWSER_TOOL | 4 | 2 |
|
||||
| VERIFICATION_AGENT | 4 | — |
|
||||
| TERMINAL_PANEL | 4 | — |
|
||||
| SSH_REMOTE | 4 | — |
|
||||
| REVIEW_ARTIFACT | 4 | — |
|
||||
| REACTIVE_COMPACT | 4 | — |
|
||||
| KAIROS_PUSH_NOTIFICATION | 4 | 1 |
|
||||
| HISTORY_PICKER | 4 | — |
|
||||
| FORK_SUBAGENT | 4 | 1 |
|
||||
| CCR_MIRROR | 4 | — |
|
||||
| TREE_SITTER_BASH | 3 | 1 |
|
||||
| MEMORY_SHAPE_TELEMETRY | 3 | — |
|
||||
| MCP_RICH_OUTPUT | 3 | — |
|
||||
| KAIROS_GITHUB_WEBHOOKS | 3 | 1 |
|
||||
| FILE_PERSISTENCE | 3 | — |
|
||||
| DAEMON | 3 | 2 |
|
||||
| CCR_AUTO_CONNECT | 3 | — |
|
||||
| UPLOAD_USER_SETTINGS | 2 | — |
|
||||
| POWERSHELL_AUTO_MODE | 2 | — |
|
||||
| OVERFLOW_TEST_TOOL | 2 | — |
|
||||
| NEW_INIT | 2 | — |
|
||||
| NATIVE_CLIPBOARD_IMAGE | 2 | — |
|
||||
| HARD_FAIL | 2 | — |
|
||||
| ENHANCED_TELEMETRY_BETA | 2 | — |
|
||||
| COWORKER_TYPE_TELEMETRY | 2 | — |
|
||||
| BREAK_CACHE_COMMAND | 2 | — |
|
||||
| AWAY_SUMMARY | 2 | — |
|
||||
| AUTO_THEME | 2 | — |
|
||||
| ALLOW_TEST_VERSIONS | 2 | — |
|
||||
| AGENT_TRIGGERS_REMOTE | 2 | — |
|
||||
| AGENT_MEMORY_SNAPSHOT | 2 | — |
|
||||
| UNATTENDED_RETRY | 1 | — |
|
||||
| ULTRATHINK | 1 | — |
|
||||
| TORCH | 1 | — |
|
||||
| STREAMLINED_OUTPUT | 1 | — |
|
||||
| SLOW_OPERATION_LOGGING | 1 | — |
|
||||
| SKILL_IMPROVEMENT | 1 | — |
|
||||
| SELF_HOSTED_RUNNER | 1 | — |
|
||||
| RUN_SKILL_GENERATOR | 1 | — |
|
||||
| PERFETTO_TRACING | 1 | — |
|
||||
| NATIVE_CLIENT_ATTESTATION | 1 | — |
|
||||
| KAIROS_DREAM | 1 | 1 |
|
||||
| IS_LIBC_MUSL | 1 | — |
|
||||
| IS_LIBC_GLIBC | 1 | — |
|
||||
| HOOK_PROMPTS | 1 | — |
|
||||
| DUMP_SYSTEM_PROMPT | 1 | — |
|
||||
| COMPACTION_REMINDERS | 1 | — |
|
||||
| CCR_REMOTE_SETUP | 1 | — |
|
||||
| BYOC_ENVIRONMENT_RUNNER | 1 | — |
|
||||
| BUILTIN_EXPLORE_PLAN_AGENTS | 1 | — |
|
||||
| BUILDING_CLAUDE_APPS | 1 | — |
|
||||
| ANTI_DISTILLATION_CC | 1 | — |
|
||||
| AGENT_TRIGGERS | 1 | — |
|
||||
| ABLATION_BASELINE | 1 | — |
|
||||
File diff suppressed because it is too large
Load Diff
201
docs/features/acp-link.md
Normal file
201
docs/features/acp-link.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# acp-link — ACP 代理服务器
|
||||
|
||||
> 源码目录:`packages/acp-link/`
|
||||
> PR: #292
|
||||
> 新增时间:2026-04-18
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
`acp-link` 是一个 ACP (Agent Client Protocol) 代理服务器,将 WebSocket 客户端桥接到 ACP agent 的 stdio 接口。它让 ACP agent(如 Claude Code)可以通过 WebSocket 远程访问,而不仅限于本地 stdio。
|
||||
|
||||
### 核心特性
|
||||
|
||||
- **WebSocket → stdio 桥接**:将浏览器/远程客户端的 WebSocket 连接转换为 ACP agent 的 stdin/stdout NDJSON 流
|
||||
- **会话管理**:创建、加载、恢复、列出、关闭会话
|
||||
- **权限审批流程**:客户端可远程审批 agent 的工具权限请求
|
||||
- **RCS 集成**:可与 Remote Control Server (RCS) 连接,将 ACP agent 注册到 RCS 并通过 Web UI 交互
|
||||
- **HTTPS 支持**:内置自签名证书生成,支持安全连接
|
||||
- **Token 认证**:自动生成或通过环境变量配置认证 token
|
||||
|
||||
## 二、架构
|
||||
|
||||
### 独立模式
|
||||
|
||||
```
|
||||
┌──────────────────┐ WebSocket ┌──────────────────┐ stdio/NDJSON ┌──────────────┐
|
||||
│ 浏览器/客户端 │ ◄──────────────►│ acp-link │ ◄────────────────►│ ACP Agent │
|
||||
│ (WS Client) │ ws://host:port │ (Proxy Server) │ spawn subprocess │ (Claude等) │
|
||||
└──────────────────┘ └──────────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
### RCS 集成模式
|
||||
|
||||
```
|
||||
┌──────────────┐ WebSocket ┌──────────────────┐ stdio/NDJSON ┌──────────────┐
|
||||
│ RCS Web UI │ ◄──────────────►│ Remote Control │ ◄─────────────────►│ acp-link │
|
||||
│ (/code/*) │ ACP Relay WS │ Server (RCS) │ ACP events │ + Agent │
|
||||
└──────────────┘ └──────────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
### 文件结构
|
||||
|
||||
```
|
||||
packages/acp-link/
|
||||
├── src/
|
||||
│ ├── server.ts # 主服务器:WS 连接管理、会话管理、权限处理、消息桥接
|
||||
│ ├── rcs-upstream.ts # RCS 上游客户端:REST 注册 + WS identify 两步流程
|
||||
│ ├── cert.ts # TLS 证书生成(自签名)
|
||||
│ ├── logger.ts # 日志模块
|
||||
│ ├── types.ts # JSON-RPC 和 ACP 协议类型定义
|
||||
│ ├── cli/
|
||||
│ │ ├── bin.ts # CLI 入口
|
||||
│ │ ├── command.ts # 命令行参数解析
|
||||
│ │ ├── app.ts # 应用启动
|
||||
│ │ └── context.ts # 上下文配置
|
||||
│ └── __tests__/ # 测试(cert, server, types)
|
||||
├── package.json
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
## 三、安装与使用
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
# 直接运行(在 monorepo 中)
|
||||
# 注意:claude 本身不支持 ACP,需要用 ccb-bun --acp 启动 ACP agent
|
||||
bun packages/acp-link/src/cli/bin.ts ccb-bun -- --acp
|
||||
|
||||
# 指定端口和主机
|
||||
acp-link --port 9000 --host 0.0.0.0 ccb-bun -- --acp
|
||||
|
||||
# 启用 HTTPS(自签名证书)
|
||||
acp-link --https ccb-bun -- --acp
|
||||
|
||||
# 调试模式
|
||||
acp-link --debug ccb-bun -- --acp
|
||||
```
|
||||
|
||||
### CLI 参考
|
||||
|
||||
```
|
||||
USAGE
|
||||
acp-link [--port value] [--host value] [--debug] [--no-auth] [--https] <command>...
|
||||
acp-link --help
|
||||
acp-link --version
|
||||
|
||||
FLAGS
|
||||
[--port] Port to listen on [default = 9315]
|
||||
[--host] Host to bind to [default = localhost]
|
||||
[--debug] Enable debug logging to file
|
||||
[--no-auth] Disable authentication (dangerous)
|
||||
[--https] Enable HTTPS with self-signed cert
|
||||
-h --help Print help information and exit
|
||||
-v --version Print version information and exit
|
||||
|
||||
ARGUMENTS
|
||||
command... Agent command followed by its arguments (e.g. "ccb-bun -- --acp")
|
||||
```
|
||||
|
||||
## 四、认证
|
||||
|
||||
默认启动时自动生成随机 token。客户端连接时需通过 query 参数传递:
|
||||
|
||||
```
|
||||
ws://localhost:9315/ws?token=<your-token>
|
||||
```
|
||||
|
||||
配置固定 token:
|
||||
|
||||
```bash
|
||||
ACP_AUTH_TOKEN=my-fixed-token acp-link ccb-bun -- --acp
|
||||
```
|
||||
|
||||
禁用认证(不推荐,仅用于开发):
|
||||
|
||||
```bash
|
||||
acp-link --no-auth ccb-bun -- --acp
|
||||
```
|
||||
|
||||
## 五、RCS 集成
|
||||
|
||||
acp-link 支持将 ACP agent 注册到 Remote Control Server,通过 Web UI 远程操控。
|
||||
|
||||
### 连接方式
|
||||
|
||||
```bash
|
||||
# 通过环境变量配置 RCS 连接
|
||||
ACP_RCS_URL=http://localhost:3000 \
|
||||
ACP_RCS_TOKEN=sk-rcs-your-key \
|
||||
acp-link ccb-bun -- --acp
|
||||
```
|
||||
|
||||
### 注册流程(两步)
|
||||
|
||||
1. **REST 注册**:通过 `POST /v1/environments/bridge` 向 RCS 注册环境
|
||||
2. **WS identify**:建立 WebSocket 连接后发送 `identify` 消息(携带 agentId),替代完整 `register`
|
||||
|
||||
```
|
||||
acp-link RCS
|
||||
│ │
|
||||
│── POST /v1/environments/bridge ──►│ (REST 注册)
|
||||
│◄── { agentId, sessionId } ───────│
|
||||
│ │
|
||||
│── WS connect ─────────────────►│ (WebSocket)
|
||||
│── identify { agentId } ────────►│ (WS 标识)
|
||||
│◄── identified ─────────────────│
|
||||
│ │
|
||||
│── ACP events ─────────────────►│ (双向消息转发)
|
||||
│◄── user prompts/permissions ───│
|
||||
```
|
||||
|
||||
## 六、权限模式
|
||||
|
||||
### permissionMode 传递链
|
||||
|
||||
权限模式通过整条链路传递:Web UI → RCS → acp-link → ACP agent。
|
||||
|
||||
支持的权限模式:
|
||||
- `default` — 每次请求权限确认
|
||||
- `auto` — 自动判断
|
||||
- `acceptEdits` — 自动接受编辑
|
||||
- `plan` — 规划模式
|
||||
- `dontAsk` — 不询问
|
||||
- `bypassPermissions` — 绕过权限(需 sandbox 环境)
|
||||
|
||||
### fallback 链
|
||||
|
||||
当客户端未显式传递 permissionMode 时,使用以下 fallback 链:
|
||||
|
||||
```
|
||||
客户端传值 > config.permissionMode > ACP_PERMISSION_MODE 环境变量
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
ACP_PERMISSION_MODE=auto acp-link ccb-bun -- --acp
|
||||
```
|
||||
|
||||
## 七、权限管道(2026-04-18 改进)
|
||||
|
||||
### 模式同步
|
||||
|
||||
`applySessionMode` 在 agent 切换权限模式时同步 `appState.toolPermissionContext.mode`,确保内部权限上下文与 ACP 客户端状态一致。
|
||||
|
||||
### 统一权限流水线
|
||||
|
||||
`createAcpCanUseTool` 接入 `hasPermissionsToUseTool` 统一权限流水线,替代原来分散的处理逻辑。支持 `onModeChange` 回调,模式变更时实时同步。
|
||||
|
||||
### bypass 检测
|
||||
|
||||
`bypassPermissions` 模式增加可用性检测 — 仅在非 root 或 sandbox 环境中允许启用,防止权限绕过的安全风险。
|
||||
|
||||
## 八、环境变量
|
||||
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| `ACP_AUTH_TOKEN` | 固定认证 token(默认自动生成) |
|
||||
| `ACP_PERMISSION_MODE` | 默认权限模式 fallback |
|
||||
| `ACP_RCS_URL` | RCS 服务器地址(启用 RCS 集成) |
|
||||
| `ACP_RCS_TOKEN` | RCS API token |
|
||||
189
docs/features/acp-zed.md
Normal file
189
docs/features/acp-zed.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# ACP (Agent Client Protocol) — Zed / IDE 集成
|
||||
|
||||
> Feature Flag: `FEATURE_ACP=1`(build 和 dev 模式默认启用)
|
||||
> 实现状态:可用(支持 Zed、Cursor 等 ACP 客户端)
|
||||
> 源码目录:`src/services/acp/`
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
ACP (Agent Client Protocol) 是一种标准化的 stdio 协议,允许 IDE 和编辑器通过 stdin/stdout 的 NDJSON 流驱动 AI Agent。CCB 实现了完整的 ACP agent 端,可以被 Zed、Cursor 等支持 ACP 的客户端直接调用。
|
||||
|
||||
### 核心特性
|
||||
|
||||
- **会话管理**:新建 / 恢复 / 加载 / 分叉 / 关闭会话
|
||||
- **历史回放**:恢复会话时自动加载并回放对话历史
|
||||
- **权限桥接**:ACP 客户端的权限决策映射到 CCB 的工具权限系统
|
||||
- **斜杠命令 & Skills**:加载真实命令列表,支持 `/commit`、`/review` 等 prompt 型 skill
|
||||
- **Context Window 跟踪**:精确的 usage_update,含 model prefix matching
|
||||
- **Prompt 排队**:支持连续发送多条 prompt,自动排队处理
|
||||
- **模式切换**:auto / default / acceptEdits / plan / dontAsk / bypassPermissions
|
||||
- **模型切换**:运行时切换 AI 模型
|
||||
|
||||
## 二、架构
|
||||
|
||||
```
|
||||
┌──────────────┐ NDJSON/stdio ┌──────────────────┐
|
||||
│ Zed / IDE │ ◄────────────────► │ CCB ACP Agent │
|
||||
│ (Client) │ stdin / stdout │ (Agent) │
|
||||
└──────────────┘ │ │
|
||||
│ entry.ts │ ← stdio → NDJSON stream
|
||||
│ agent.ts │ ← ACP protocol handler
|
||||
│ bridge.ts │ ← SDKMessage → ACP SessionUpdate
|
||||
│ permissions.ts │ ← 权限桥接
|
||||
│ utils.ts │ ← 通用工具
|
||||
│ │
|
||||
│ QueryEngine │ ← 内部查询引擎
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
### 文件职责
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `entry.ts` | 入口,创建 stdio → NDJSON stream,启动 `AgentSideConnection` |
|
||||
| `agent.ts` | 实现 ACP `Agent` 接口:会话 CRUD、prompt、cancel、模式/模型切换 |
|
||||
| `bridge.ts` | `SDKMessage` → ACP `SessionUpdate` 转换:文本/思考/工具/用量/编辑 diff |
|
||||
| `permissions.ts` | ACP `requestPermission()` → CCB `CanUseToolFn` 桥接 |
|
||||
| `utils.ts` | Pushable、流转换、权限模式解析、session fingerprint、路径显示 |
|
||||
|
||||
## 三、配置 Zed 编辑器
|
||||
|
||||
### 3.1 Zed settings.json 配置
|
||||
|
||||
打开 Zed 的 `settings.json`(`Cmd+,` → Open Settings),添加 `agent_servers` 配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"ccb": {
|
||||
"type": "custom",
|
||||
"command": "ccb",
|
||||
"args": ["--acp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 API 认证配置
|
||||
|
||||
CCB 的 ACP agent 在启动时会自动加载 `settings.json` 中的环境变量(`ANTHROPIC_BASE_URL`、`ANTHROPIC_AUTH_TOKEN` 等)。确保已通过 `/login` 配置好 API 供应商。
|
||||
|
||||
也可通过环境变量传入:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"claude-code": {
|
||||
"command": "ccb",
|
||||
"args": ["--acp"],
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://api.example.com/v1",
|
||||
"ANTHROPIC_AUTH_TOKEN": "sk-xxx"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 在 Zed 中使用
|
||||
|
||||
1. 配置完成后重启 Zed
|
||||
2. 打开任意项目目录
|
||||
3. 按 `Cmd+'`(macOS)或 `Ctrl+'`(Linux)打开 Agent Panel
|
||||
4. 在 Agent Panel 顶部的下拉菜单中选择 **claude-code**
|
||||
5. 开始对话
|
||||
|
||||
### 3.5 功能说明
|
||||
|
||||
| 功能 | 操作 |
|
||||
|------|------|
|
||||
| 对话 | 在 Agent Panel 中直接输入消息 |
|
||||
| 斜杠命令 | 输入 `/` 查看可用 skills 列表(如 `/commit`、`/review`) |
|
||||
| 工具权限 | 弹出权限请求时选择 Allow / Reject / Always Allow |
|
||||
| 模式切换 | 通过 Agent Panel 的设置菜单切换 auto/default/plan 等模式 |
|
||||
| 模型切换 | 通过 Agent Panel 的设置菜单切换 AI 模型 |
|
||||
| 会话恢复 | 关闭重开 Zed 后,之前的会话可自动恢复(含历史消息) |
|
||||
|
||||
## 四、配置其他 ACP 客户端
|
||||
|
||||
ACP 是开放协议,任何支持 ACP 的客户端都可以连接 CCB。通用配置模式:
|
||||
|
||||
```
|
||||
命令: ccb --acp
|
||||
参数: ["--acp"]
|
||||
通信: stdin/stdout NDJSON
|
||||
协议版本: ACP v1
|
||||
```
|
||||
|
||||
### 4.1 Cursor
|
||||
|
||||
在 Cursor 的设置中配置 MCP / Agent Server,使用同样的 `ccb --acp` 命令。
|
||||
|
||||
### 4.2 自定义客户端
|
||||
|
||||
使用 `@agentclientprotocol/sdk` 可以快速构建 ACP 客户端:
|
||||
|
||||
```typescript
|
||||
import { ClientSideConnection, ndJsonStream } from '@agentclientprotocol/sdk'
|
||||
|
||||
// 创建连接(将 ccb --acp 作为子进程启动)
|
||||
const child = spawn('ccb', ['--acp'])
|
||||
const stream = ndJsonStream(
|
||||
Writable.toWeb(child.stdin),
|
||||
Readable.toWeb(child.stdout),
|
||||
)
|
||||
|
||||
const client = new ClientSideConnection(stream)
|
||||
|
||||
// 初始化
|
||||
await client.initialize({ clientCapabilities: {} })
|
||||
|
||||
// 创建会话
|
||||
const { sessionId } = await client.newSession({
|
||||
cwd: '/path/to/project',
|
||||
})
|
||||
|
||||
// 发送 prompt
|
||||
const response = await client.prompt({
|
||||
sessionId,
|
||||
prompt: [{ type: 'text', text: 'Hello, explain this project' }],
|
||||
})
|
||||
|
||||
// 监听 session 更新
|
||||
client.on('sessionUpdate', (update) => {
|
||||
console.log('Update:', update)
|
||||
})
|
||||
```
|
||||
|
||||
## 五、ACP 协议支持矩阵
|
||||
|
||||
| 方法 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| `initialize` | ✅ | 返回 agent 信息和能力 |
|
||||
| `authenticate` | ✅ | 无需认证(自托管) |
|
||||
| `newSession` | ✅ | 创建新会话 |
|
||||
| `resumeSession` | ✅ | 恢复已有会话(含历史回放) |
|
||||
| `loadSession` | ✅ | 加载指定会话(含历史回放) |
|
||||
| `listSessions` | ✅ | 列出可用会话 |
|
||||
| `forkSession` | ✅ | 分叉会话 |
|
||||
| `closeSession` | ✅ | 关闭会话 |
|
||||
| `prompt` | ✅ | 发送消息,支持排队 |
|
||||
| `cancel` | ✅ | 取消当前/排队的 prompt |
|
||||
| `setSessionMode` | ✅ | 切换权限模式 |
|
||||
| `setSessionModel` | ✅ | 切换 AI 模型 |
|
||||
| `setSessionConfigOption` | ✅ | 动态修改配置 |
|
||||
|
||||
### SessionUpdate 类型
|
||||
|
||||
| 类型 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| `agent_message_chunk` | ✅ | 助手文本消息 |
|
||||
| `agent_thought_chunk` | ✅ | 思考/推理内容 |
|
||||
| `user_message_chunk` | ✅ | 用户消息(历史回放) |
|
||||
| `tool_call` | ✅ | 工具调用开始 |
|
||||
| `tool_call_update` | ✅ | 工具调用结果/状态更新 |
|
||||
| `usage_update` | ✅ | token 用量 + context window |
|
||||
| `plan` | ✅ | TodoWrite → plan entries |
|
||||
| `available_commands_update` | ✅ | 斜杠命令 & skills 列表 |
|
||||
| `current_mode_update` | ✅ | 模式切换通知 |
|
||||
| `config_option_update` | ✅ | 配置更新通知 |
|
||||
574
docs/features/all-features-guide.md
Normal file
574
docs/features/all-features-guide.md
Normal file
@@ -0,0 +1,574 @@
|
||||
# Claude Code Best (CCB) — 全功能使用指南
|
||||
|
||||
本文档覆盖我们通过 13 个 PR 为 CCB 恢复/新增的**全部功能**,按类别组织,每个功能包含说明、使用方法和示例。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [Buddy 伴侣系统](#1-buddy-伴侣系统)
|
||||
2. [Remote Control 远程控制](#2-remote-control-远程控制)
|
||||
3. [定时任务 /schedule](#3-定时任务-schedule)
|
||||
4. [Voice Mode 语音模式](#4-voice-mode-语音模式)
|
||||
5. [Chrome 浏览器控制](#5-chrome-浏览器控制)
|
||||
6. [Computer Use 屏幕操控](#6-computer-use-屏幕操控)
|
||||
7. [Feature Flags 与 GrowthBook](#7-feature-flags-与-growthbook)
|
||||
8. [/ultraplan 高级规划](#8-ultraplan-高级规划)
|
||||
9. [Daemon 后台守护](#9-daemon-后台守护)
|
||||
10. [Pipe IPC 多实例协作](#10-pipe-ipc-多实例协作)
|
||||
11. [LAN Pipes 局域网群控](#11-lan-pipes-局域网群控)
|
||||
12. [Monitor 后台监控](#12-monitor-后台监控)
|
||||
13. [Workflow 工作流脚本](#13-workflow-工作流脚本)
|
||||
14. [Coordinator 多Worker协调](#14-coordinator-多worker协调)
|
||||
15. [Proactive 自主模式](#15-proactive-自主模式)
|
||||
16. [History / Snip 历史管理](#16-history--snip-历史管理)
|
||||
17. [Fork 子Agent](#17-fork-子agent)
|
||||
18. [其他恢复的工具](#18-其他恢复的工具)
|
||||
|
||||
---
|
||||
|
||||
## 1. Buddy 伴侣系统
|
||||
|
||||
**PR**: #82 `refactor(buddy): align companion system with official CLI`
|
||||
**Feature Flag**: `BUDDY`
|
||||
|
||||
### 说明
|
||||
Buddy 是一个后台运行的伴侣 AI,在你主对话进行的同时,异步观察会话内容并提供建议。
|
||||
|
||||
### 使用
|
||||
```bash
|
||||
# 启动时自动加载(feature 默认开启)
|
||||
bun run dev
|
||||
|
||||
# 在对话中,Buddy 会在适当时机自动提供建议
|
||||
# 例如当你在调试时,Buddy 可能提示你检查日志
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Remote Control 远程控制
|
||||
|
||||
**PR**: #60 `feat: enable Remote Control (BRIDGE_MODE)` + #170 `feat: restore daemon supervisor`
|
||||
**Feature Flag**: `BRIDGE_MODE`
|
||||
|
||||
### 说明
|
||||
通过 WebSocket 远程控制 Claude Code 会话。支持自托管私有部署。
|
||||
|
||||
### 使用
|
||||
```bash
|
||||
# 启动远程控制模式
|
||||
bun run dev -- remote-control
|
||||
|
||||
# 使用自托管服务器
|
||||
CLAUDE_BRIDGE_BASE_URL=https://your-server.com CLAUDE_BRIDGE_OAUTH_TOKEN=your-token bun run dev --remote-control
|
||||
|
||||
# 或通过 /remote-control 命令在会话中启动
|
||||
/remote-control
|
||||
```
|
||||
|
||||
### 命令
|
||||
- `claude remote-control` / `claude rc` — 启动远程控制客户端
|
||||
- `claude bridge` — 同上(别名)
|
||||
|
||||
---
|
||||
|
||||
## 3. 定时任务 /schedule
|
||||
|
||||
**PR**: #88 `feat: enable /schedule by adding AGENT_TRIGGERS_REMOTE`
|
||||
**Feature Flag**: `AGENT_TRIGGERS_REMOTE`
|
||||
|
||||
### 说明
|
||||
创建定时执行的远程 agent 任务,支持 cron 表达式。
|
||||
|
||||
### 使用
|
||||
```
|
||||
/schedule create "每天检查依赖更新" --cron "0 9 * * *" --prompt "检查 package.json 中的过期依赖并创建更新 PR"
|
||||
/schedule list — 列出所有定时任务
|
||||
/schedule delete <id> — 删除指定任务
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Voice Mode 语音模式
|
||||
|
||||
**PR**: #92 `feat: enable /voice mode with native audio binaries`
|
||||
**Feature Flag**: `VOICE_MODE`
|
||||
|
||||
### 说明
|
||||
Push-to-Talk 语音输入,音频通过 WebSocket 流式传输到 Anthropic STT(Nova 3)。需要 Anthropic OAuth 认证(非 API key)。
|
||||
|
||||
### 使用
|
||||
```bash
|
||||
# 确保已通过 OAuth 登录
|
||||
claude auth login
|
||||
|
||||
# 在会话中按住指定键说话
|
||||
# 松开后自动转写为文字输入
|
||||
```
|
||||
|
||||
### 前提条件
|
||||
- Anthropic OAuth 认证(不支持 API key 模式)
|
||||
- 系统麦克风权限
|
||||
|
||||
---
|
||||
|
||||
## 5. Chrome 浏览器控制
|
||||
|
||||
**PR**: #93 `feat: enable Claude in Chrome MCP with full browser control`
|
||||
**Feature Flag**: `CHICAGO_MCP`
|
||||
|
||||
### 说明
|
||||
通过 Chrome 扩展控制浏览器:导航、点击、填表、截图、执行 JS。
|
||||
|
||||
### 使用
|
||||
```bash
|
||||
# 启动带 Chrome 控制的模式
|
||||
bun run dev -- --chrome
|
||||
|
||||
# 安装 Chrome 扩展后,AI 可以:
|
||||
# - 打开网页、点击按钮
|
||||
# - 填写表单
|
||||
# - 截取页面内容
|
||||
# - 执行 JavaScript
|
||||
```
|
||||
|
||||
### AI 可用工具
|
||||
- `navigate` — 导航到 URL
|
||||
- `click` / `find` / `form_input` — 页面交互
|
||||
- `get_page_text` / `read_page` — 读取内容
|
||||
- `javascript_tool` — 执行 JS
|
||||
- `gif_creator` — 录制操作 GIF
|
||||
|
||||
---
|
||||
|
||||
## 6. Computer Use 屏幕操控
|
||||
|
||||
**PR**: #98 + #137 `feat: Computer Use — 跨平台 Executor + Python Bridge + GUI 无障碍`
|
||||
**Feature Flag**: `CHICAGO_MCP`
|
||||
|
||||
### 说明
|
||||
跨平台屏幕操控:截图、键鼠模拟、应用管理。支持 macOS + Windows,Linux 后端待完成。
|
||||
|
||||
### 使用
|
||||
```bash
|
||||
# 启动后 AI 可自动调用屏幕操控工具
|
||||
bun run dev
|
||||
|
||||
# AI 可以:
|
||||
# - 截取屏幕/窗口截图
|
||||
# - 模拟键盘输入和鼠标操作
|
||||
# - 列出运行的应用
|
||||
# - 使用剪贴板
|
||||
```
|
||||
|
||||
### 平台支持
|
||||
| 平台 | 截图 | 键鼠 | 应用管理 |
|
||||
|------|------|------|----------|
|
||||
| macOS | ✅ | ✅ | ✅ |
|
||||
| Windows | ✅ | ✅ | ✅ |
|
||||
| Linux | ⏳ | ⏳ | ⏳ |
|
||||
|
||||
---
|
||||
|
||||
## 7. Feature Flags 与 GrowthBook
|
||||
|
||||
**PR**: #140 + #153 `feat: enable GrowthBook local gate defaults`
|
||||
**Feature Flags**: `SHOT_STATS`, `PROMPT_CACHE_BREAK_DETECTION`, `TOKEN_BUDGET`
|
||||
|
||||
### 说明
|
||||
本地 GrowthBook gate defaults 机制,绕过远程 feature flag 服务,确保功能在无网络时也可使用。
|
||||
|
||||
### 使用
|
||||
```bash
|
||||
# 通过环境变量启用任意 feature
|
||||
FEATURE_PROACTIVE=1 bun run dev
|
||||
|
||||
# dev/build 模式有各自的默认启用列表
|
||||
# 查看 scripts/dev.ts 中的 DEFAULT_FEATURES
|
||||
```
|
||||
|
||||
### 关键 feature flags
|
||||
| Flag | 说明 |
|
||||
|------|------|
|
||||
| `SHOT_STATS` | API 调用统计 |
|
||||
| `TOKEN_BUDGET` | Token 预算控制 |
|
||||
| `PROMPT_CACHE_BREAK_DETECTION` | Prompt 缓存命中检测 |
|
||||
|
||||
---
|
||||
|
||||
## 8. /ultraplan 高级规划
|
||||
|
||||
**PR**: #156 `feat: enable /ultraplan and harden GrowthBook fallback chain`
|
||||
**Feature Flag**: `ULTRAPLAN`
|
||||
|
||||
### 说明
|
||||
高级多 agent 规划模式。将复杂任务分解为多个阶段,每阶段可分配给不同 agent 并行执行。
|
||||
|
||||
### 使用
|
||||
```
|
||||
/ultraplan 实现一个完整的用户认证系统,包括注册、登录、密码重置、OAuth 集成
|
||||
```
|
||||
|
||||
AI 会生成:
|
||||
1. 任务分解(多阶段)
|
||||
2. 每阶段的 agent 分配
|
||||
3. 依赖关系图
|
||||
4. 并行执行计划
|
||||
|
||||
---
|
||||
|
||||
## 9. Daemon 后台守护
|
||||
|
||||
**PR**: #170 `feat: restore daemon supervisor and remoteControlServer command`
|
||||
**Feature Flag**: `DAEMON`
|
||||
|
||||
### 说明
|
||||
Daemon 模式允许 Claude Code 作为后台长驻进程运行,管理多个 worker。
|
||||
|
||||
### 使用
|
||||
```bash
|
||||
# 启动 daemon
|
||||
claude daemon start
|
||||
|
||||
# 查看状态
|
||||
claude daemon status
|
||||
|
||||
# 停止
|
||||
claude daemon stop
|
||||
|
||||
# 启动远程控制服务器
|
||||
bun run rcs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Pipe IPC 多实例协作
|
||||
|
||||
**PR**: #241 `feat: restore pipe IPC, LAN pipes, monitor tool`
|
||||
**Feature Flag**: `UDS_INBOX`
|
||||
|
||||
### 说明
|
||||
同一台机器上的多个 Claude Code 实例通过 UDS(Unix Domain Socket / Windows Named Pipe)自动发现并协作。首个启动的实例成为 main,后续自动注册为 sub。
|
||||
|
||||
### 使用
|
||||
|
||||
**启动多实例**:
|
||||
```bash
|
||||
# 终端 1
|
||||
bun run dev
|
||||
# → 自动成为 main
|
||||
|
||||
# 终端 2
|
||||
bun run dev
|
||||
# → 自动成为 sub-1,被 main attach
|
||||
```
|
||||
|
||||
**管理实例**:
|
||||
```
|
||||
/pipes — 显示所有实例,Shift+↓ 展开选择面板
|
||||
/pipes select <name> — 选中实例
|
||||
/pipes all — 全选
|
||||
/pipes none — 取消全选
|
||||
/attach <name> — 手动 attach 某实例
|
||||
/detach <name> — 断开连接
|
||||
/send <name> <msg> — 向指定实例发送消息
|
||||
/claim-main — 强制声明为 main
|
||||
/pipe-status — 显示详细状态
|
||||
/peers — 列出所有已发现的 peer
|
||||
```
|
||||
|
||||
**选择面板操作**:
|
||||
1. 按 `Shift+↓` 展开面板
|
||||
2. `↑/↓` 移动光标
|
||||
3. `Space` 选中/取消 pipe
|
||||
4. `Enter` 确认关闭
|
||||
5. `←/→` 切换路由模式(selected pipes ↔ local main)
|
||||
|
||||
**消息广播**:
|
||||
选中 pipe 后,输入的消息自动路由到所有选中的 slave 执行,结果流式回传到 main。
|
||||
|
||||
**权限转发**:
|
||||
slave 执行需要权限的工具时(如 BashTool),权限请求自动转发到 main 的确认队列。
|
||||
|
||||
---
|
||||
|
||||
## 11. LAN Pipes 局域网群控
|
||||
|
||||
**PR**: #241(同上)
|
||||
**Feature Flag**: `LAN_PIPES`
|
||||
|
||||
### 说明
|
||||
在 Pipe IPC 基础上增加 TCP 传输层和 UDP Multicast 发现,实现跨机器零配置协作。
|
||||
|
||||
### 使用
|
||||
|
||||
**局域网多机器**:
|
||||
```bash
|
||||
# 机器 A (192.168.50.22)
|
||||
bun run dev
|
||||
|
||||
# 机器 B (192.168.50.27)
|
||||
bun run dev
|
||||
|
||||
# 两边启动后 3-5 秒自动发现和 attach
|
||||
# /pipes 显示 [LAN] 标记的远端实例
|
||||
```
|
||||
|
||||
**防火墙配置**(每台机器都需要):
|
||||
|
||||
Windows(管理员 PowerShell):
|
||||
```powershell
|
||||
New-NetFirewallRule -DisplayName "CCB LAN Beacon (UDP)" -Direction Inbound -Protocol UDP -LocalPort 7101 -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "CCB LAN Pipes (TCP)" -Direction Inbound -Protocol TCP -LocalPort 1024-65535 -Program (Get-Command bun).Source -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "CCB LAN Beacon Out (UDP)" -Direction Outbound -Protocol UDP -RemotePort 7101 -Action Allow -Profile Private
|
||||
```
|
||||
|
||||
macOS:
|
||||
```bash
|
||||
# 首次运行时系统弹对话框,点"允许"即可
|
||||
```
|
||||
|
||||
Linux:
|
||||
```bash
|
||||
sudo firewall-cmd --zone=trusted --add-port=7101/udp --permanent
|
||||
sudo firewall-cmd --zone=trusted --add-port=1024-65535/tcp --permanent
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
**通知显示格式**:
|
||||
```
|
||||
# 本机 sub
|
||||
Routed to [sub-1]; main can continue other tasks
|
||||
|
||||
# LAN peer
|
||||
Routed to [main] vmwin11/192.168.50.27; main can continue other tasks
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Monitor 后台监控
|
||||
|
||||
**PR**: #241(同上)
|
||||
**Feature Flag**: `MONITOR_TOOL`
|
||||
|
||||
### 说明
|
||||
在后台运行 shell 命令持续监控输出(类似 `watch` 命令)。AI 也可自主调用 MonitorTool。
|
||||
|
||||
### 使用
|
||||
|
||||
**用户命令**:
|
||||
```
|
||||
/monitor tail -f /var/log/syslog
|
||||
/monitor watch -n 5 docker ps
|
||||
/monitor "while true; do curl -s localhost:3000/health; sleep 10; done"
|
||||
```
|
||||
|
||||
**查看监控**:
|
||||
- 按 `Shift+Down` 展开后台任务面板
|
||||
- 查看监控输出和状态
|
||||
|
||||
**Windows 兼容**:
|
||||
`watch -n <sec> <cmd>` 自动转为 PowerShell 循环:
|
||||
```powershell
|
||||
while($true){ <cmd>; Start-Sleep -Seconds <sec> }
|
||||
```
|
||||
|
||||
**AI 调用**:
|
||||
AI 可在对话中自动调用 `MonitorTool` 监控日志、构建输出等。
|
||||
|
||||
---
|
||||
|
||||
## 13. Workflow 工作流脚本
|
||||
|
||||
**PR**: #241(同上)
|
||||
**Feature Flag**: `WORKFLOW_SCRIPTS`
|
||||
|
||||
### 说明
|
||||
执行 `.claude/workflows/` 目录下的用户定义工作流脚本。
|
||||
|
||||
### 使用
|
||||
|
||||
**创建工作流**:
|
||||
```bash
|
||||
mkdir -p .claude/workflows
|
||||
cat > .claude/workflows/deploy.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
echo "Running tests..."
|
||||
bun test
|
||||
echo "Building..."
|
||||
bun run build
|
||||
echo "Deploying..."
|
||||
EOF
|
||||
chmod +x .claude/workflows/deploy.sh
|
||||
```
|
||||
|
||||
**列出可用工作流**:
|
||||
```
|
||||
/workflows
|
||||
```
|
||||
|
||||
**AI 调用**:
|
||||
AI 可通过 `WorkflowTool` 自动执行工作流:
|
||||
```
|
||||
请执行 deploy 工作流
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. Coordinator 多Worker协调
|
||||
|
||||
**PR**: #241(同上)
|
||||
**Feature Flag**: `COORDINATOR_MODE`
|
||||
|
||||
### 说明
|
||||
启用 coordinator 模式后,AI 可自动将任务分配给多个 worker 并行执行。
|
||||
|
||||
### 使用
|
||||
```
|
||||
/coordinator — 切换 coordinator 模式开/关
|
||||
```
|
||||
|
||||
启用后,AI 在处理复杂任务时会:
|
||||
1. 分析任务可并行的部分
|
||||
2. 自动创建 worker 分支
|
||||
3. 分配子任务
|
||||
4. 汇总结果
|
||||
|
||||
---
|
||||
|
||||
## 15. Proactive 自主模式
|
||||
|
||||
**PR**: #241(同上)
|
||||
**Feature Flag**: `PROACTIVE` / `KAIROS`
|
||||
|
||||
### 说明
|
||||
启用后 AI 会主动发起操作(而不仅回应用户输入),例如自动检测文件变更、主动提出优化建议。
|
||||
|
||||
### 使用
|
||||
```
|
||||
/proactive — 切换 proactive 模式开/关
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16. History / Snip 历史管理
|
||||
|
||||
**PR**: #241(同上)
|
||||
**Feature Flag**: `HISTORY_SNIP`
|
||||
|
||||
### 说明
|
||||
查看和管理对话历史,支持手动截断以释放上下文窗口空间。
|
||||
|
||||
### 使用
|
||||
```
|
||||
/history — 显示对话历史摘要
|
||||
/force-snip — 强制在当前位置截断历史
|
||||
```
|
||||
|
||||
AI 也可通过 `SnipTool` 自动截断过长的对话:
|
||||
```
|
||||
对话太长了,请帮我截断历史
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17. Fork 子Agent
|
||||
|
||||
**PR**: #241(同上)
|
||||
**Feature Flag**: `FORK_SUBAGENT`
|
||||
|
||||
### 说明
|
||||
在当前对话上下文中 fork 一个独立的子 agent,继承完整会话状态独立执行。
|
||||
|
||||
### 使用
|
||||
```
|
||||
/fork — 基于当前上下文 fork 子 agent
|
||||
```
|
||||
|
||||
子 agent 会:
|
||||
- 继承当前的全部对话历史
|
||||
- 在独立的执行环境中运行
|
||||
- 不影响主会话状态
|
||||
|
||||
---
|
||||
|
||||
## 18. 其他恢复的工具
|
||||
|
||||
以下工具从 stub 恢复为完整实现:
|
||||
|
||||
| 工具 | 说明 | 使用 |
|
||||
|------|------|------|
|
||||
| `SleepTool` | 暂停执行指定时间 | AI 在轮询场景自动调用 |
|
||||
| `WebBrowserTool` | 终端内网页交互 | AI 需要查看网页时调用 |
|
||||
| `SubscribePRTool` | 订阅 GitHub PR 变更 | `/subscribe-pr` 或 AI 调用 |
|
||||
| `PushNotificationTool` | 推送桌面通知 | AI 在长任务完成时调用 |
|
||||
| `CtxInspectTool` | 检查上下文窗口使用 | AI 判断上下文剩余空间 |
|
||||
| `TerminalCaptureTool` | 截取终端屏幕 | AI 需要看终端输出时调用 |
|
||||
| `SendUserFileTool` | 向用户发送文件 | AI 导出文件时调用 |
|
||||
| `REPLTool` | 启动子 REPL 会话 | AI 需要独立交互环境时调用 |
|
||||
| `VerifyPlanExecutionTool` | 验证执行计划完成度 | AI 完成计划后自动验证 |
|
||||
| `SuggestBackgroundPRTool` | 建议创建后台 PR | AI 发现可独立的变更时提议 |
|
||||
| `ListPeersTool` | 列出已发现的 peer | AI 查询多实例状态时调用 |
|
||||
|
||||
---
|
||||
|
||||
## 附录:全部 Feature Flags
|
||||
|
||||
| Flag | 默认 | 说明 |
|
||||
|------|------|------|
|
||||
| `BUDDY` | ✅ dev only | 伴侣系统 |
|
||||
| `BRIDGE_MODE` | ✅ dev only | 远程控制 |
|
||||
| `VOICE_MODE` | ✅ dev+build | 语音模式 |
|
||||
| `CHICAGO_MCP` | ✅ dev+build | Computer Use + Chrome |
|
||||
| `AGENT_TRIGGERS_REMOTE` | ✅ dev+build | 定时任务 |
|
||||
| `SHOT_STATS` | ✅ dev+build | API 统计 |
|
||||
| `TOKEN_BUDGET` | ✅ dev+build | Token 预算 |
|
||||
| `PROMPT_CACHE_BREAK_DETECTION` | ✅ dev+build | 缓存检测 |
|
||||
| `ULTRAPLAN` | ✅ dev+build | 高级规划 |
|
||||
| `DAEMON` | ✅ dev+build | 后台守护 |
|
||||
| `UDS_INBOX` | ✅ dev only | Pipe IPC |
|
||||
| `LAN_PIPES` | ✅ dev only | LAN 群控 |
|
||||
| `MONITOR_TOOL` | ✅ dev+build | 后台监控 |
|
||||
| `WORKFLOW_SCRIPTS` | ✅ dev+build | 工作流脚本 |
|
||||
| `FORK_SUBAGENT` | ✅ dev+build | 子 Agent |
|
||||
| `KAIROS` | ✅ dev+build | Kairos 调度 |
|
||||
| `COORDINATOR_MODE` | ✅ dev+build | 多 Worker |
|
||||
| `HISTORY_SNIP` | ✅ dev+build | 历史管理 |
|
||||
| `CONTEXT_COLLAPSE` | ✅ dev+build | 上下文折叠 |
|
||||
| `ULTRATHINK` | ✅ dev+build | 扩展思考 |
|
||||
| `EXTRACT_MEMORIES` | ✅ dev+build | 自动记忆提取 |
|
||||
| `VERIFICATION_AGENT` | ✅ dev+build | 验证 Agent |
|
||||
| `KAIROS_BRIEF` | ✅ dev+build | Brief 模式 |
|
||||
| `AWAY_SUMMARY` | ✅ dev+build | 离开摘要 |
|
||||
| `ACP` | ✅ dev+build | ACP 协议 |
|
||||
| `LODESTONE` | ✅ dev+build | 深度链接 |
|
||||
| `BUILTIN_EXPLORE_PLAN_AGENTS` | ✅ dev+build | 内置 Explore/Plan agent |
|
||||
| `AGENT_TRIGGERS` | ✅ dev+build | 本地定时任务 |
|
||||
| `BG_SESSIONS` | ✅ dev only | 后台会话 |
|
||||
| `TEMPLATES` | ✅ dev only | 模板系统 |
|
||||
| `TRANSCRIPT_CLASSIFIER` | ✅ dev only | 对话分类 |
|
||||
|
||||
手动启用任意 flag:
|
||||
```bash
|
||||
FEATURE_FLAG_NAME=1 bun run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 附录:PR 列表
|
||||
|
||||
| PR | 日期 | 标题 |
|
||||
|----|------|------|
|
||||
| #60 | 2026-04-02 | feat: enable Remote Control (BRIDGE_MODE) |
|
||||
| #82 | 2026-04-03 | refactor(buddy): align companion system |
|
||||
| #88 | 2026-04-03 | feat: enable /schedule (AGENT_TRIGGERS_REMOTE) |
|
||||
| #89 | 2026-04-03 | feat: built-in status line |
|
||||
| #92 | 2026-04-03 | feat: enable /voice mode |
|
||||
| #93 | 2026-04-03 | feat: enable Chrome MCP |
|
||||
| #98 | 2026-04-03 | feat: enable Computer Use (macOS + Windows + Linux) |
|
||||
| #137 | 2026-04-05 | feat: Computer Use v2 — 跨平台 Executor |
|
||||
| #140 | 2026-04-05 | feat: enable SHOT_STATS, TOKEN_BUDGET |
|
||||
| #153 | 2026-04-06 | feat: enable GrowthBook local gate defaults |
|
||||
| #156 | 2026-04-06 | feat: enable /ultraplan |
|
||||
| #170 | 2026-04-07 | feat: restore daemon supervisor |
|
||||
| #241 | 2026-04-11 | feat: restore pipe IPC, LAN pipes, monitor tool |
|
||||
@@ -102,6 +102,6 @@ FEATURE_BASH_CLASSIFIER=1 FEATURE_TREE_SITTER_BASH=1 bun run dev
|
||||
| `src/utils/permissions/bashClassifier.ts` | — | Bash 分类器(stub,ANT-ONLY) |
|
||||
| `src/utils/permissions/yoloClassifier.ts` | 1496 | YOLO 分类器(完整参考实现) |
|
||||
| `src/utils/classifierApprovals.ts` | — | 分类器审批信号管理 |
|
||||
| `src/components/permissions/BashPermissionRequest.tsx:261-469` | — | 分类器 UI |
|
||||
| `src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx` | — | 分类器 UI |
|
||||
| `src/hooks/toolPermission/handlers/interactiveHandler.ts` | — | 交互式权限处理 |
|
||||
| `src/services/api/withRetry.ts:81` | — | API beta 标头 |
|
||||
| `src/services/api/withRetry.ts` | — | API beta 标头 |
|
||||
|
||||
@@ -30,7 +30,7 @@ BRIDGE_MODE 将本地 CLI 注册为"bridge 环境",可从 claude.ai 或其他
|
||||
|
||||
文件:`src/bridge/bridgeApi.ts`
|
||||
|
||||
Bridge API Client 提供 7 个核心操作:
|
||||
Bridge API Client 提供 9 个核心操作:
|
||||
|
||||
| 操作 | HTTP | 说明 |
|
||||
|------|------|------|
|
||||
@@ -137,7 +137,7 @@ FEATURE_BRIDGE_MODE=1 FEATURE_DAEMON=1 bun run dev
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `src/bridge/bridgeApi.ts` | 540 | API Client(核心) |
|
||||
| `src/bridge/bridgeApi.ts` | 541 | API Client(核心) |
|
||||
| `src/bridge/sessionRunner.ts` | — | 会话运行器 |
|
||||
| `src/bridge/bridgeConfig.ts` | — | 配置管理 |
|
||||
| `src/bridge/replBridgeTransport.ts` | — | 传输层 |
|
||||
|
||||
@@ -78,10 +78,13 @@ FEATURE_BUDDY=1 bun run dev
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `src/commands/buddy/index.ts` | `/buddy` 命令注册 |
|
||||
| `src/commands/buddy/buddy.ts` | `/buddy` 命令处理 |
|
||||
| `src/buddy/companion.ts` | 宠物生成与加载 |
|
||||
| `src/buddy/companionReact.ts` | 宠物反应系统(REPL 每轮查询后触发) |
|
||||
| `src/buddy/types.ts` | 类型定义(物种、稀有度、属性) |
|
||||
| `src/buddy/sprites.ts` | 终端像素画渲染 |
|
||||
| `src/buddy/CompanionSprite.tsx` | React 组件(输入框旁显示) |
|
||||
| `src/buddy/CompanionCard.tsx` | 宠物信息卡片(`/buddy` 无参数时展示) |
|
||||
| `src/buddy/useBuddyNotification.tsx` | 启动提示通知 |
|
||||
| `src/buddy/prompt.ts` | 宠物相关 prompt 模板 |
|
||||
|
||||
89
docs/features/channels.md
Normal file
89
docs/features/channels.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Channels — 外部频道消息接入
|
||||
|
||||
> 启动参数:`--channels` / `--dangerously-load-development-channels`
|
||||
> 状态:已解除 feature flag 和 OAuth 限制,可直接使用
|
||||
|
||||
## 概述
|
||||
|
||||
Channel 是一个 MCP 服务器,它将外部事件推送到你运行中的 Claude Code 会话中,以便 Claude 可以在你不在终端时做出反应。详细使用说明请参考以下文档:
|
||||
|
||||
- **官方文档**:[使用 channels 将事件推送到运行中的会话](https://code.claude.com/docs/zh-CN/channels)
|
||||
- **飞书插件**:[claude-code-feishu-channel](https://github.com/whobot-ai/claude-code-feishu-channel) — 社区首个飞书 Channel 插件,支持双向消息、配对认证、群组聊天、文件附件
|
||||
|
||||
本仓库现在内置了 **微信 WeChat channel**,不需要单独安装外部 marketplace 插件。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 启用频道监听(plugin 格式)
|
||||
ccb --channels plugin:feishu@claude-code-feishu-channel
|
||||
|
||||
# 启用内置微信 channel
|
||||
ccb weixin login
|
||||
ccb --channels plugin:weixin@builtin
|
||||
|
||||
# 启用频道监听(server 格式)
|
||||
ccb --channels server:my-slack-bridge
|
||||
|
||||
# 同时启用多个频道
|
||||
ccb --channels plugin:feishu@claude-code-feishu-channel --channels server:discord-bot
|
||||
|
||||
# 开发模式(跳过 allowlist 检查,用于测试自定义 channel)
|
||||
ccb --dangerously-load-development-channels server:my-custom-channel
|
||||
```
|
||||
|
||||
## 支持的 Channel
|
||||
|
||||
| Channel | 说明 | 来源 |
|
||||
|---------|------|------|
|
||||
| **Telegram** | 官方 Telegram Bot 集成 | `/plugin install telegram@claude-plugins-official` |
|
||||
| **Discord** | 官方 Discord Bot 集成 | `/plugin install discord@claude-plugins-official` |
|
||||
| **iMessage** | macOS 原生消息 | `/plugin install imessage@claude-plugins-official` |
|
||||
| **飞书 (Feishu/Lark)** | 双向消息、群组聊天、文件附件 | `/plugin install feishu@claude-code-feishu-channel` |
|
||||
| **微信 (WeChat)** | 内置 channel,支持扫码登录、双向消息、附件透传 | `ccb weixin login` + `ccb --channels plugin:weixin@builtin` |
|
||||
|
||||
## 微信内置 Channel
|
||||
|
||||
### 登录
|
||||
|
||||
```bash
|
||||
ccb weixin login
|
||||
```
|
||||
|
||||
已登录状态可清除:
|
||||
|
||||
```bash
|
||||
ccb weixin login clear
|
||||
```
|
||||
|
||||
### 会话启用
|
||||
|
||||
```bash
|
||||
ccb --channels plugin:weixin@builtin
|
||||
```
|
||||
|
||||
### 配对授权
|
||||
|
||||
首次收到未授权微信用户消息时,weixin channel 会回一条 6 位 pairing code。运营侧可在终端执行:
|
||||
|
||||
```bash
|
||||
ccb weixin access pair <code>
|
||||
```
|
||||
|
||||
确认后,该微信用户后续消息才会进入 Claude Code 会话。
|
||||
|
||||
## 相关文件
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/services/mcp/channelNotification.ts` | 频道 gate 逻辑、消息包装 |
|
||||
| `src/services/mcp/channelAllowlist.ts` | 频道开关(已默认开启) |
|
||||
| `src/services/mcp/useManageMCPConnections.ts` | MCP 连接管理中的频道注册 |
|
||||
| `src/components/LogoV2/ChannelsNotice.tsx` | 启动时频道状态提示 |
|
||||
| `src/main.tsx` | `--channels` 参数解析 |
|
||||
| `src/interactiveHelpers.tsx` | Dev channels 确认对话框 |
|
||||
|
||||
## 参考链接
|
||||
|
||||
- [官方 Channels 文档](https://code.claude.com/docs/zh-CN/channels) — 完整使用说明、安全性、Enterprise 控制
|
||||
- [飞书 Channel 插件](https://github.com/whobot-ai/claude-code-feishu-channel) — 安装配置教程、MCP 工具、Skill 命令参考
|
||||
30
docs/features/chrome-use-mcp.md
Normal file
30
docs/features/chrome-use-mcp.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Chrome Use — 浏览器自动化快速指南
|
||||
|
||||
让 Claude Code 直接控制你的 Chrome 浏览器,用自然语言完成网页操作。
|
||||
|
||||
## 快速开始(3 分钟)
|
||||
|
||||
### 第一步:安装 Chrome 扩展
|
||||
|
||||
1. 下载扩展:https://github.com/hangwin/mcp-chrome/releases
|
||||
2. 解压 zip 文件
|
||||
3. 打开 Chrome 访问 `chrome://extensions/`
|
||||
4. 开启右上角「开发者模式」
|
||||
5. 点击「加载已解压的扩展程序」,选择解压后的文件夹
|
||||
|
||||
### 第二步:启动 Claude Code
|
||||
|
||||
```bash
|
||||
bun run dev
|
||||
ccb # 或者 ccb 安装版也行
|
||||
```
|
||||
|
||||
### 第三步:启用 Chrome MCP
|
||||
|
||||
1. 在 REPL 中输入 `/mcp` 打开 MCP 面板
|
||||
2. 找到 `mcp-chrome`,按空格键启用
|
||||
3. 按 Enter 确认
|
||||
|
||||
## 相关文档
|
||||
|
||||
- GitHub 仓库:https://github.com/hangwin/mcp-chrome
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
## 概览
|
||||
|
||||
Computer Use 提供 37 个工具,分为三类:
|
||||
Computer Use 提供 38 个工具,分为三类:
|
||||
|
||||
| 分类 | 平台 | 工具数 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| 通用工具 | 全平台 | 24 | 官方 Computer Use 标准能力 |
|
||||
| Windows 专属工具 | Win32 | 10 | 绑定窗口模式下的增强能力 |
|
||||
| Windows 专属工具 | Win32 | 11 | 绑定窗口模式下的增强能力 |
|
||||
| 教学工具 | 全平台 | 3 | 分步引导模式(需 teachMode 开启) |
|
||||
|
||||
---
|
||||
@@ -82,7 +82,7 @@ Computer Use 提供 37 个工具,分为三类:
|
||||
|
||||
---
|
||||
|
||||
## 二、Windows 专属工具(10 个)
|
||||
## 二、Windows 专属工具(12 个)
|
||||
|
||||
仅 Windows 平台可见。核心能力:**绑定窗口后的独立操作——不抢占用户鼠标键盘**。
|
||||
|
||||
@@ -235,8 +235,19 @@ Computer Use 提供 37 个工具,分为三类:
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| `open_terminal` | `agent`, `command?` | 打开新终端窗口并启动 AI agent(claude/codex/gemini/custom)。自动绑定窗口并截图验证 |
|
||||
| `activate_window` | `click_x?`, `click_y?` | 激活绑定窗口:SetForegroundWindow + BringWindowToTop + 点击确保焦点 |
|
||||
| `prompt_respond` | `response_type`, `arrow_direction?`, `arrow_count?`, `text?` | 处理终端 Yes/No/选择提示 |
|
||||
|
||||
**open_terminal agent 类型:**
|
||||
|
||||
| agent | 命令 | 说明 |
|
||||
|-------|------|------|
|
||||
| `claude` | `claude` | 启动 Claude Code |
|
||||
| `codex` | `codex` | 启动 Codex |
|
||||
| `gemini` | `gemini` | 启动 Gemini |
|
||||
| `custom` | 用户指定 | 自定义命令 |
|
||||
|
||||
**response_type 详情:**
|
||||
|
||||
| response_type | 操作 | 场景 |
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
- ✅ `@ant/computer-use-input` 拆为 dispatcher + backends(darwin + win32)
|
||||
- ✅ `@ant/computer-use-swift` 拆为 dispatcher + backends(darwin + win32)
|
||||
- ✅ `CHICAGO_MCP` 编译开关已开
|
||||
- ❌ `src/` 层有 6 处 macOS 硬编码阻塞
|
||||
- ✅ `src/` 层 macOS 硬编码已移除(Phase 2 已完成)
|
||||
|
||||
## 2. 阻塞点全景
|
||||
|
||||
@@ -19,25 +19,25 @@
|
||||
|
||||
| # | 文件:行号 | 阻塞代码 | 影响 |
|
||||
|---|----------|---------|------|
|
||||
| 1 | `src/main.tsx:1605` | `getPlatform() === 'macos'` | 整个 CU 初始化被跳过 |
|
||||
| 1 | `src/main.tsx:2366` | `feature("CHICAGO_MCP")` 门控 | CU 初始化入口 |
|
||||
|
||||
### 2.2 加载层
|
||||
|
||||
| # | 文件:行号 | 阻塞代码 | 影响 |
|
||||
|---|----------|---------|------|
|
||||
| 2 | `src/utils/computerUse/swiftLoader.ts:16` | `process.platform !== 'darwin'` → throw | 截图、应用管理全部不可用 |
|
||||
| 3 | `src/utils/computerUse/executor.ts:263` | `process.platform !== 'darwin'` → throw | 整个 executor 工厂函数不可用 |
|
||||
| 2 | `src/utils/computerUse/swiftLoader.ts` | macOS-only loader(已改为仅 darwin 加载) | 非 darwin 使用 platforms/ 替代 |
|
||||
| 3 | `src/utils/computerUse/executor.ts:302` | `process.platform !== 'darwin'` → cross-platform executor | 非 darwin 走跨平台路径 |
|
||||
|
||||
### 2.3 macOS 特有依赖
|
||||
|
||||
| # | 文件:行号 | 依赖 | macOS 实现 | 需要替代方案 |
|
||||
|---|----------|------|-----------|------------|
|
||||
| 4 | `executor.ts:70-88` | 剪贴板 | `pbcopy`/`pbpaste` | Win: PowerShell `Get/Set-Clipboard`;Linux: `xclip`/`wl-copy` |
|
||||
| 5 | `drainRunLoop.ts:21` | CFRunLoop pump | `cu._drainMainRunLoop()` | 非 darwin:直接执行 fn(),不需要 pump |
|
||||
| 6 | `escHotkey.ts:28` | ESC 热键 | CGEventTap | 非 darwin:返回 false(已有 Ctrl+C fallback) |
|
||||
| 7 | `hostAdapter.ts:48-54` | 系统权限 | TCC accessibility + screenRecording | Win:直接 granted;Linux:检查 xdotool |
|
||||
| 8 | `common.ts:56` | 平台标识 | `platform: 'darwin'` 硬编码 | 动态获取 |
|
||||
| 9 | `executor.ts:180` | 粘贴快捷键 | `command+v` | Win/Linux:`ctrl+v` |
|
||||
| 4 | `executor.ts:72-96` | 剪贴板 | `pbcopy`/`pbpaste` / PowerShell / xclip | Win: PowerShell `Get/Set-Clipboard`;Linux: `xclip`/`wl-copy` |
|
||||
| 5 | `drainRunLoop.ts` | CFRunLoop pump | `cu._drainMainRunLoop()` | 非 darwin:直接执行 fn(),不需要 pump |
|
||||
| 6 | `escHotkey.ts` | ESC 热键 | CGEventTap | 非 darwin:返回 false(已有 Ctrl+C fallback) |
|
||||
| 7 | `hostAdapter.ts` | 系统权限 | TCC accessibility + screenRecording | Win:直接 granted;Linux:检查 xdotool |
|
||||
| 8 | `common.ts:55-58` | 平台标识 | 动态获取 | 已改为 `process.platform` 分发 |
|
||||
| 9 | `executor.ts:232` | 粘贴快捷键 | `command`/`ctrl` 分发 | 已按平台分发粘贴快捷键 |
|
||||
|
||||
### 2.4 缺失的 Linux 后端
|
||||
|
||||
@@ -100,19 +100,19 @@
|
||||
|
||||
| 步骤 | 文件 | 改动 |
|
||||
|------|------|------|
|
||||
| 2.1 | `src/main.tsx:1605` | `getPlatform() === 'macos'` → 去掉平台限制,或改为 `!== 'unknown'` |
|
||||
| 2.2 | `src/utils/computerUse/swiftLoader.ts:16-18` | 移除 `process.platform !== 'darwin'` throw。`@ant/computer-use-swift/index.ts` 已有跨平台 dispatch |
|
||||
| 2.3 | `src/utils/computerUse/executor.ts:263-267` | 移除 `process.platform !== 'darwin'` throw。改为检查 input/swift isSupported |
|
||||
| 2.4 | `src/utils/computerUse/executor.ts:70-88` | 剪贴板函数按平台分发:darwin→pbcopy/pbpaste,win32→PowerShell Get/Set-Clipboard,linux→xclip |
|
||||
| 2.5 | `src/utils/computerUse/executor.ts:180` | `typeViaClipboard` 中 `command+v` → 非 darwin 时用 `ctrl+v` |
|
||||
| 2.6 | `src/utils/computerUse/executor.ts:273` | `const cu = requireComputerUseSwift()` → 改为 `new ComputerUseAPI()`(从 package 直接实例化,不走 swiftLoader throw) |
|
||||
| 2.7 | `src/utils/computerUse/drainRunLoop.ts` | 开头加 `if (process.platform !== 'darwin') return fn()` |
|
||||
| 2.8 | `src/utils/computerUse/escHotkey.ts` | `registerEscHotkey` 非 darwin 返回 false(已有 Ctrl+C fallback) |
|
||||
| 2.9 | `src/utils/computerUse/hostAdapter.ts:48-54` | `ensureOsPermissions` 非 darwin 返回 `{ granted: true }` |
|
||||
| 2.10 | `src/utils/computerUse/common.ts:56` | `platform: 'darwin'` → `platform: process.platform === 'win32' ? 'windows' : process.platform === 'linux' ? 'linux' : 'darwin'` |
|
||||
| 2.11 | `src/utils/computerUse/common.ts:55` | `screenshotFiltering: 'native'` → 非 darwin 时 `'none'`(Windows/Linux 截图不支持 per-app 过滤) |
|
||||
| 2.12 | `src/utils/computerUse/gates.ts:13` | `enabled: false` → `enabled: true`(无 GrowthBook 时默认可用) |
|
||||
| 2.13 | `src/utils/computerUse/gates.ts:39-43` | `hasRequiredSubscription()` → 直接返回 `true` |
|
||||
| 2.1 | `src/main.tsx:2366` | `feature("CHICAGO_MCP")` → 已为跨平台入口 |
|
||||
| 2.2 | `src/utils/computerUse/swiftLoader.ts` | 已改为仅 darwin 加载,非 darwin 使用 platforms/ |
|
||||
| 2.3 | `src/utils/computerUse/executor.ts:302-309` | 已改为 cross-platform dispatch(非 darwin → createCrossPlatformExecutor) |
|
||||
| 2.4 | `src/utils/computerUse/executor.ts:72-96` | 剪贴板已按平台分发:darwin→pbcopy/pbpaste,win32→PowerShell,linux→xclip |
|
||||
| 2.5 | `src/utils/computerUse/executor.ts:232` | 粘贴快捷键已按平台分发:darwin→command,其他→ctrl |
|
||||
| 2.6 | `src/utils/computerUse/executor.ts:302-309` | 非 darwin 已改为 `createCrossPlatformExecutor()` |
|
||||
| 2.7 | `src/utils/computerUse/drainRunLoop.ts` | 非 darwin 无需 pump(直接执行 fn) |
|
||||
| 2.8 | `src/utils/computerUse/escHotkey.ts` | 非 darwin 返回 false(已有 Ctrl+C fallback) |
|
||||
| 2.9 | `src/utils/computerUse/hostAdapter.ts` | 非 darwin 权限检查逻辑已实现 |
|
||||
| 2.10 | `src/utils/computerUse/common.ts:58` | 已改为动态 `process.platform` 分发 |
|
||||
| 2.11 | `src/utils/computerUse/common.ts:55` | 已改为 darwin→'native',其他→'none' |
|
||||
| 2.12 | `src/utils/computerUse/gates.ts:55` | 已更新(需验证 enabled 默认值) |
|
||||
| 2.13 | `src/utils/computerUse/gates.ts:39` | `hasRequiredSubscription()` 已更新 |
|
||||
|
||||
### Phase 3:新增 Linux 后端
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ CONTEXT_COLLAPSE 让模型内省上下文窗口使用情况,并智能压缩旧
|
||||
| 折叠核心 | `src/services/contextCollapse/index.ts` | **Stub** — 接口完整(`ContextCollapseStats`、`CollapseResult`、`DrainResult`),函数全部空操作 |
|
||||
| 折叠操作 | `src/services/contextCollapse/operations.ts` | **Stub** — `projectView` 为恒等函数 |
|
||||
| 折叠持久化 | `src/services/contextCollapse/persist.ts` | **Stub** — `restoreFromEntries` 为空操作 |
|
||||
| CtxInspectTool | `src/tools/CtxInspectTool/` | **缺失** — 目录不存在 |
|
||||
| CtxInspectTool | `packages/builtin-tools/src/tools/CtxInspectTool/CtxInspectTool.ts` | **实现** — 上下文内省工具 |
|
||||
| SnipTool 提示 | `src/tools/SnipTool/prompt.ts` | **Stub** — 空工具名 |
|
||||
| SnipTool 实现 | `src/tools/SnipTool/SnipTool.ts` | **缺失** |
|
||||
| force-snip 命令 | `src/commands/force-snip.js` | **缺失** |
|
||||
@@ -106,7 +106,7 @@ SnipTool 提供手动折叠能力:
|
||||
| 1 | `services/contextCollapse/index.ts` | 大 | 折叠状态机、LLM 调用、消息压缩 |
|
||||
| 2 | `services/contextCollapse/operations.ts` | 中 | `projectView()` 消息过滤 |
|
||||
| 3 | `services/contextCollapse/persist.ts` | 小 | `restoreFromEntries()` 磁盘持久化 |
|
||||
| 4 | `tools/CtxInspectTool/` | 中 | 上下文内省工具(token 计数、已折叠范围) |
|
||||
| 4 | `tools/CtxInspectTool/` | 已完成 | 上下文内省工具已实现(`packages/builtin-tools/src/tools/CtxInspectTool/`) |
|
||||
| 5 | `tools/SnipTool/SnipTool.ts` | 中 | Snip 工具实现 |
|
||||
| 6 | `commands/force-snip.js` | 小 | `/force-snip` 命令 |
|
||||
|
||||
|
||||
318
docs/features/daemon-restructure-design.md
Normal file
318
docs/features/daemon-restructure-design.md
Normal file
@@ -0,0 +1,318 @@
|
||||
# Daemon 重构设计方案
|
||||
|
||||
> 分支: `feat/integrate-5-branches`
|
||||
> 基于: `f41745cb` (= main `11bb3f62` 内容)
|
||||
> 日期: 2026-04-13
|
||||
|
||||
## 一、问题概述
|
||||
|
||||
### 1.1 命令结构散乱
|
||||
|
||||
当前后台进程相关的命令分布在三个不同的位置,没有统一的命名空间:
|
||||
|
||||
| 命令 | 注册位置 | 入口 |
|
||||
|------|---------|------|
|
||||
| `claude daemon start/status/stop` | `cli.tsx` 快速路径 L203 | `daemon/main.ts` |
|
||||
| `claude ps` | `cli.tsx` 快速路径 L220 | `cli/bg.ts` |
|
||||
| `claude logs <x>` | `cli.tsx` 快速路径 L232 | `cli/bg.ts` |
|
||||
| `claude attach <x>` | `cli.tsx` 快速路径 L236 | `cli/bg.ts` |
|
||||
| `claude kill <x>` | `cli.tsx` 快速路径 L238 | `cli/bg.ts` |
|
||||
| `claude --bg` | `cli.tsx` 快速路径 L244 | `cli/bg.ts` |
|
||||
| `claude new/list/reply` | `cli.tsx` 快速路径 L250 | `cli/handlers/templateJobs.ts` |
|
||||
| `claude rollback` | `main.tsx` Commander.js L6525 | `cli/rollback.ts` |
|
||||
| `claude up` | `main.tsx` Commander.js L6511 | `cli/up.ts` |
|
||||
|
||||
**问题**:
|
||||
- `ps/logs/attach/kill` 与 `daemon` 逻辑上都是后台进程管理,但互不关联
|
||||
- 这些命令都**只有 CLI 入口**,REPL 里输入 `/daemon` 或 `/ps` 不存在
|
||||
- `new/list/reply` 是模板任务系统的顶级命令,容易与其他命令冲突(特别是 `list`)
|
||||
|
||||
### 1.2 Windows 不支持
|
||||
|
||||
`--bg` 和 `attach` 硬依赖 tmux:
|
||||
- `bg.ts:handleBgFlag()` 第一步就检查 tmux,不可用直接报错退出
|
||||
- `bg.ts:attachHandler()` 用 `tmux attach-session`,无 tmux 替代方案
|
||||
- Windows (包括 VS Code 终端) 完全无法使用后台会话功能
|
||||
|
||||
### 1.3 无 REPL 入口
|
||||
|
||||
对比 `/mcp` 的双注册模式:
|
||||
- **CLI**: `claude mcp serve/add/remove/list` (Commander.js, `main.tsx:5760`)
|
||||
- **REPL**: `/mcp enable/disable/reconnect` (slash command, `commands/mcp/index.ts`)
|
||||
|
||||
`daemon`/`bg`/`job` 系列只有 CLI 快速路径,REPL 中完全不可用。
|
||||
|
||||
## 二、目标
|
||||
|
||||
1. **层级化命令结构**: 参照 `/mcp` 模式,将后台管理收归 `/daemon`,模板任务收归 `/job`
|
||||
2. **跨平台后台会话**: Windows / macOS / Linux 都能启动、附着、终止后台会话
|
||||
3. **双注册**: CLI (`claude daemon ...`) + REPL (`/daemon ...`) 同时可用
|
||||
4. **向后兼容**: 旧命令保留但输出 deprecation 提示
|
||||
|
||||
## 三、命令结构设计
|
||||
|
||||
### 3.1 `/daemon` — 后台进程管理
|
||||
|
||||
合并 daemon supervisor + bg sessions 为统一命名空间:
|
||||
|
||||
```
|
||||
claude daemon <subcommand> ← CLI 入口 (cli.tsx 快速路径)
|
||||
/daemon <subcommand> ← REPL 入口 (slash command, local-jsx)
|
||||
|
||||
子命令:
|
||||
status 综合状态面板 (daemon + 所有会话)
|
||||
start [--dir <path>] 启动 daemon supervisor
|
||||
stop 停止 daemon
|
||||
bg [args...] 启动后台会话
|
||||
attach [target] 附着到后台会话
|
||||
logs [target] 查看会话日志
|
||||
kill [target] 终止会话
|
||||
(无参数) 等同于 status
|
||||
```
|
||||
|
||||
**CLI 快速路径路由** (`cli.tsx`):
|
||||
```typescript
|
||||
// 新: 统一入口
|
||||
if (feature('DAEMON') && args[0] === 'daemon') {
|
||||
const sub = args[1] || 'status'
|
||||
switch (sub) {
|
||||
case 'start': case 'stop': case 'status':
|
||||
await daemonMain([sub, ...args.slice(2)])
|
||||
break
|
||||
case 'bg':
|
||||
await bg.handleBgStart(args.slice(2))
|
||||
break
|
||||
case 'attach': case 'logs': case 'kill':
|
||||
await bg[`${sub}Handler`](args[2])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 向后兼容 (deprecated)
|
||||
if (feature('BG_SESSIONS') && ['ps','logs','attach','kill'].includes(args[0])) {
|
||||
console.warn(`[deprecated] Use: claude daemon ${args[0] === 'ps' ? 'status' : args[0]}`)
|
||||
// ... delegate to daemon subcommand
|
||||
}
|
||||
```
|
||||
|
||||
**REPL 斜杠命令** (`commands/daemon/index.ts`):
|
||||
```typescript
|
||||
const daemon = {
|
||||
type: 'local-jsx',
|
||||
name: 'daemon',
|
||||
description: 'Manage background sessions and daemon',
|
||||
argumentHint: '[status|start|stop|bg|attach|logs|kill]',
|
||||
isEnabled: () => feature('DAEMON') || feature('BG_SESSIONS'),
|
||||
load: () => import('./daemon.js'),
|
||||
} satisfies Command
|
||||
```
|
||||
|
||||
### 3.2 `/job` — 模板任务管理
|
||||
|
||||
```
|
||||
claude job <subcommand> ← CLI 入口
|
||||
/job <subcommand> ← REPL 入口
|
||||
|
||||
子命令:
|
||||
list 列出模板和活跃任务
|
||||
new <template> [args] 从模板创建任务
|
||||
reply <id> <text> 回复任务
|
||||
status <id> 查看任务状态
|
||||
(无参数) 等同于 list
|
||||
```
|
||||
|
||||
### 3.3 独立命令 (不变)
|
||||
|
||||
```
|
||||
claude up 保持顶级 (简短的 bootstrap 命令)
|
||||
claude rollback [target] 保持顶级 (低频运维命令)
|
||||
```
|
||||
|
||||
## 四、跨平台后台引擎
|
||||
|
||||
### 4.1 引擎抽象
|
||||
|
||||
```typescript
|
||||
// src/cli/bg/engine.ts
|
||||
export interface BgEngine {
|
||||
readonly name: string
|
||||
|
||||
/** 当前平台是否可用 */
|
||||
available(): Promise<boolean>
|
||||
|
||||
/** 启动后台会话 */
|
||||
start(opts: BgStartOptions): Promise<BgStartResult>
|
||||
|
||||
/** 附着到后台会话(blocking) */
|
||||
attach(session: SessionEntry): Promise<void>
|
||||
}
|
||||
|
||||
export interface BgStartOptions {
|
||||
sessionName: string
|
||||
args: string[]
|
||||
env: Record<string, string | undefined>
|
||||
logPath: string
|
||||
cwd: string
|
||||
}
|
||||
|
||||
export interface BgStartResult {
|
||||
pid: number
|
||||
sessionName: string
|
||||
logPath: string
|
||||
engineUsed: string
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 三种引擎实现
|
||||
|
||||
| 引擎 | 平台 | 启动方式 | attach 方式 |
|
||||
|------|------|---------|------------|
|
||||
| TmuxEngine | macOS/Linux (有 tmux) | `tmux new-session -d` | `tmux attach-session` |
|
||||
| DetachedEngine | Windows / 无 tmux 的 macOS/Linux | `spawn({ detached, stdio→logFile })` | `tail -f` 日志文件 |
|
||||
|
||||
#### DetachedEngine 详细设计
|
||||
|
||||
**启动 (`start`)**:
|
||||
```typescript
|
||||
// 1. 打开日志文件 fd
|
||||
const logFd = fs.openSync(logPath, 'a')
|
||||
// 2. detached spawn, stdout/stderr 重定向到日志
|
||||
const child = spawn(process.execPath, execArgs, {
|
||||
detached: true,
|
||||
stdio: ['ignore', logFd, logFd],
|
||||
env,
|
||||
cwd,
|
||||
})
|
||||
child.unref()
|
||||
fs.closeSync(logFd)
|
||||
// 3. 写 sessions/<PID>.json
|
||||
```
|
||||
|
||||
**附着 (`attach`)**:
|
||||
```typescript
|
||||
// 跨平台 tail -f 实现
|
||||
// 1. 读取已有日志内容输出到 stdout
|
||||
// 2. fs.watch(logPath) 监听变化
|
||||
// 3. 每次变化读取新增内容
|
||||
// 4. Ctrl+C 退出 tail(不杀后台进程)
|
||||
```
|
||||
|
||||
#### 引擎选择逻辑
|
||||
|
||||
```typescript
|
||||
// src/cli/bg/engines/index.ts
|
||||
export async function selectEngine(): Promise<BgEngine> {
|
||||
if (process.platform === 'win32') {
|
||||
return new DetachedEngine()
|
||||
}
|
||||
|
||||
const tmux = new TmuxEngine()
|
||||
if (await tmux.available()) {
|
||||
return tmux
|
||||
}
|
||||
|
||||
return new DetachedEngine()
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 SessionEntry 扩展
|
||||
|
||||
```typescript
|
||||
interface SessionEntry {
|
||||
// ... 现有字段
|
||||
engine: 'tmux' | 'detached' // 新增: 记录使用的引擎
|
||||
tmuxSessionName?: string // tmux 引擎才有
|
||||
logPath?: string // 两种引擎都有
|
||||
}
|
||||
```
|
||||
|
||||
`attach` 时根据 `session.engine` 选择对应的 attach 策略。
|
||||
|
||||
## 五、文件变更清单
|
||||
|
||||
### 新增文件 (10 个)
|
||||
|
||||
```
|
||||
src/cli/bg/engine.ts BgEngine 接口定义
|
||||
src/cli/bg/engines/tmux.ts TmuxEngine (从 bg.ts 提取)
|
||||
src/cli/bg/engines/detached.ts DetachedEngine (新实现)
|
||||
src/cli/bg/engines/index.ts 引擎选择 + re-export
|
||||
src/cli/bg/tail.ts 跨平台日志 tail (用于 detached attach)
|
||||
src/commands/daemon/index.ts /daemon REPL 斜杠命令注册
|
||||
src/commands/daemon/daemon.tsx /daemon 子命令路由 + status UI
|
||||
src/commands/job/index.ts /job REPL 斜杠命令注册
|
||||
src/commands/job/job.tsx /job 子命令路由 + UI
|
||||
docs/features/daemon-restructure-design.md 本设计文档
|
||||
```
|
||||
|
||||
### 修改文件 (6 个)
|
||||
|
||||
```
|
||||
src/cli/bg.ts 重构: handler 函数改为调用 BgEngine
|
||||
src/entrypoints/cli.tsx 快速路径: daemon 统一入口 + 向后兼容
|
||||
src/commands.ts 注册 /daemon 和 /job 斜杠命令
|
||||
src/daemon/main.ts daemonMain() 增加 bg/ps/logs 子命令分发
|
||||
src/main.tsx Commander.js: 可选注册 daemon/job 子命令
|
||||
src/cli/handlers/templateJobs.ts 适配 /job 入口 (可能不需改)
|
||||
```
|
||||
|
||||
### 不动的文件
|
||||
|
||||
```
|
||||
src/daemon/state.ts daemon PID 状态管理 (无需改)
|
||||
src/jobs/state.ts job 状态管理 (无需改)
|
||||
src/jobs/templates.ts 模板发现 (无需改)
|
||||
src/jobs/classifier.ts 任务分类器 (无需改)
|
||||
src/cli/rollback.ts 保持顶级命令 (无需改)
|
||||
src/cli/up.ts 保持顶级命令 (无需改)
|
||||
```
|
||||
|
||||
## 六、可行性分析
|
||||
|
||||
### 6.1 风险评估
|
||||
|
||||
| 风险 | 级别 | 缓解措施 |
|
||||
|------|------|---------|
|
||||
| cli.tsx 快速路径修改影响启动性能 | 低 | 仅改路由逻辑,import 仍然 lazy |
|
||||
| DetachedEngine 的 attach 在 Windows 上 fs.watch 不可靠 | 中 | 使用轮询 fallback (setInterval + fs.stat) |
|
||||
| 向后兼容的 deprecation 可能破坏脚本 | 低 | 旧命令保持可用,仅输出 stderr 警告 |
|
||||
| REPL 中 /daemon bg 需要 spawn 子进程 | 中 | 参考 /assistant 的 NewInstallWizard (已有 spawn 先例) |
|
||||
| tsc 类型兼容 | 低 | 接口定义清晰,不引入 any |
|
||||
|
||||
### 6.2 工作量估计
|
||||
|
||||
| Task | 文件数 | 复杂度 |
|
||||
|------|--------|--------|
|
||||
| Task 013: BgEngine 抽象 + 引擎实现 | 5 新增 + 1 修改 | 中 |
|
||||
| Task 014: /daemon 命令层级化 | 3 新增 + 3 修改 | 中 |
|
||||
| Task 015: /job 命令层级化 | 2 新增 + 2 修改 | 低 |
|
||||
| Task 016: 向后兼容 + 测试 | 0 新增 + 2 修改 | 低 |
|
||||
|
||||
### 6.3 依赖关系
|
||||
|
||||
```
|
||||
Task 013 (BgEngine) ← 无依赖,可独立开发
|
||||
Task 014 (/daemon) ← 依赖 Task 013 (引擎选择)
|
||||
Task 015 (/job) ← 无依赖,可与 013 并行
|
||||
Task 016 (兼容) ← 依赖 Task 014 + 015
|
||||
```
|
||||
|
||||
## 七、设计决策记录
|
||||
|
||||
### D1: 为什么 daemon + bg sessions 合为一个命名空间?
|
||||
|
||||
用户视角:都是"后台运行的东西"。分开会导致 `claude daemon status` 看 supervisor + `claude ps` 看会话,割裂感强。合并后 `claude daemon status` 一次性展示 supervisor 状态 + 所有会话列表。
|
||||
|
||||
### D2: 为什么 rollback/up 不收入 daemon?
|
||||
|
||||
它们本质是**版本管理/环境初始化**,不是后台进程管理。`claude up` 是同步阻塞的 setup 脚本,不涉及 daemon 或后台会话。保持顶级更直观。
|
||||
|
||||
### D3: 为什么 DetachedEngine 的 attach 用 tail 而不是 IPC?
|
||||
|
||||
1. 日志文件是最简单的跨平台方案,无需额外依赖
|
||||
2. UDS Pipe IPC 系统 (usePipeIpc) 设计用于实例间通信,不是终端附着
|
||||
3. tmux attach 的体验(完整 PTY)无法在纯 detached 模式下复制,tail 是最诚实的替代
|
||||
|
||||
### D4: 为什么不用 Windows Terminal 的 tab/pane API?
|
||||
|
||||
Windows Terminal 的 `wt.exe` 新窗口/标签功能不够通用——用户可能在 VS Code、ConEmu、cmder 等终端中。detached + log 是唯一跨终端方案。
|
||||
@@ -1,12 +1,12 @@
|
||||
# DAEMON — 后台守护进程
|
||||
|
||||
> Feature Flag: `FEATURE_DAEMON=1`
|
||||
> 实现状态:主进程和 worker 注册为 Stub,CLI 路由完整
|
||||
> 实现状态:Supervisor 和 remoteControl Worker 已实现
|
||||
> 引用数:3
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
DAEMON 将 Claude Code 变为后台守护进程。主进程(supervisor)管理多个 worker 进程的生命周期,通过 Unix 域套接字进行 IPC。适用于持续运行的后台服务场景(如配合 BRIDGE_MODE 提供远程控制服务)。
|
||||
DAEMON 将 Claude Code 变为后台守护进程。主进程(supervisor)管理多个 worker 子进程的生命周期,通过文件系统状态文件进行通信。适用于持续运行的后台服务场景(如配合 BRIDGE_MODE 提供远程控制服务)。
|
||||
|
||||
## 二、实现架构
|
||||
|
||||
@@ -14,8 +14,9 @@ DAEMON 将 Claude Code 变为后台守护进程。主进程(supervisor)管
|
||||
|
||||
| 模块 | 文件 | 状态 |
|
||||
|------|------|------|
|
||||
| 守护主进程 | `src/daemon/main.ts` | **Stub** — `daemonMain: () => Promise.resolve()` |
|
||||
| Worker 注册 | `src/daemon/workerRegistry.ts` | **Stub** — `runDaemonWorker: () => Promise.resolve()` |
|
||||
| 守护主进程 | `src/daemon/main.ts` | **已实现** — Supervisor 含子命令、Worker 生命周期管理、指数退避重启 |
|
||||
| Worker 注册 | `src/daemon/workerRegistry.ts` | **已实现** — remoteControl Worker(headless bridge) |
|
||||
| Daemon 状态 | `src/daemon/state.ts` | **已实现** — PID/状态文件的读写与查询 |
|
||||
| CLI 路由 | `src/entrypoints/cli.tsx` | **布线** — `--daemon-worker` 和 `daemon` 子命令 |
|
||||
| 命令注册 | `src/commands.ts` | **布线** — DAEMON + BRIDGE_MODE 门控 |
|
||||
|
||||
@@ -23,34 +24,49 @@ DAEMON 将 Claude Code 变为后台守护进程。主进程(supervisor)管
|
||||
|
||||
```
|
||||
# 启动守护进程
|
||||
claude daemon
|
||||
claude daemon start
|
||||
|
||||
# 以 worker 身份启动
|
||||
claude --daemon-worker=<kind>
|
||||
# 查看状态(默认子命令)
|
||||
claude daemon status
|
||||
claude daemon ps
|
||||
|
||||
# 停止守护进程
|
||||
claude daemon stop
|
||||
|
||||
# 以 worker 身份启动(由 supervisor 自动调用)
|
||||
claude --daemon-worker=remoteControl
|
||||
|
||||
# 后台会话管理
|
||||
claude daemon bg
|
||||
claude daemon attach <session>
|
||||
claude daemon logs <session>
|
||||
claude daemon kill <session>
|
||||
```
|
||||
|
||||
### 2.3 预期架构
|
||||
### 2.3 架构
|
||||
|
||||
```
|
||||
Supervisor (daemonMain)
|
||||
│
|
||||
├── Worker 1: assistant-mode
|
||||
│ └── 接收和处理 assistant 会话
|
||||
│
|
||||
├── Worker 2: bridge-sync
|
||||
│ └── bridge 消息同步
|
||||
│
|
||||
└── Worker 3: proactive
|
||||
└── 主动任务执行
|
||||
├── Worker: remoteControl
|
||||
│ └── runBridgeHeadless() — 远程控制 headless 模式
|
||||
│ 接收远程会话、处理消息、权限审批
|
||||
│
|
||||
▼
|
||||
IPC via Unix Domain Sockets
|
||||
- 生命周期管理(启动、停止、重启)
|
||||
- 工作分发
|
||||
- 状态报告
|
||||
文件系统状态文件 (daemon-state.json)
|
||||
- PID、CWD、启动时间、Worker 类型
|
||||
- queryDaemonStatus() / stopDaemonByPid()
|
||||
```
|
||||
|
||||
### 2.4 与 BRIDGE_MODE 的关系
|
||||
### 2.4 Worker 生命周期管理
|
||||
|
||||
Supervisor 为每个 worker 实现:
|
||||
- **指数退避重启**:初始 2s,上限 120s,倍数 ×2
|
||||
- **快速失败检测**:10s 内连续崩溃 5 次则 parking(不再重启)
|
||||
- **永久错误退出码**:78 (EXIT_CODE_PERMANENT) 导致直接 parking
|
||||
- **优雅关闭**:SIGTERM/SIGINT → abort signal → 30s 强制 SIGKILL
|
||||
|
||||
### 2.5 与 BRIDGE_MODE 的关系
|
||||
|
||||
DAEMON 和 BRIDGE_MODE 常组合使用:
|
||||
|
||||
@@ -63,40 +79,39 @@ if (feature('DAEMON') && feature('BRIDGE_MODE')) {
|
||||
|
||||
双重门控:两个 feature 都需要开启才能使用远程控制服务器。
|
||||
|
||||
## 三、需要补全的内容
|
||||
|
||||
| 模块 | 工作量 | 说明 |
|
||||
|------|--------|------|
|
||||
| `daemon/main.ts` | 大 | Supervisor 主进程:启动 worker、生命周期管理、IPC |
|
||||
| `daemon/workerRegistry.ts` | 中 | Worker 类型分发(assistant/bridge-sync/proactive) |
|
||||
| Worker 实现 | 大 | 各类型 worker 的具体实现 |
|
||||
| IPC 协议 | 中 | Supervisor-Worker 通信层 |
|
||||
|
||||
## 四、关键设计决策
|
||||
## 三、关键设计决策
|
||||
|
||||
1. **多进程架构**:一个 supervisor + 多个 worker,进程隔离
|
||||
2. **Unix 域套接字 IPC**:本地进程间通信,低延迟
|
||||
2. **文件系统状态通信**:通过 `daemon-state.json` 文件进行状态共享(非 Unix 域套接字)
|
||||
3. **与 BRIDGE_MODE 强绑定**:守护进程最常见的用途是提供远程控制服务
|
||||
4. **CLI 子命令路由**:`daemon` 子命令和 `--daemon-worker` 参数在 `cli.tsx` 中路由
|
||||
5. **Worker 环境变量**:supervisor 通过环境变量(`DAEMON_WORKER_*`)向 worker 传递配置
|
||||
|
||||
## 五、使用方式
|
||||
## 四、使用方式
|
||||
|
||||
```bash
|
||||
# 启用守护进程模式
|
||||
FEATURE_DAEMON=1 FEATURE_BRIDGE_MODE=1 bun run dev
|
||||
|
||||
# 启动守护进程
|
||||
claude daemon
|
||||
claude daemon start
|
||||
|
||||
# 以特定 worker 启动
|
||||
claude --daemon-worker=assistant
|
||||
# 查看状态
|
||||
claude daemon status
|
||||
|
||||
# 停止守护进程
|
||||
claude daemon stop
|
||||
|
||||
# 以特定 worker 启动(通常由 supervisor 自动调用)
|
||||
claude --daemon-worker=remoteControl
|
||||
```
|
||||
|
||||
## 六、文件索引
|
||||
## 五、文件索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/daemon/main.ts` | Supervisor 主进程(stub) |
|
||||
| `src/daemon/workerRegistry.ts` | Worker 注册(stub) |
|
||||
| `src/entrypoints/cli.tsx:95,149` | CLI 路由 |
|
||||
| `src/commands.ts:77` | 命令注册(双重门控) |
|
||||
| `src/daemon/main.ts` | Supervisor 主进程:子命令分发、Worker 生命周期管理、退避重启 |
|
||||
| `src/daemon/workerRegistry.ts` | Worker 入口:remoteControl worker 实现 |
|
||||
| `src/daemon/state.ts` | Daemon 状态管理:PID 文件读写、状态查询 |
|
||||
| `src/entrypoints/cli.tsx` | CLI 路由 |
|
||||
| `src/commands.ts` | 命令注册(双重门控) |
|
||||
|
||||
@@ -27,13 +27,15 @@ bun run dev:inspect
|
||||
|
||||
## 原理
|
||||
|
||||
`dev:inspect` 脚本实际执行的是:
|
||||
`dev:inspect` 脚本实际执行的是 `scripts/dev-debug.ts`:
|
||||
|
||||
```bash
|
||||
bun --inspect-wait=localhost:8888/<token> run scripts/dev.ts
|
||||
```typescript
|
||||
// scripts/dev-debug.ts
|
||||
process.env.BUN_INSPECT = "localhost:8888/<token>"
|
||||
await import("./dev")
|
||||
```
|
||||
|
||||
Bun 的 `--inspect-wait` 参数启动一个 Chrome DevTools Protocol 兼容的 inspect 服务,等待调试器连接后才开始执行。VS Code 的 `bun` 扩展通过 WebSocket 连接到这个地址实现 attach。
|
||||
通过设置 `BUN_INSPECT` 环境变量启动一个 Chrome DevTools Protocol 兼容的 inspect 服务,然后导入 dev 模式入口。VS Code 的 `bun` 扩展通过 WebSocket 连接到输出的地址实现 attach。
|
||||
|
||||
## JetBrains IDE
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Claude Code 使用三层门控系统:
|
||||
|------|------|----------|
|
||||
| COMPLETE | 22 | BRIDGE_MODE, COORDINATOR_MODE, CONTEXT_COLLAPSE, VOICE_MODE, TEAMMEM, COMMIT_ATTRIBUTION, ULTRAPLAN, BASH_CLASSIFIER, TRANSCRIPT_CLASSIFIER, EXTRACT_MEMORIES, CACHED_MICROCOMPACT, TOKEN_BUDGET, AGENT_TRIGGERS, REACTIVE_COMPACT, KAIROS_BRIEF, CCR_REMOTE_SETUP, SHOT_STATS, BG_SESSIONS, PROACTIVE, CHICAGO_MCP, VERIFICATION_AGENT, PROMPT_CACHE_BREAK_DETECTION |
|
||||
| PARTIAL | 19 | KAIROS, BUDDY, MONITOR_TOOL, HISTORY_SNIP, WORKFLOW_SCRIPTS, UDS_INBOX, KAIROS_CHANNELS, FORK_SUBAGENT, EXPERIMENTAL_SKILL_SEARCH, WEB_BROWSER_TOOL, MCP_SKILLS, REVIEW_ARTIFACT, KAIROS_GITHUB_WEBHOOKS, CONNECTOR_TEXT, TEMPLATES, LODESTONE, HISTORY_PICKER, MESSAGE_ACTIONS, TERMINAL_PANEL |
|
||||
| STUB | 51 | TORCH, KAIROS_DREAM, KAIROS_PUSH_NOTIFICATION, DAEMON, DIRECT_CONNECT, SSH_REMOTE, STREAMLINED_OUTPUT, ANTI_DISTILLATION_CC, NATIVE_CLIENT_ATTESTATION, ABLATION_BASELINE, AGENT_MEMORY_SNAPSHOT, AGENT_TRIGGERS_REMOTE, ALLOW_TEST_VERSIONS, AUTO_THEME, AWAY_SUMMARY, BREAK_CACHE_COMMAND, BUILDING_CLAUDE_APPS, BUILTIN_EXPLORE_PLAN_AGENTS, BYOC_ENVIRONMENT_RUNNER, CCR_AUTO_CONNECT, CCR_MIRROR, COMPACTION_REMINDERS, COWORKER_TYPE_TELEMETRY, DOWNLOAD_USER_SETTINGS, DUMP_SYSTEM_PROMPT, ENHANCED_TELEMETRY_BETA, FILE_PERSISTENCE, HARD_FAIL, HOOK_PROMPTS, IS_LIBC_GLIBC, IS_LIBC_MUSL, MCP_RICH_OUTPUT, MEMORY_SHAPE_TELEMETRY, NATIVE_CLIPBOARD_IMAGE, NEW_INIT, OVERFLOW_TEST_TOOL, PERFETTO_TRACING, POWERSHELL_AUTO_MODE, QUICK_SEARCH, RUN_SKILL_GENERATOR, SELF_HOSTED_RUNNER, SKILL_IMPROVEMENT, SLOW_OPERATION_LOGGING, TREE_SITTER_BASH, TREE_SITTER_BASH_SHADOW, ULTRATHINK, UNATTENDED_RETRY, UPLOAD_USER_SETTINGS, SKIP_DETECTION_WHEN_AUTOUPDATES_DISABLED |
|
||||
| STUB | 38 | TORCH, KAIROS_DREAM, KAIROS_PUSH_NOTIFICATION, DIRECT_CONNECT, SSH_REMOTE, STREAMLINED_OUTPUT, ANTI_DISTILLATION_CC, NATIVE_CLIENT_ATTESTATION, ABLATION_BASELINE, AGENT_MEMORY_SNAPSHOT, ALLOW_TEST_VERSIONS, AUTO_THEME, BREAK_CACHE_COMMAND, BUILDING_CLAUDE_APPS, BYOC_ENVIRONMENT_RUNNER, CCR_AUTO_CONNECT, CCR_MIRROR, COMPACTION_REMINDERS, COWORKER_TYPE_TELEMETRY, DOWNLOAD_USER_SETTINGS, DUMP_SYSTEM_PROMPT, ENHANCED_TELEMETRY_BETA, FILE_PERSISTENCE, HARD_FAIL, HOOK_PROMPTS, IS_LIBC_GLIBC, IS_LIBC_MUSL, MCP_RICH_OUTPUT, MEMORY_SHAPE_TELEMETRY, NATIVE_CLIPBOARD_IMAGE, NEW_INIT, OVERFLOW_TEST_TOOL, PERFETTO_TRACING, POWERSHELL_AUTO_MODE, QUICK_SEARCH, RUN_SKILL_GENERATOR, SELF_HOSTED_RUNNER, SKILL_IMPROVEMENT, SLOW_OPERATION_LOGGING, TREE_SITTER_BASH, TREE_SITTER_BASH_SHADOW, UNATTENDED_RETRY, UPLOAD_USER_SETTINGS, SKIP_DETECTION_WHEN_AUTOUPDATES_DISABLED |
|
||||
|
||||
---
|
||||
|
||||
@@ -51,14 +51,31 @@ Claude Code 使用三层门控系统:
|
||||
| SHOT_STATS | **ON** | **ON** | compile-only, 已验证 | 纯本地统计 |
|
||||
| PROMPT_CACHE_BREAK_DETECTION | **ON** | **ON** | compile-only, 已验证 | 内部诊断 |
|
||||
| TOKEN_BUDGET | **ON** | **ON** | compile-only, 已验证 | 支持 `+500k` 语法 |
|
||||
| AGENT_TRIGGERS | **ON** | **ON** | compile+GB gate, 已验证 | 本轮新增,定时任务系统 |
|
||||
| EXTRACT_MEMORIES | **ON** | **ON** | compile+GB gate, 已验证 | 本轮新增,自动记忆提取 |
|
||||
| VERIFICATION_AGENT | **ON** | **ON** | compile+GB gate, 已验证 | 本轮新增,对抗性验证代理 |
|
||||
| KAIROS_BRIEF | **ON** | **ON** | compile+GB gate, 已验证 | 本轮新增,Brief 精简模式 |
|
||||
| AWAY_SUMMARY | **ON** | **ON** | compile+GB gate, 已验证 | 本轮新增,离开摘要 |
|
||||
| AGENT_TRIGGERS | **ON** | **ON** | compile+GB gate, 已验证 | 本地定时任务系统 |
|
||||
| ULTRATHINK | **ON** | **ON** | compile-only | 扩展思考模式 |
|
||||
| BUILTIN_EXPLORE_PLAN_AGENTS | **ON** | **ON** | compile-only | 内置 Explore/Plan agent |
|
||||
| LODESTONE | **ON** | **ON** | compile-only | 深度链接 URL 协议 |
|
||||
| EXTRACT_MEMORIES | **ON** | **ON** | compile+GB gate, 已验证 | 自动记忆提取 |
|
||||
| VERIFICATION_AGENT | **ON** | **ON** | compile+GB gate, 已验证 | 对抗性验证代理 |
|
||||
| KAIROS_BRIEF | **ON** | **ON** | compile+GB gate, 已验证 | Brief 精简模式 |
|
||||
| AWAY_SUMMARY | **ON** | **ON** | compile+GB gate, 已验证 | 离开摘要 |
|
||||
| ULTRAPLAN | **ON** | **ON** | compile+remote | 高级规划,需 CCR 基础设施 |
|
||||
| DAEMON | **ON** | **ON** | compile-only | 后台守护进程 |
|
||||
| ACP | **ON** | **ON** | compile-only | ACP 协议支持 |
|
||||
| WORKFLOW_SCRIPTS | **ON** | **ON** | compile-only | 工作流脚本 |
|
||||
| HISTORY_SNIP | **ON** | **ON** | compile-only | 历史管理 |
|
||||
| CONTEXT_COLLAPSE | **ON** | **ON** | compile-only | 上下文折叠(核心 stub) |
|
||||
| MONITOR_TOOL | **ON** | **ON** | compile-only | 后台监控 |
|
||||
| FORK_SUBAGENT | **ON** | **ON** | compile-only | 子 Agent |
|
||||
| KAIROS | **ON** | **ON** | compile-only | Kairos 调度 |
|
||||
| COORDINATOR_MODE | **ON** | **ON** | compile-only | 多 Worker 协调 |
|
||||
| BUDDY | off | **ON** | compile+GrowthBook | 仅 dev 模式 |
|
||||
| TRANSCRIPT_CLASSIFIER | off | **ON** | compile+GrowthBook | 仅 dev 模式 |
|
||||
| BRIDGE_MODE | off | **ON** | compile+remote | 仅 dev 模式,需 claude.ai 订阅 |
|
||||
| UDS_INBOX | off | **ON** | compile-only | 仅 dev 模式 |
|
||||
| LAN_PIPES | off | **ON** | compile-only | 仅 dev 模式 |
|
||||
| BG_SESSIONS | off | **ON** | compile+GB gate | 仅 dev 模式 |
|
||||
| TEMPLATES | off | **ON** | compile-only | 仅 dev 模式 |
|
||||
|
||||
---
|
||||
|
||||
@@ -124,9 +141,9 @@ Claude Code 使用三层门控系统:
|
||||
8. src/hooks/useReplBridge.tsx — REPL 桥接 Hook
|
||||
9. src/main.tsx — 主入口中的桥接模式启动
|
||||
10. src/screens/REPL.tsx — REPL 屏幕中的桥接集成
|
||||
11. src/tools/BriefTool/attachments.ts — Brief 工具附件处理
|
||||
12. src/tools/BriefTool/upload.ts — Brief 工具上传
|
||||
13. src/tools/ConfigTool/supportedSettings.ts — 配置工具中的桥接设置
|
||||
11. packages/builtin-tools/src/tools/BriefTool/attachments.ts — Brief 工具附件处理
|
||||
12. packages/builtin-tools/src/tools/BriefTool/upload.ts — Brief 工具上传
|
||||
13. packages/builtin-tools/src/tools/ConfigTool/supportedSettings.ts — 配置工具中的桥接设置
|
||||
|
||||
**启用所需操作**: 仅需将编译标志 `BRIDGE_MODE` 设为 `true`。所有代码完整,命令入口 `src/commands/bridge/index.ts`(604 行)和 `src/commands/bridge/bridge.tsx`(46,907 行)均存在。
|
||||
|
||||
@@ -185,8 +202,8 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
7. src/screens/REPL.tsx — REPL 屏幕中的协调器集成
|
||||
8. src/screens/ResumeConversation.tsx — 恢复对话时的协调器处理
|
||||
9. src/tools.ts — 工具注册中的协调器工具
|
||||
10. src/tools/AgentTool/AgentTool.tsx — Agent 工具中的协调器模式分支
|
||||
11. src/tools/AgentTool/builtInAgents.ts — 内置代理定义
|
||||
10. packages/builtin-tools/src/tools/AgentTool/AgentTool.tsx — Agent 工具中的协调器模式分支
|
||||
11. packages/builtin-tools/src/tools/AgentTool/builtInAgents.ts — 内置代理定义
|
||||
12. src/utils/processUserInput/processSlashCommand.tsx — 斜杠命令处理中的协调器
|
||||
13. src/utils/sessionRestore.ts — 会话恢复中的协调器状态
|
||||
14. src/utils/systemPrompt.ts — 系统提示中的协调器指令
|
||||
@@ -259,9 +276,9 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
9. src/screens/REPL.tsx — REPL 中的语音模式集成
|
||||
10. src/services/voiceStreamSTT.ts — STT 服务
|
||||
11. src/state/AppState.tsx — 应用状态中的语音状态
|
||||
12. src/tools/ConfigTool/ConfigTool.ts — 配置工具中的语音设置
|
||||
13. src/tools/ConfigTool/prompt.ts — 配置工具提示
|
||||
14. src/tools/ConfigTool/supportedSettings.ts — 支持的设置项
|
||||
12. packages/builtin-tools/src/tools/ConfigTool/ConfigTool.ts — 配置工具中的语音设置
|
||||
13. packages/builtin-tools/src/tools/ConfigTool/prompt.ts — 配置工具提示
|
||||
14. packages/builtin-tools/src/tools/ConfigTool/supportedSettings.ts — 支持的设置项
|
||||
15. src/utils/settings/types.ts — 设置类型定义
|
||||
16. src/voice/voiceModeEnabled.ts — 语音模式启用逻辑
|
||||
|
||||
@@ -385,8 +402,8 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
11. src/hooks/toolPermission/permissionLogging.ts — 权限日志
|
||||
12. src/hooks/useCanUseTool.tsx — 工具可用性检查
|
||||
13. src/services/api/withRetry.ts — API 重试中的分类器
|
||||
14. src/tools/BashTool/bashPermissions.ts — Bash 权限逻辑
|
||||
15. src/tools/BashTool/pathValidation.ts — 路径验证
|
||||
14. packages/builtin-tools/src/tools/BashTool/bashPermissions.ts — Bash 权限逻辑
|
||||
15. packages/builtin-tools/src/tools/BashTool/pathValidation.ts — 路径验证
|
||||
16. src/utils/classifierApprovals.ts — 分类器审批记录
|
||||
17. src/utils/messages.ts — 消息处理
|
||||
18. src/utils/permissions/permissions.ts — 权限核心
|
||||
@@ -431,11 +448,11 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
22. src/screens/REPL.tsx — REPL 屏幕
|
||||
23. src/services/api/claude.ts — Claude API 服务
|
||||
24. src/services/tools/toolExecution.ts — 工具执行
|
||||
25. src/tools/AgentTool/AgentTool.tsx — Agent 工具
|
||||
26. src/tools/AgentTool/agentToolUtils.ts — Agent 工具工具函数
|
||||
27. src/tools/AgentTool/runAgent.ts — 运行 Agent
|
||||
28. src/tools/BashTool/bashPermissions.ts — Bash 权限
|
||||
29. src/tools/ConfigTool/supportedSettings.ts — 支持的设置
|
||||
25. packages/builtin-tools/src/tools/AgentTool/AgentTool.tsx — Agent 工具
|
||||
26. packages/builtin-tools/src/tools/AgentTool/agentToolUtils.ts — Agent 工具工具函数
|
||||
27. packages/builtin-tools/src/tools/AgentTool/runAgent.ts — 运行 Agent
|
||||
28. packages/builtin-tools/src/tools/BashTool/bashPermissions.ts — Bash 权限
|
||||
29. packages/builtin-tools/src/tools/ConfigTool/supportedSettings.ts — 支持的设置
|
||||
30. src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.ts — 退出计划模式工具
|
||||
31. src/tools/NotebookEditTool/NotebookEditTool.ts — Notebook 编辑工具
|
||||
32. src/types/permissions.ts — 权限类型
|
||||
@@ -543,11 +560,10 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
|
||||
| 文件路径 | 行数 | 功能说明 |
|
||||
|----------|------|----------|
|
||||
| src/tools/ScheduleCronTool/CronCreateTool.ts | 157 行 | Cron 创建工具 |
|
||||
| src/tools/ScheduleCronTool/prompt.ts | 135 行 | Cron 工具提示词 |
|
||||
| src/tools/ScheduleCronTool/CronListTool.ts | 97 行 | Cron 列表工具 |
|
||||
| src/tools/ScheduleCronTool/CronDeleteTool.ts | 95 行 | Cron 删除工具 |
|
||||
| src/tools/ScheduleCronTool/UI.tsx | 59 行 | Cron UI 组件 |
|
||||
| packages/builtin-tools/src/tools/ScheduleCronTool/CronCreateTool.ts | 157 行 | Cron 创建工具 |
|
||||
| packages/builtin-tools/src/tools/ScheduleCronTool/prompt.ts | 135 行 | Cron 工具提示词 |
|
||||
| packages/builtin-tools/src/tools/ScheduleCronTool/CronListTool.ts | 97 行 | Cron 列表工具 |
|
||||
| packages/builtin-tools/src/tools/ScheduleCronTool/CronDeleteTool.ts | 95 行 | Cron 删除工具 |
|
||||
|
||||
**引用该标志的文件(6 个)**:
|
||||
1. src/cli/print.ts — CLI 输出
|
||||
@@ -598,7 +614,7 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
|
||||
| 文件路径 | 行数 | 功能说明 |
|
||||
|----------|------|----------|
|
||||
| src/tools/BriefTool/BriefTool.ts | 204 行 | Brief 工具核心 |
|
||||
| packages/builtin-tools/src/tools/BriefTool/BriefTool.ts | 204 行 | Brief 工具核心 |
|
||||
| src/commands/brief.ts | 130 行 | Brief 命令实现 |
|
||||
|
||||
**引用该标志的文件(20 个)**:
|
||||
@@ -616,7 +632,7 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
12. src/hooks/useGlobalKeybindings.tsx — 全局键绑定
|
||||
13. src/keybindings/defaultBindings.ts — 默认键绑定
|
||||
14. src/main.tsx — 主入口
|
||||
15. src/tools/BriefTool/BriefTool.ts — Brief 工具
|
||||
15. packages/builtin-tools/src/tools/BriefTool/BriefTool.ts — Brief 工具
|
||||
16. src/tools/ToolSearchTool/prompt.ts — 工具搜索提示
|
||||
17. src/utils/attachments.ts — 附件
|
||||
18. src/utils/conversationRecovery.ts — 对话恢复
|
||||
@@ -718,7 +734,7 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
9. src/screens/REPL.tsx — REPL(多处引用,通过 require 加载 proactive 模块)
|
||||
10. src/services/compact/prompt.ts — 压缩提示
|
||||
11. src/tools.ts — 工具注册
|
||||
12. src/tools/AgentTool/AgentTool.tsx — Agent 工具
|
||||
12. packages/builtin-tools/src/tools/AgentTool/AgentTool.tsx — Agent 工具
|
||||
13. src/utils/sessionStorage.ts — 会话存储
|
||||
14. src/utils/settings/types.ts — 设置类型
|
||||
15. src/utils/systemPrompt.ts — 系统提示
|
||||
@@ -771,11 +787,11 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
| 文件路径 | 行数 | 功能说明 |
|
||||
|----------|------|----------|
|
||||
| src/tools/TaskUpdateTool/TaskUpdateTool.ts | 406 行 | 任务更新工具 |
|
||||
| src/tools/AgentTool/builtInAgents.ts | 72 行 | 内置代理定义 |
|
||||
| packages/builtin-tools/src/tools/AgentTool/builtInAgents.ts | 72 行 | 内置代理定义 |
|
||||
|
||||
**引用该标志的文件(4 个)**:
|
||||
1. src/constants/prompts.ts — 提示词
|
||||
2. src/tools/AgentTool/builtInAgents.ts — 内置代理
|
||||
2. packages/builtin-tools/src/tools/AgentTool/builtInAgents.ts — 内置代理
|
||||
3. src/tools/TaskUpdateTool/TaskUpdateTool.ts — 任务更新工具
|
||||
4. src/tools/TodoWriteTool/TodoWriteTool.ts — TodoWrite 工具
|
||||
|
||||
@@ -796,7 +812,7 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
3. src/services/compact/autoCompact.ts — 自动压缩
|
||||
4. src/services/compact/compact.ts — 压缩核心
|
||||
5. src/services/compact/microCompact.ts — 微压缩
|
||||
6. src/tools/AgentTool/runAgent.ts — 运行 Agent
|
||||
6. packages/builtin-tools/src/tools/AgentTool/runAgent.ts — 运行 Agent
|
||||
|
||||
**启用所需操作**: 仅需将编译标志 `PROMPT_CACHE_BREAK_DETECTION` 设为 `true`。
|
||||
|
||||
@@ -856,11 +872,11 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
38. src/services/mcp/useManageMCPConnections.ts
|
||||
39. src/skills/bundled/index.ts
|
||||
40. src/tools.ts
|
||||
41. src/tools/AgentTool/AgentTool.tsx
|
||||
41. packages/builtin-tools/src/tools/AgentTool/AgentTool.tsx
|
||||
42. src/tools/AskUserQuestionTool/AskUserQuestionTool.tsx
|
||||
43. src/tools/BashTool/BashTool.tsx
|
||||
44. src/tools/BriefTool/BriefTool.ts
|
||||
45. src/tools/ConfigTool/supportedSettings.ts
|
||||
43. packages/builtin-tools/src/tools/BashTool/BashTool.tsx
|
||||
44. packages/builtin-tools/src/tools/BriefTool/BriefTool.ts
|
||||
45. packages/builtin-tools/src/tools/ConfigTool/supportedSettings.ts
|
||||
46. src/tools/EnterPlanModeTool/EnterPlanModeTool.ts
|
||||
47. src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.ts
|
||||
48. src/tools/PowerShellTool/PowerShellTool.tsx
|
||||
@@ -877,8 +893,8 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
59. src/utils/systemPrompt.ts
|
||||
|
||||
**缺失文件**:
|
||||
- src/commands/assistant/index.ts — 完全缺失(src/commands.ts 第 69 行引用了 `commands/assistant/index.js`)
|
||||
- src/commands/assistant/gate.ts — 完全缺失
|
||||
- ~~src/commands/assistant/index.ts~~ — 已补全
|
||||
- ~~src/commands/assistant/gate.ts~~ — 已补全
|
||||
|
||||
**启用所需修复**: 需要创建 `src/commands/assistant/` 目录及其 `index.ts` 和 `gate.ts` 文件。
|
||||
|
||||
@@ -930,7 +946,7 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
| 文件路径 | 行数 | 功能说明 |
|
||||
|----------|------|----------|
|
||||
| src/tasks/LocalShellTask/LocalShellTask.tsx | 522 行 | 本地 Shell 任务完整实现 |
|
||||
| src/tools/MonitorTool/MonitorTool.ts | 1 行 | 监控工具(桩) |
|
||||
| packages/builtin-tools/src/tools/MonitorTool/MonitorTool.ts | 1 行 | 监控工具(桩) |
|
||||
| src/tasks/MonitorMcpTask/MonitorMcpTask.ts | 5 行 | MCP 监控任务(桩) |
|
||||
| src/components/tasks/MonitorMcpDetailDialog.tsx | 3 行 | MCP 详情对话框(桩) |
|
||||
| src/components/permissions/MonitorPermissionRequest/MonitorPermissionRequest.tsx | 3 行 | 监控权限请求(桩) |
|
||||
@@ -941,12 +957,12 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
3. src/tasks.ts — 任务注册
|
||||
4. src/tasks/LocalShellTask/LocalShellTask.tsx — Shell 任务
|
||||
5. src/tools.ts — 工具注册
|
||||
6. src/tools/AgentTool/runAgent.ts — Agent 运行
|
||||
7. src/tools/BashTool/BashTool.tsx — Bash 工具
|
||||
8. src/tools/BashTool/prompt.ts — Bash 提示
|
||||
6. packages/builtin-tools/src/tools/AgentTool/runAgent.ts — Agent 运行
|
||||
7. packages/builtin-tools/src/tools/BashTool/BashTool.tsx — Bash 工具
|
||||
8. packages/builtin-tools/src/tools/BashTool/prompt.ts — Bash 提示
|
||||
9. src/tools/PowerShellTool/PowerShellTool.tsx — PowerShell 工具
|
||||
|
||||
**启用所需修复**: 需要实现 `src/tools/MonitorTool/MonitorTool.ts`、`src/tasks/MonitorMcpTask/MonitorMcpTask.ts`、`src/components/tasks/MonitorMcpDetailDialog.tsx` 和 `src/components/permissions/MonitorPermissionRequest/MonitorPermissionRequest.tsx`。
|
||||
**启用所需修复**: 需要实现 `packages/builtin-tools/src/tools/MonitorTool/MonitorTool.ts`、`src/tasks/MonitorMcpTask/MonitorMcpTask.ts`、`src/components/tasks/MonitorMcpDetailDialog.tsx` 和 `src/components/permissions/MonitorPermissionRequest/MonitorPermissionRequest.tsx`。
|
||||
|
||||
---
|
||||
|
||||
@@ -988,10 +1004,11 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
| src/components/WorkflowMultiselectDialog.tsx | 127 行 | 工作流多选对话框(有内容) |
|
||||
| src/tasks/LocalWorkflowTask/LocalWorkflowTask.ts | 5 行 | 本地工作流任务(桩) |
|
||||
| src/components/tasks/WorkflowDetailDialog.tsx | 3 行 | 工作流详情对话框(桩) |
|
||||
| src/tools/WorkflowTool/WorkflowPermissionRequest.tsx | 3 行 | 工作流权限请求(桩) |
|
||||
| src/tools/WorkflowTool/createWorkflowCommand.ts | 3 行 | 创建工作流命令(桩) |
|
||||
| src/tools/WorkflowTool/WorkflowTool.ts | 1 行 | 工作流工具(桩) |
|
||||
| src/tools/WorkflowTool/constants.ts | 1 行 | 常量(桩) |
|
||||
| packages/builtin-tools/src/tools/WorkflowTool/WorkflowPermissionRequest.tsx | ~80 行 | 工作流权限请求组件 |
|
||||
| packages/builtin-tools/src/tools/WorkflowTool/createWorkflowCommand.ts | 41 行 | 创建工作流命令(已实现) |
|
||||
| packages/builtin-tools/src/tools/WorkflowTool/WorkflowTool.ts | 74 行 | 工作流工具(部分实现,call 需运行时) |
|
||||
| packages/builtin-tools/src/tools/WorkflowTool/constants.ts | ~10 行 | 常量定义 |
|
||||
| packages/builtin-tools/src/tools/WorkflowTool/bundled/index.ts | ~20 行 | 内置工作流初始化 |
|
||||
|
||||
**引用该标志的文件(7 个)**:
|
||||
1. src/commands.ts — 命令注册(引用 `commands/workflows/index.js`)
|
||||
@@ -1011,38 +1028,32 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
|
||||
## 28. UDS_INBOX
|
||||
|
||||
**编译时引用次数**: 18(单引号 17 + 双引号 1)
|
||||
**功能描述**: UDS(Unix Domain Socket)收件箱。允许 Claude Code 实例之间通过 Unix 套接字发送消息。
|
||||
**分类**: PARTIAL
|
||||
**缺失原因**: `src/utils/udsMessaging.ts` 仅 1 行,`src/utils/udsClient.ts` 仅 3 行(空壳),命令入口缺失
|
||||
**编译时引用次数**: 18(历史快照)
|
||||
**功能描述**: 本机进程间通信能力。当前由两层组成:
|
||||
1. `udsMessaging` / `udsClient`:通用 UDS 消息层,供 `SendMessageTool` 与 `/peers` 使用。
|
||||
2. `pipeTransport` / `pipeRegistry`:会话级 named-pipe 协调层,供 `/pipes`、`/attach`、`/detach`、`/send`、`/pipe-status`、`/history`、`/claim-main` 使用。
|
||||
|
||||
**当前分类**: IMPLEMENTED / EXPERIMENTAL
|
||||
|
||||
**当前事实**:
|
||||
- `src/utils/udsMessaging.ts` 与 `src/utils/udsClient.ts` 已实现,不再是空壳。
|
||||
- `src/utils/pipeTransport.ts` 使用本机 named pipe / Unix socket;`localIp` / `hostname` / `machineId` 仅用于注册表展示与身份判定,不是已上线的局域网传输层。
|
||||
- `src/screens/REPL.tsx` 内联承载当前有效的 pipe 控制平面;早期 hook 试验路径已清理。
|
||||
|
||||
**核心实现文件**:
|
||||
|
||||
| 文件路径 | 行数 | 功能说明 |
|
||||
|----------|------|----------|
|
||||
| src/tools/SendMessageTool/SendMessageTool.ts | 917 行 | 发送消息工具(完整实现) |
|
||||
| src/tools/SendMessageTool/prompt.ts | 49 行 | 消息工具提示词 |
|
||||
| src/utils/udsClient.ts | 3 行 | UDS 客户端(桩) |
|
||||
| src/utils/udsMessaging.ts | 1 行 | UDS 消息(桩) |
|
||||
| 文件路径 | 功能说明 |
|
||||
|----------|----------|
|
||||
| src/utils/udsMessaging.ts | 通用 UDS server / inbox |
|
||||
| src/utils/udsClient.ts | 通用 peer 发现、探活、发送 |
|
||||
| src/utils/pipeTransport.ts | named-pipe server/client、探活、AppState 扩展 |
|
||||
| src/utils/pipeRegistry.ts | main/sub 注册表、machineId、claim-main |
|
||||
| src/commands/peers/peers.ts | UDS peer 可达性检查 |
|
||||
| src/commands/pipes/pipes.ts | pipe 注册表检查与选择器入口 |
|
||||
| src/commands/attach/attach.ts | master -> slave attach |
|
||||
| src/screens/REPL.tsx | 当前生效的 REPL pipe bootstrap 与心跳 |
|
||||
|
||||
**引用该标志的文件(10 个)**:
|
||||
1. src/cli/print.ts — CLI 输出
|
||||
2. src/commands.ts — 命令注册(引用 `commands/peers/index.js`)
|
||||
3. src/components/messages/UserTextMessage.tsx — 用户消息
|
||||
4. src/main.tsx — 主入口
|
||||
5. src/setup.ts — 初始化
|
||||
6. src/tools.ts — 工具注册
|
||||
7. src/tools/SendMessageTool/SendMessageTool.ts — 发送消息工具
|
||||
8. src/tools/SendMessageTool/prompt.ts — 提示词
|
||||
9. src/utils/concurrentSessions.ts — 并发会话
|
||||
10. src/utils/messages/systemInit.ts — 系统初始化消息
|
||||
|
||||
**缺失文件**:
|
||||
- src/commands/peers/index.ts — 命令入口缺失
|
||||
- src/utils/udsMessaging.ts — 仅 1 行空壳
|
||||
- src/utils/udsClient.ts — 仅 3 行空壳
|
||||
|
||||
**启用所需修复**: 需要实现 UDS 客户端和消息模块,并创建命令入口。
|
||||
**备注**: 如需真实局域网通信,需要单独引入 TCP/WebSocket 传输、认证与发现机制;现有代码尚未实现该层。详见 `docs/features/uds-inbox.md`。
|
||||
|
||||
---
|
||||
|
||||
@@ -1092,13 +1103,13 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
|
||||
| 文件路径 | 行数 | 功能说明 |
|
||||
|----------|------|----------|
|
||||
| src/tools/AgentTool/forkSubagent.ts | 210 行 | 分叉子代理核心逻辑 |
|
||||
| packages/builtin-tools/src/tools/AgentTool/forkSubagent.ts | 210 行 | 分叉子代理核心逻辑 |
|
||||
|
||||
**引用该标志的文件(5 个)**:
|
||||
1. src/commands.ts — 命令注册
|
||||
2. src/commands/branch/index.ts — 分支命令入口
|
||||
3. src/components/messages/UserTextMessage.tsx — 用户消息
|
||||
4. src/tools/AgentTool/forkSubagent.ts — 分叉逻辑
|
||||
4. packages/builtin-tools/src/tools/AgentTool/forkSubagent.ts — 分叉逻辑
|
||||
5. src/tools/ToolSearchTool/prompt.ts — 工具搜索提示
|
||||
|
||||
**缺失文件**:
|
||||
@@ -1122,7 +1133,7 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
4. src/query.ts — 查询
|
||||
5. src/services/compact/compact.ts — 压缩
|
||||
6. src/services/mcp/useManageMCPConnections.ts — MCP 连接管理
|
||||
7. src/tools/SkillTool/SkillTool.ts — 技能工具(1,108 行)
|
||||
7. packages/builtin-tools/src/tools/SkillTool/SkillTool.ts — 技能工具(1,108 行)
|
||||
8. src/utils/attachments.ts — 附件
|
||||
9. src/utils/messages.ts — 消息
|
||||
|
||||
@@ -1342,21 +1353,24 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
**引用文件**:
|
||||
1. src/components/Settings/Config.tsx — 设置
|
||||
2. src/tools.ts — 工具注册
|
||||
3. src/tools/ConfigTool/supportedSettings.ts — 支持的设置
|
||||
3. packages/builtin-tools/src/tools/ConfigTool/supportedSettings.ts — 支持的设置
|
||||
**代码量**: 0 行专属代码,仅在设置中预留了开关位
|
||||
|
||||
---
|
||||
|
||||
## 45. DAEMON
|
||||
## 45. DAEMON `[build: ON] [dev: ON]`
|
||||
|
||||
**编译时引用次数**: 3
|
||||
**功能描述**: 守护进程模式。
|
||||
**分类**: STUB
|
||||
**功能描述**: 守护进程模式。允许 Claude Code 作为后台长驻 supervisor 进程运行,管理多个 worker。
|
||||
**分类**: COMPLETE(已恢复)
|
||||
**核心实现文件**:
|
||||
1. src/daemon/main.ts — 413 行,daemon 主入口,管理生命周期
|
||||
2. src/daemon/workerRegistry.ts — 112 行,worker 注册和管理
|
||||
3. src/commands/daemon/index.ts — daemon 子命令入口
|
||||
**引用文件**:
|
||||
1. src/commands.ts — 条件注册命令(与 BRIDGE_MODE 组合)
|
||||
2. src/entrypoints/cli.tsx — CLI 入口
|
||||
**代码量**: 0 行专属代码
|
||||
**说明**: 在 commands.ts 中,`DAEMON` 与 `BRIDGE_MODE` 一起用于条件加载 `commands/remoteControlServer/index.js`,该文件不存在。
|
||||
1. src/commands.ts — 条件注册命令
|
||||
2. src/entrypoints/cli.tsx — CLI 入口中的 `--daemon-worker` 路径
|
||||
**说明**: 已从 stub 恢复为完整实现,支持 `daemon start/status/stop` 子命令、exponential backoff、state file 持久化。
|
||||
|
||||
---
|
||||
|
||||
@@ -1427,7 +1441,7 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
**分类**: STUB
|
||||
**引用文件**:
|
||||
1. src/main.tsx — 主入口
|
||||
2. src/tools/AgentTool/loadAgentsDir.ts — 加载代理目录
|
||||
2. packages/builtin-tools/src/tools/AgentTool/loadAgentsDir.ts — 加载代理目录
|
||||
**代码量**: 0 行专属代码
|
||||
|
||||
---
|
||||
@@ -1462,7 +1476,7 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
**引用文件**:
|
||||
1. src/components/ThemePicker.tsx — 主题选择器
|
||||
2. src/components/design-system/ThemeProvider.tsx — 主题提供者
|
||||
3. src/tools/ConfigTool/supportedSettings.ts — 支持的设置
|
||||
3. packages/builtin-tools/src/tools/ConfigTool/supportedSettings.ts — 支持的设置
|
||||
**代码量**: 0 行专属代码
|
||||
|
||||
---
|
||||
@@ -1504,7 +1518,7 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
**编译时引用次数**: 1
|
||||
**功能描述**: 内置探索和计划代理。
|
||||
**分类**: STUB
|
||||
**引用文件**: src/tools/AgentTool/builtInAgents.ts — 内置代理定义
|
||||
**引用文件**: packages/builtin-tools/src/tools/AgentTool/builtInAgents.ts — 内置代理定义
|
||||
**代码量**: 0 行专属代码
|
||||
|
||||
---
|
||||
@@ -1795,7 +1809,7 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
**功能描述**: Tree-sitter Bash 影子模式(并行运行 tree-sitter 和传统解析器进行对比)。
|
||||
**分类**: STUB
|
||||
**引用文件**:
|
||||
1. src/tools/BashTool/bashPermissions.ts — Bash 权限
|
||||
1. packages/builtin-tools/src/tools/BashTool/bashPermissions.ts — Bash 权限
|
||||
2. src/utils/bash/parser.ts — Bash 解析器
|
||||
**代码量**: 0 行专属代码
|
||||
|
||||
@@ -1869,16 +1883,16 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
|
||||
| 文件路径 | 行数 | 所属标志 |
|
||||
|----------|------|----------|
|
||||
| src/tools/MonitorTool/MonitorTool.ts | 1 行 | MONITOR_TOOL |
|
||||
| src/tools/WorkflowTool/WorkflowTool.ts | 1 行 | WORKFLOW_SCRIPTS |
|
||||
| src/tools/WorkflowTool/constants.ts | 1 行 | WORKFLOW_SCRIPTS |
|
||||
| packages/builtin-tools/src/tools/MonitorTool/MonitorTool.ts | 1 行 | MONITOR_TOOL |
|
||||
| packages/builtin-tools/src/tools/WorkflowTool/WorkflowTool.ts | 1 行 | WORKFLOW_SCRIPTS |
|
||||
| packages/builtin-tools/src/tools/WorkflowTool/constants.ts | 1 行 | WORKFLOW_SCRIPTS |
|
||||
| src/tools/ReviewArtifactTool/ReviewArtifactTool.ts | 1 行 | REVIEW_ARTIFACT |
|
||||
| src/utils/udsMessaging.ts | 1 行 | UDS_INBOX |
|
||||
| src/utils/udsClient.ts | 3 行 | UDS_INBOX |
|
||||
| src/utils/udsMessaging.ts | 已实现 | UDS_INBOX |
|
||||
| src/utils/udsClient.ts | 已实现 | UDS_INBOX |
|
||||
| src/skills/mcpSkills.ts | 3 行 | MCP_SKILLS |
|
||||
| src/tools/WebBrowserTool/WebBrowserPanel.tsx | 3 行 | WEB_BROWSER_TOOL |
|
||||
| src/tools/WorkflowTool/createWorkflowCommand.ts | 3 行 | WORKFLOW_SCRIPTS |
|
||||
| src/tools/WorkflowTool/WorkflowPermissionRequest.tsx | 3 行 | WORKFLOW_SCRIPTS |
|
||||
| packages/builtin-tools/src/tools/WorkflowTool/createWorkflowCommand.ts | 3 行 | WORKFLOW_SCRIPTS |
|
||||
| packages/builtin-tools/src/tools/WorkflowTool/WorkflowPermissionRequest.tsx | 3 行 | WORKFLOW_SCRIPTS |
|
||||
| src/components/tasks/WorkflowDetailDialog.tsx | 3 行 | WORKFLOW_SCRIPTS |
|
||||
| src/components/permissions/MonitorPermissionRequest/MonitorPermissionRequest.tsx | 3 行 | MONITOR_TOOL |
|
||||
| src/components/tasks/MonitorMcpDetailDialog.tsx | 3 行 | MONITOR_TOOL |
|
||||
|
||||
@@ -37,7 +37,7 @@ Agent({ subagent_type: "general-purpose", prompt: "..." })
|
||||
|
||||
### 3.1 门控与互斥
|
||||
|
||||
文件:`src/tools/AgentTool/forkSubagent.ts:32-39`
|
||||
文件:`packages/builtin-tools/src/tools/AgentTool/forkSubagent.ts:32-39`
|
||||
|
||||
```ts
|
||||
export function isForkSubagentEnabled(): boolean {
|
||||
@@ -105,7 +105,7 @@ isForkSubagentEnabled() && !subagent_type?
|
||||
|
||||
### 3.4 消息构建:buildForkedMessages
|
||||
|
||||
文件:`src/tools/AgentTool/forkSubagent.ts:107-169`
|
||||
文件:`packages/builtin-tools/src/tools/AgentTool/forkSubagent.ts:107-169`
|
||||
|
||||
构建的消息结构:
|
||||
|
||||
@@ -185,11 +185,11 @@ FEATURE_FORK_SUBAGENT=1 bun run dev
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `src/tools/AgentTool/forkSubagent.ts` | ~210 | 核心定义 + 消息构建 + 递归防护 |
|
||||
| `src/tools/AgentTool/AgentTool.tsx` | — | Fork 路由 + 强制异步 |
|
||||
| `src/tools/AgentTool/prompt.ts` | — | "When to Fork" 提示词段落 |
|
||||
| `src/tools/AgentTool/runAgent.ts` | — | useExactTools 路径 |
|
||||
| `src/tools/AgentTool/resumeAgent.ts` | — | Fork agent 恢复 |
|
||||
| `packages/builtin-tools/src/tools/AgentTool/forkSubagent.ts` | ~210 | 核心定义 + 消息构建 + 递归防护 |
|
||||
| `packages/builtin-tools/src/tools/AgentTool/AgentTool.tsx` | — | Fork 路由 + 强制异步 |
|
||||
| `packages/builtin-tools/src/tools/AgentTool/prompt.ts` | — | "When to Fork" 提示词段落 |
|
||||
| `packages/builtin-tools/src/tools/AgentTool/runAgent.ts` | — | useExactTools 路径 |
|
||||
| `packages/builtin-tools/src/tools/AgentTool/resumeAgent.ts` | — | Fork agent 恢复 |
|
||||
| `src/constants/xml.ts` | — | XML 标签常量 |
|
||||
| `src/utils/forkedAgent.ts` | — | CacheSafeParams + ContentReplacementState 克隆 |
|
||||
| `src/commands/fork/index.ts` | — | /fork 命令(stub) |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# KAIROS — 常驻助手模式
|
||||
|
||||
> Feature Flag: `FEATURE_KAIROS=1`(及子 Feature)
|
||||
> 实现状态:核心框架完整,部分子模块为 stub
|
||||
> 实现状态:核心框架完整,部分子模块为 stub;proactive/sleep 节奏控制已可用
|
||||
> 引用数:154(全库最大)
|
||||
|
||||
## 一、功能概述
|
||||
@@ -34,13 +34,13 @@ KAIROS 在系统提示中注入两大段落:
|
||||
|
||||
### 2.1 Brief 段落 (`getBriefSection`)
|
||||
|
||||
文件:`src/constants/prompts.ts:843-858`
|
||||
文件:`src/constants/prompts.ts:847-858`
|
||||
|
||||
当 `feature('KAIROS') || feature('KAIROS_BRIEF')` 时注入。Brief 工具(`SendUserMessage`)的结构化消息输出指令。`/brief` toggle 和 `--brief` flag 只控制显示过滤,不影响模型行为。
|
||||
|
||||
### 2.2 Proactive/Autonomous Work 段落 (`getProactiveSection`)
|
||||
|
||||
文件:`src/constants/prompts.ts:860-914`
|
||||
文件:`src/constants/prompts.ts:864-918`
|
||||
|
||||
当 `feature('PROACTIVE') || feature('KAIROS')` 且 `isProactiveActive()` 时注入。核心行为指令:
|
||||
|
||||
@@ -74,8 +74,9 @@ KAIROS 在系统提示中注入两大段落:
|
||||
|
||||
SleepTool 是 KAIROS/Proactive 的节奏控制核心。工具描述让模型理解"休眠"概念:
|
||||
- 工具名:`Sleep`
|
||||
- 功能:等待指定时间后响应 tick prompt
|
||||
- 功能:等待指定时间后响应 tick prompt;若队列出现新工作或 proactive 被关闭,会提前唤醒
|
||||
- 与 `<tick_tag>` 配合实现心跳式自主工作
|
||||
- 远程控制 surfaces 可通过 `automation_state` 看到 `standby` / `sleeping` 两种状态
|
||||
|
||||
### 3.3 Bridge 集成
|
||||
|
||||
@@ -172,8 +173,10 @@ FEATURE_KAIROS=1 FEATURE_TOKEN_BUDGET=1 bun run dev
|
||||
| `src/assistant/AssistantSessionChooser.ts` | — | Session 选择 UI(stub) |
|
||||
| `src/tools/BriefTool/` | — | BriefTool 实现(stub) |
|
||||
| `src/tools/SleepTool/prompt.ts` | ~30 | SleepTool 工具提示 |
|
||||
| `src/tools/SleepTool/SleepTool.ts` | ~200 | 休眠/唤醒与 automation metadata |
|
||||
| `src/services/mcp/channelNotification.ts` | 5 | 频道消息接入(stub) |
|
||||
| `src/memdir/memdir.ts` | — | 记忆目录管理(stub) |
|
||||
| `src/constants/prompts.ts:552-554,843-914` | 72 | 系统提示注入 |
|
||||
| `src/constants/prompts.ts:557,847-918` | 72 | 系统提示注入 |
|
||||
| `src/components/tasks/src/tasks/DreamTask/` | 3 | Dream 任务(stub) |
|
||||
| `src/proactive/index.ts` | — | Proactive 核心(stub,KAIROS 共享) |
|
||||
| `src/proactive/index.ts` | — | Proactive 核心(KAIROS 共享) |
|
||||
| `src/utils/sessionState.ts` | — | 向 bridge/CCR 暴露 automation 状态 |
|
||||
|
||||
321
docs/features/lan-pipes-implementation.md
Normal file
321
docs/features/lan-pipes-implementation.md
Normal file
@@ -0,0 +1,321 @@
|
||||
# LAN Pipes — 技术实现文档
|
||||
|
||||
面向开发者的实现细节。用户指南见 [lan-pipes.md](./lan-pipes.md)。
|
||||
|
||||
---
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
Machine A (192.168.50.22) Machine B (192.168.50.27)
|
||||
┌───────────────────────────┐ ┌───────────────────────────┐
|
||||
│ PipeServer │ │ PipeServer │
|
||||
│ UDS: ~/.claude/pipes/ │ │ UDS: ~/.claude/pipes/ │
|
||||
│ cli-abc.sock │ │ cli-def.sock │
|
||||
│ TCP: 0.0.0.0:<random> │◄──TCP───►│ TCP: 0.0.0.0:<random> │
|
||||
├───────────────────────────┤ ├───────────────────────────┤
|
||||
│ LanBeacon │ │ LanBeacon │
|
||||
│ UDP 224.0.71.67:7101 │◄──UDP───►│ UDP 224.0.71.67:7101 │
|
||||
├───────────────────────────┤ ├───────────────────────────┤
|
||||
│ usePipeIpc (hook) │ │ usePipeIpc (hook) │
|
||||
│ initPipeServer │ │ initPipeServer │
|
||||
│ registerMessageHandlers │ │ registerMessageHandlers │
|
||||
│ runMainHeartbeat │ │ runSubHeartbeat │
|
||||
│ cleanupPipeIpc │ │ cleanupPipeIpc │
|
||||
└───────────────────────────┘ └───────────────────────────┘
|
||||
```
|
||||
|
||||
## Feature Flag
|
||||
|
||||
`LAN_PIPES` — 在 `scripts/dev.ts` 和 `build.ts` 的 `DEFAULT_FEATURES` 中启用。
|
||||
|
||||
所有 LAN 代码路径通过 `feature('LAN_PIPES')` 编译时门控。`feature()` 只能在 `if` 或三元中使用(Bun 编译时常量约束)。
|
||||
|
||||
---
|
||||
|
||||
## 核心文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `src/utils/pipeTransport.ts` | PipeServer/PipeClient(UDS + TCP 双模式) |
|
||||
| `src/utils/lanBeacon.ts` | UDP multicast beacon + module singleton |
|
||||
| `src/utils/ndjsonFramer.ts` | 共享 NDJSON socket 帧解析 |
|
||||
| `src/utils/pipeRegistry.ts` | 文件注册表 + `mergeWithLanPeers()` |
|
||||
| `src/utils/peerAddress.ts` | 地址解析(uds/bridge/tcp scheme) |
|
||||
| `src/utils/pipePermissionRelay.ts` | 权限转发 + `setPipeRelay`/`getPipeRelay` singleton |
|
||||
| `src/hooks/usePipeIpc.ts` | 生命周期 hook(从 REPL.tsx 提取) |
|
||||
| `src/hooks/usePipeRelay.ts` | 消息回传 hook |
|
||||
| `src/hooks/usePipePermissionForward.ts` | 权限转发 hook |
|
||||
| `src/hooks/usePipeRouter.ts` | 输入路由 hook |
|
||||
| `src/hooks/useMasterMonitor.ts` | slave 注册表 + 消息订阅 |
|
||||
|
||||
---
|
||||
|
||||
## PipeServer TCP 扩展
|
||||
|
||||
`src/utils/pipeTransport.ts`
|
||||
|
||||
### 类型
|
||||
|
||||
```typescript
|
||||
export type PipeTransportMode = 'uds' | 'tcp'
|
||||
export type TcpEndpoint = { host: string; port: number }
|
||||
export type PipeServerOptions = { enableTcp?: boolean; tcpPort?: number }
|
||||
```
|
||||
|
||||
### PipeServer 变更
|
||||
|
||||
- `setupSocket(socket)` — 从 start() 提取的共享方法,UDS 和 TCP 共用
|
||||
- `start(options?)` — 可选启用 TCP,port=0 让 OS 分配
|
||||
- 内部维护两个 `net.Server`,共享同一组 `clients: Set<Socket>` 和 `handlers`
|
||||
- `tcpAddress` getter 暴露 TCP 端口
|
||||
- `close()` 同时关闭两个 server
|
||||
|
||||
socket 帧解析使用 `attachNdjsonFramer()` from `ndjsonFramer.ts`(替代原先 3 份重复代码)。
|
||||
|
||||
### PipeClient 变更
|
||||
|
||||
- 构造函数新增可选 `TcpEndpoint` 参数
|
||||
- `connect()` 根据 tcpEndpoint 分派到 `connectTcp()` 或 `connectUds()`
|
||||
- TCP 不需要文件存在轮询,直接建连
|
||||
|
||||
---
|
||||
|
||||
## LAN Beacon
|
||||
|
||||
`src/utils/lanBeacon.ts`
|
||||
|
||||
### 协议参数
|
||||
|
||||
| 参数 | 值 |
|
||||
|------|-----|
|
||||
| Multicast 组 | `224.0.71.67` |
|
||||
| 端口 | `7101` |
|
||||
| 广播间隔 | `3000ms` |
|
||||
| Peer 超时 | `15000ms` |
|
||||
| TTL | `1` |
|
||||
|
||||
### Announce 包
|
||||
|
||||
```typescript
|
||||
type LanAnnounce = {
|
||||
proto: 'claude-pipe-v1'
|
||||
pipeName: string
|
||||
machineId: string
|
||||
hostname: string
|
||||
ip: string
|
||||
tcpPort: number
|
||||
role: 'main' | 'sub'
|
||||
ts: number
|
||||
}
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
class LanBeacon extends EventEmitter {
|
||||
constructor(announce: Omit<LanAnnounce, 'proto' | 'ts'>)
|
||||
start(): void
|
||||
stop(): void
|
||||
getPeers(): Map<string, LanAnnounce> // 防御性拷贝
|
||||
updateAnnounce(partial): void // 使用 spread(不可变更新)
|
||||
|
||||
on('peer-discovered', (peer: LanAnnounce) => void)
|
||||
on('peer-lost', (pipeName: string) => void)
|
||||
}
|
||||
```
|
||||
|
||||
### 存储
|
||||
|
||||
module-level singleton:`getLanBeacon()` / `setLanBeacon()`。不挂在 Zustand state 上(避免 `setState` 展开时丢失引用)。
|
||||
|
||||
### 网卡绑定
|
||||
|
||||
`addMembership(group, localIp)` + `setMulticastInterface(localIp)` 指定 LAN 网卡。解决 Windows 上 WSL/Docker 虚拟网卡劫持 multicast 的问题。
|
||||
|
||||
---
|
||||
|
||||
## Hook 架构
|
||||
|
||||
从 REPL.tsx 提取的 ~830 行 Pipe IPC 代码:
|
||||
|
||||
### usePipeIpc(生命周期)
|
||||
|
||||
`src/hooks/usePipeIpc.ts`(623 行)
|
||||
|
||||
在 REPL.tsx 顶层通过 feature-gated require 加载:
|
||||
|
||||
```typescript
|
||||
const usePipeIpc = feature('UDS_INBOX')
|
||||
? require('../hooks/usePipeIpc.js').usePipeIpc
|
||||
: () => undefined;
|
||||
|
||||
// 组件内
|
||||
usePipeIpc({ store, handleIncomingPrompt });
|
||||
```
|
||||
|
||||
内部使用 **lazy getter** 函数加载依赖(避免循环依赖导致 Bun 运行时崩溃):
|
||||
|
||||
```typescript
|
||||
const pt = () => require('../utils/pipeTransport.js')
|
||||
const pr = () => require('../utils/pipeRegistry.js')
|
||||
const mm = () => require('./useMasterMonitor.js')
|
||||
// ...
|
||||
```
|
||||
|
||||
`import type` 用于静态类型(不会触发模块加载)。
|
||||
|
||||
### 四个阶段函数
|
||||
|
||||
| 函数 | 职责 |
|
||||
|------|------|
|
||||
| `initPipeServer` | 角色判定 + server 创建 + beacon 启动 |
|
||||
| `registerMessageHandlers` | ping、attach、prompt、permission、detach 五个 handler |
|
||||
| `runMainHeartbeat` | cleanup + 发现 + auto-attach + 清理死连接 |
|
||||
| `runSubHeartbeat` | 检测 main 是否存活,死亡则接管或独立 |
|
||||
|
||||
### usePipeRelay(消息回传)
|
||||
|
||||
`src/hooks/usePipeRelay.ts`(38 行)
|
||||
|
||||
提供 `relayPipeMessage()` 和 `pipeReturnHadErrorRef`。relay 函数通过 `getPipeRelay()` module singleton 读取(替代 `globalThis.__pipeSendToMaster`)。
|
||||
|
||||
### usePipePermissionForward(权限转发)
|
||||
|
||||
`src/hooks/usePipePermissionForward.ts`(159 行)
|
||||
|
||||
订阅 `subscribePipeEntries()`,处理:
|
||||
- `permission_request` → 解析 payload → 查找 tool → 加入确认队列
|
||||
- `permission_cancel` → 从队列移除
|
||||
- `stream/error/done` → 转为系统消息显示(含 role + IP 标签)
|
||||
|
||||
### usePipeRouter(输入路由)
|
||||
|
||||
`src/hooks/usePipeRouter.ts`(130 行)
|
||||
|
||||
提供 `routeToSelectedPipes(input): boolean`。读取 `selectedPipes` + `routeMode`,逐个发送到已连接目标。通知显示 `[role] hostname/ip`(LAN peer)或 `[role]`(本机)。
|
||||
|
||||
---
|
||||
|
||||
## Registry 并行探测
|
||||
|
||||
`src/utils/pipeRegistry.ts`
|
||||
|
||||
### getAliveSubs()
|
||||
|
||||
```typescript
|
||||
export async function getAliveSubs(): Promise<PipeRegistrySub[]> {
|
||||
const registry = await readRegistry()
|
||||
const results = await Promise.all(
|
||||
registry.subs.map(sub =>
|
||||
isPipeAlive(sub.pipeName, 1000).then(alive => alive ? sub : null)
|
||||
)
|
||||
)
|
||||
return results.filter(Boolean)
|
||||
}
|
||||
```
|
||||
|
||||
### cleanupStaleEntries()
|
||||
|
||||
两阶段:
|
||||
1. **无锁并行探测**:`Promise.all` 探测 main + 所有 subs
|
||||
2. **短暂持锁写入**:`acquireLock()` → 重新读取 → 应用变更 → 写入 → `releaseLock()`
|
||||
|
||||
持锁时间从 N 秒降至 ~10ms。
|
||||
|
||||
### getMachineId()
|
||||
|
||||
Windows/macOS 使用 `execFile`(异步),不阻塞主线程。结果缓存,仅首次调用执行。
|
||||
|
||||
---
|
||||
|
||||
## NDJSON 协议
|
||||
|
||||
### 消息类型
|
||||
|
||||
| 类型 | 方向 | 数据 |
|
||||
|------|------|------|
|
||||
| `ping` / `pong` | 双向 | 无 |
|
||||
| `attach_request` | M→S | `meta: { machineId }` |
|
||||
| `attach_accept` / `attach_reject` | S→M | `data: reason` |
|
||||
| `detach` | M→S | 无 |
|
||||
| `prompt` | M→S | `data: prompt_text` |
|
||||
| `prompt_ack` | S→M | `data: 'accepted'` |
|
||||
| `stream` | S→M | `data: partial_text` |
|
||||
| `done` | S→M | 无 |
|
||||
| `error` | 双向 | `data: error_message` |
|
||||
| `permission_request` | S→M | `data: JSON(PipePermissionRequestPayload)` |
|
||||
| `permission_response` | M→S | `data: JSON(PipePermissionResponsePayload)` |
|
||||
| `permission_cancel` | M→S | `data: JSON({ requestId, reason })` |
|
||||
|
||||
### 帧格式
|
||||
|
||||
每行一个 JSON 对象,`\n` 分隔:
|
||||
```
|
||||
{"type":"ping","from":"cli-abc","ts":"2026-04-11T00:00:00.000Z"}\n
|
||||
{"type":"prompt","data":"检查 git status","from":"cli-abc"}\n
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 跨机器 Attach 流程
|
||||
|
||||
```
|
||||
CLI-B (192.168.50.27) 心跳循环
|
||||
→ beacon.getPeers() 发现 CLI-A (192.168.50.22)
|
||||
→ connectToPipe(pName, myName, 3000, { host: '192.168.50.22', port: 58853 })
|
||||
→ PipeClient.connectTcp() → net.createConnection({ host, port })
|
||||
→ client.send({ type: 'attach_request', meta: { machineId } })
|
||||
→ CLI-A 收到:
|
||||
isLanPeer = (msg.meta.machineId !== myMachineId) → true
|
||||
→ 不检查 role,直接 reply({ type: 'attach_accept' })
|
||||
→ setPipeRelay(socket.write)
|
||||
→ CLI-B 收到 attach_accept
|
||||
→ addSlaveClient(pName, client)
|
||||
→ store.setState: role='master', slaves[pName] = { status: 'idle' }
|
||||
```
|
||||
|
||||
关键:跨机器 attach 不要求对方是 sub 角色。通过 `machineId` 区分 LAN peer。
|
||||
|
||||
---
|
||||
|
||||
## SendMessageTool TCP 支持
|
||||
|
||||
`packages/builtin-tools/src/tools/SendMessageTool/SendMessageTool.ts`
|
||||
|
||||
- `to` 字段支持 `tcp:host:port` 格式
|
||||
- `checkPermissions`:`tcp:` scheme 返回 `behavior: 'ask'`,`classifierApprovable: false`
|
||||
- `call()`:创建临时 `PipeClient` → connect → send → disconnect
|
||||
|
||||
---
|
||||
|
||||
## 测试
|
||||
|
||||
| 文件 | 测试数 | 覆盖 |
|
||||
|------|--------|------|
|
||||
| `lanBeacon.test.ts` | 7 | socket 初始化、announce、peer 发现/过滤/清理 |
|
||||
| `peerAddress.test.ts` | 8 | scheme 解析、parseTcpTarget、端口范围验证 |
|
||||
| `pipePermissionRelay.test.ts` | 2 | setPipeRelay singleton、权限请求/响应 |
|
||||
| `pipeTransport.test.ts` | 2 | UDS 基础行为 |
|
||||
| `useMasterMonitor.test.ts` | 5 | slave 注册/移除、事件发射 |
|
||||
|
||||
全量:2190 pass / 0 fail
|
||||
|
||||
---
|
||||
|
||||
## 已知限制
|
||||
|
||||
1. **TCP 无认证** — 同 LAN 内知道端口号即可连接
|
||||
2. **Beacon 明文广播** — IP/hostname/machineId 未 hash
|
||||
3. **单网卡选择** — `getLocalIp()` 返回首个非内部 IPv4,可能选到 VPN
|
||||
4. **端口随机** — 每次启动不同端口,依赖 beacon 发现
|
||||
5. **SendMessageTool 每次创建新连接** — 未复用已有 slave client
|
||||
|
||||
## 后续改进方向
|
||||
|
||||
1. HMAC-SHA256 TCP 握手认证
|
||||
2. machineId hash 后再广播
|
||||
3. 多网卡选择(优先 RFC 1918 地址)
|
||||
4. 固定端口范围配置
|
||||
5. TLS 加密传输
|
||||
6. SendMessageTool 复用已连接的 slave client
|
||||
193
docs/features/lan-pipes.md
Normal file
193
docs/features/lan-pipes.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# LAN Pipes — 局域网多机器群控指南
|
||||
|
||||
## 什么是 LAN Pipes
|
||||
|
||||
LAN Pipes 让多台机器上的 Claude Code 实例通过局域网自动发现并协作。你可以在一台机器(main)上操控其他机器(sub)上的 Claude Code,发送 prompt、查看执行结果、审批权限请求——全程零配置。
|
||||
|
||||
基于本机 Pipe IPC(`UDS_INBOX`)扩展,新增 TCP 传输层 + UDP Multicast 发现。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 两台或以上机器在同一局域网
|
||||
- 每台机器安装了 CCB 并能 `bun run dev`
|
||||
- Feature flag `LAN_PIPES`(dev/build 默认开启)
|
||||
- 防火墙允许 UDP 7101 + TCP 动态端口(见下方配置)
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 第一步:配置防火墙
|
||||
|
||||
**每台机器都需要执行。**
|
||||
|
||||
**Windows**(管理员 PowerShell):
|
||||
```powershell
|
||||
New-NetFirewallRule -DisplayName "CCB LAN Beacon (UDP)" -Direction Inbound -Protocol UDP -LocalPort 7101 -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "CCB LAN Pipes (TCP)" -Direction Inbound -Protocol TCP -LocalPort 1024-65535 -Program (Get-Command bun).Source -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "CCB LAN Beacon Out (UDP)" -Direction Outbound -Protocol UDP -RemotePort 7101 -Action Allow -Profile Private
|
||||
```
|
||||
|
||||
验证网络为"专用"(非公共):`Get-NetConnectionProfile`
|
||||
|
||||
**macOS**:
|
||||
首次运行时系统弹出"允许接受传入连接"对话框,点击"允许"。
|
||||
|
||||
如果使用 pf 防火墙:
|
||||
```bash
|
||||
echo "pass in proto udp from any to any port 7101" | sudo pfctl -ef -
|
||||
```
|
||||
|
||||
**Linux**(firewalld):
|
||||
```bash
|
||||
sudo firewall-cmd --zone=trusted --add-port=7101/udp --permanent
|
||||
sudo firewall-cmd --zone=trusted --add-port=1024-65535/tcp --permanent
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
**Linux**(iptables):
|
||||
```bash
|
||||
sudo iptables -A INPUT -p udp --dport 7101 -j ACCEPT
|
||||
sudo iptables -A INPUT -p tcp --dport 1024:65535 -m owner --uid-owner $(id -u) -j ACCEPT
|
||||
```
|
||||
|
||||
### 第二步:启动
|
||||
|
||||
```bash
|
||||
# 机器 A(例如 192.168.50.22)
|
||||
bun run dev
|
||||
|
||||
# 机器 B(例如 192.168.50.27)
|
||||
bun run dev
|
||||
```
|
||||
|
||||
启动后等待 3-5 秒(beacon 广播间隔),两边自动发现并连接。
|
||||
|
||||
### 第三步:查看和操作
|
||||
|
||||
在任一台机器上:
|
||||
```
|
||||
/pipes
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
pipe: cli-a91bad56 (main) 192.168.50.22 2/3 selected
|
||||
|
||||
Main machine: 205d6c3a... (this machine)
|
||||
[main] cli-a91bad56 XC/192.168.50.22 [alive] (you)
|
||||
☑ [sub-1] cli-da029538 XC/192.168.50.22 [alive] [connected]
|
||||
|
||||
LAN Peers:
|
||||
☐ [main] cli-04d67950 vmwin11/192.168.50.27 tcp:192.168.50.27:58853 [LAN]
|
||||
```
|
||||
|
||||
### 第四步:选中目标并发送任务
|
||||
|
||||
1. 按 `Shift+↓` 展开选择面板
|
||||
2. `↑↓` 移动到 LAN peer
|
||||
3. `Space` 选中
|
||||
4. `Enter` 确认
|
||||
5. 输入 prompt,自动路由到远端执行
|
||||
|
||||
远端执行结果会流式回传到你的消息列表:
|
||||
```
|
||||
[main vmwin11/192.168.50.27 / cli-04d67950] 正在检查 git status...
|
||||
[main vmwin11/192.168.50.27 / cli-04d67950] Completed
|
||||
```
|
||||
|
||||
## 完整命令参考
|
||||
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
| `/pipes` | 显示所有实例(本机 + LAN),Shift+↓ 展开选择面板 |
|
||||
| `/pipes select <name>` | 选中某实例 |
|
||||
| `/pipes all` | 全选 |
|
||||
| `/pipes none` | 取消全选 |
|
||||
| `/attach <name>` | 手动 attach(自动识别 LAN peer 并通过 TCP 连接) |
|
||||
| `/detach <name>` | 断开连接 |
|
||||
| `/send <name> <msg>` | 向指定 pipe 发送消息 |
|
||||
| `/send tcp:host:port <msg>` | 直接通过 TCP 地址发送 |
|
||||
| `/claim-main` | 强制声明为 main |
|
||||
| `/pipe-status` | 显示详细状态 |
|
||||
| `/peers` | 列出所有已发现的 peer |
|
||||
|
||||
## 快捷键
|
||||
|
||||
| 快捷键 | 场景 | 作用 |
|
||||
|--------|------|------|
|
||||
| `Shift+↓` | 状态栏可见时 | 展开/收起选择面板 |
|
||||
| `↑ / ↓` | 面板展开时 | 移动光标 |
|
||||
| `Space` | 面板展开时 | 选中/取消 |
|
||||
| `Enter` | 面板展开时 | 确认关闭 |
|
||||
| `Esc` | 面板展开时 | 取消关闭 |
|
||||
| `← / →` | 有选中 pipe 时 | 切换路由模式 |
|
||||
| `M` | 面板展开时 | 同 ←/→ 切换路由模式 |
|
||||
|
||||
## 路由模式
|
||||
|
||||
| 模式 | 显示 | 行为 |
|
||||
|------|------|------|
|
||||
| `selected pipes only` | 绿色 | prompt 仅发送到选中的 pipe,本地不执行 |
|
||||
| `local main` | 灰色 | prompt 仅在本地执行,不转发 |
|
||||
|
||||
切换路由模式不会清空选择。
|
||||
|
||||
## 权限转发
|
||||
|
||||
当远端 slave 执行需要权限的工具(如 BashTool)时:
|
||||
1. slave 发送 `permission_request` 到 main
|
||||
2. main 弹出权限确认对话框,显示 `[role hostname/ip / pipeName]`
|
||||
3. 用户确认/拒绝
|
||||
4. 结果发回 slave,继续或中断
|
||||
|
||||
## 工作原理
|
||||
|
||||
### 发现机制
|
||||
|
||||
- 每台机器启动时创建 UDP multicast beacon
|
||||
- 组地址 `224.0.71.67`,端口 `7101`,TTL=1(不跨路由器)
|
||||
- 每 3 秒广播一次自身信息(pipeName、IP、TCP 端口、角色)
|
||||
- 15 秒未收到广播则标记 peer 丢失
|
||||
|
||||
### 通信机制
|
||||
|
||||
- 本机实例:UDS(Unix Domain Socket / Named Pipe)
|
||||
- 跨机器:TCP(动态端口,通过 beacon 发现)
|
||||
- 协议:NDJSON(每行一个 JSON 对象)
|
||||
- 消息类型:ping/pong、attach/detach、prompt/stream/done/error、permission
|
||||
|
||||
### 角色模型
|
||||
|
||||
| 角色 | 说明 |
|
||||
|------|------|
|
||||
| `main` | 首个启动的实例 |
|
||||
| `sub` | 同机后续启动的实例 |
|
||||
| `master` | attach 了至少一个 slave 的实例 |
|
||||
| `slave` | 被 master attach 的实例 |
|
||||
|
||||
跨机器 attach 时,两边都可以是 main——不要求对方必须是 sub。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 看不到 LAN peer
|
||||
|
||||
1. 检查防火墙是否放行 UDP 7101
|
||||
2. `Get-NetConnectionProfile`(Windows)确认网络为"专用"
|
||||
3. 确认两台机器在同一子网(`ping` 能通)
|
||||
4. 路由器未开启 AP 隔离
|
||||
|
||||
### 连接超时
|
||||
|
||||
1. 检查 TCP 入站防火墙规则
|
||||
2. 确认没有 VPN 劫持流量
|
||||
3. 尝试 `/send tcp:ip:port hello` 直接测试
|
||||
|
||||
### beacon 绑到了错误网卡
|
||||
|
||||
Windows 上 WSL/Docker 虚拟网卡可能劫持 multicast。beacon 会自动选择非内部 IPv4 接口。如果选错,检查 `getLocalIp()` 返回值。
|
||||
|
||||
## 安全说明
|
||||
|
||||
- TCP 连接当前**无认证**——同 LAN 内知道端口号即可连接
|
||||
- Multicast TTL=1,不跨路由器
|
||||
- AI 通过 `SendMessageTool` 发送 `tcp:` 消息时需**用户显式确认**
|
||||
- 建议仅在信任的局域网中使用
|
||||
205
docs/features/langfuse-monitoring.md
Normal file
205
docs/features/langfuse-monitoring.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# Langfuse 监控集成
|
||||
|
||||
> 实现状态:已完成,通过环境变量启用
|
||||
> 依赖:`@langfuse/otel`、`@langfuse/tracing`、`@opentelemetry/sdk-trace-base`
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
Langfuse 是一个开源的 LLM 可观测性平台,用于追踪、监控和调试 AI 应用的请求链路。CCB 通过 OpenTelemetry (OTel) 桥接层将 Langfuse 集成到查询流程中,实现:
|
||||
|
||||
- **LLM 调用追踪** — 记录每次 API 请求的模型、Provider、输入/输出、Token 用量
|
||||
- **工具执行追踪** — 记录每个工具调用的名称、输入、输出、耗时和错误
|
||||
- **多 Agent 追踪** — 主 Agent 和子 Agent 各自独立的 Trace 链路
|
||||
- **数据脱敏** — 自动遮蔽敏感信息(API Key、文件内容、Shell 输出等)
|
||||
|
||||
## 二、启用方式
|
||||
|
||||
Langfuse 是开源项目,你可以 **自部署**(Docker / Kubernetes),也可以使用官方提供的 **[Langfuse Cloud](https://cloud.langfuse.com)** 免费测试。注册后在 Project Settings → API Keys 页面获取密钥。
|
||||
|
||||
核心只需要三个环境变量:
|
||||
|
||||
| 环境变量 | 说明 |
|
||||
|---------|------|
|
||||
| `LANGFUSE_PUBLIC_KEY` | Langfuse 公钥(必填) |
|
||||
| `LANGFUSE_SECRET_KEY` | Langfuse 密钥(必填) |
|
||||
| `LANGFUSE_BASE_URL` | 服务地址,默认 `https://cloud.langfuse.com`;自部署时改为你的地址(必填) |
|
||||
|
||||
未配置时所有追踪函数为 no-op,零开销。
|
||||
|
||||
### 通过 settings.json 配置(推荐)
|
||||
|
||||
在 `.claude/settings.json` 的 `env` 字段中添加,这样每次启动自动生效:
|
||||
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"LANGFUSE_PUBLIC_KEY": "pk-xxx",
|
||||
"LANGFUSE_SECRET_KEY": "sk-xxx",
|
||||
"LANGFUSE_BASE_URL": "https://cloud.langfuse.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 其他可选参数
|
||||
|
||||
| 环境变量 | 默认值 | 说明 |
|
||||
|---------|--------|------|
|
||||
| `LANGFUSE_TRACING_ENVIRONMENT` | `development` | 环境标签,用于 Langfuse 面板筛选 |
|
||||
| `LANGFUSE_FLUSH_AT` | `20` | 批量发送的 span 数量阈值 |
|
||||
| `LANGFUSE_FLUSH_INTERVAL` | `10` | 定时刷新间隔(秒) |
|
||||
| `LANGFUSE_EXPORT_MODE` | `batched` | 导出模式:`batched`(批量)或 `immediate`(即时) |
|
||||
| `LANGFUSE_TIMEOUT` | `5` | 请求超时(秒) |
|
||||
|
||||
## 四、架构
|
||||
|
||||
### 4.1 模块结构
|
||||
|
||||
```
|
||||
src/services/langfuse/
|
||||
├── index.ts # 统一导出
|
||||
├── client.ts # OTel Provider + LangfuseSpanProcessor 初始化
|
||||
├── tracing.ts # Trace/Span 创建、LLM 和工具观察记录
|
||||
├── convert.ts # 内部 Message 类型 → Langfuse OpenAI 兼容格式转换
|
||||
└── sanitize.ts # 数据脱敏(敏感字段、文件路径、工具输出)
|
||||
```
|
||||
|
||||
### 4.2 追踪层级
|
||||
|
||||
```
|
||||
Trace (Agent Span) ← createTrace() / createSubagentTrace()
|
||||
├── Generation (LLM 调用) ← recordLLMObservation()
|
||||
├── Tool Observation (工具调用) ← recordToolObservation()
|
||||
├── Tool Observation (工具调用) ← recordToolObservation()
|
||||
└── ...
|
||||
```
|
||||
|
||||
### 4.3 数据流
|
||||
|
||||
```
|
||||
query.ts ──→ createTrace() # 每个 query turn 创建根 trace
|
||||
│
|
||||
├── claude.ts ──→ recordLLMObservation() # API 调用完成后记录 LLM 观察
|
||||
│
|
||||
├── toolExecution.ts ──→ recordToolObservation() # 每个工具执行记录
|
||||
│
|
||||
└── query.ts ──→ endTrace() # turn 结束时关闭 trace
|
||||
|
||||
runAgent.ts ──→ createSubagentTrace() # 子 Agent 有独立 trace
|
||||
```
|
||||
|
||||
## 五、追踪详情
|
||||
|
||||
### 5.1 主 Agent Trace
|
||||
|
||||
每次 `query()` 调用(即用户一次对话 turn)创建一个类型为 `agent` 的根 Span:
|
||||
|
||||
- **名称**: `agent-run` 或 `agent-run:<querySource>`
|
||||
- **元数据**: `provider`、`model`、`agentType: "main"`
|
||||
- **Session ID**: 关联到 Langfuse 的 Session 功能,支持按会话聚合
|
||||
|
||||
### 5.2 子 Agent Trace
|
||||
|
||||
通过 `AgentTool` 启动的子 Agent 创建独立 Trace:
|
||||
|
||||
- **名称**: `agent:<agentType>`
|
||||
- **元数据**: `provider`、`model`、`agentType`、`agentId`
|
||||
- 独立于主 Trace,有自己的 Session 关联
|
||||
|
||||
### 5.3 LLM Generation
|
||||
|
||||
每次 API 调用记录为一个 `generation` 类型的 Span:
|
||||
|
||||
- **名称**: 按 Provider 映射(如 `ChatAnthropic`、`ChatOpenAI`、`ChatBedrockAnthropic` 等)
|
||||
- **记录内容**: 输入消息、输出消息、Token 用量(input/output)
|
||||
- **时间**: 精确记录 `startTime`、`endTime`、`completionStartTime`(TTFT 指标)
|
||||
|
||||
Provider 名称映射:
|
||||
|
||||
| Provider | Generation 名称 |
|
||||
|----------|-----------------|
|
||||
| `firstParty` | `ChatAnthropic` |
|
||||
| `bedrock` | `ChatBedrockAnthropic` |
|
||||
| `vertex` | `ChatVertexAnthropic` |
|
||||
| `foundry` | `ChatFoundry` |
|
||||
| `openai` | `ChatOpenAI` |
|
||||
| `gemini` | `ChatGoogleGenerativeAI` |
|
||||
| `grok` | `ChatXAI` |
|
||||
|
||||
### 5.4 工具执行
|
||||
|
||||
每个工具调用记录为一个 `tool` 类型的 Span:
|
||||
|
||||
- **名称**: 工具名(如 `FileEditTool`、`BashTool`)
|
||||
- **记录内容**: 输入(经脱敏)、输出(经脱敏)、`toolUseId`
|
||||
- **错误标记**: `isError` 标志 + `level: ERROR`
|
||||
|
||||
## 六、数据脱敏
|
||||
|
||||
所有上传到 Langfuse 的数据都会经过脱敏处理(`sanitize.ts`),确保敏感信息不会泄露:
|
||||
|
||||
### 6.1 全局脱敏(`sanitizeGlobal`)
|
||||
|
||||
- **Home 路径替换** — `/Users/xxx` → `~`
|
||||
- **敏感字段遮蔽** — 匹配 `api_key`、`token`、`secret`、`password`、`credential`、`auth_header` 等关键字的字段值替换为 `[REDACTED]`
|
||||
|
||||
### 6.2 工具输入脱敏(`sanitizeToolInput`)
|
||||
|
||||
- 敏感字段遮蔽(同全局)
|
||||
- `file_path`、`path`、`directory` 路径中的 Home 目录替换
|
||||
|
||||
### 6.3 工具输出脱敏(`sanitizeToolOutput`)
|
||||
|
||||
| 工具 | 脱敏策略 |
|
||||
|------|---------|
|
||||
| `FileReadTool`、`FileWriteTool`、`FileEditTool` | 完全遮蔽,仅保留字符数:`[file content redacted, N chars]` |
|
||||
| `BashTool`、`PowerShellTool` | 截断至 500 字符 |
|
||||
| `ConfigTool`、`MCPTool` | 完全遮蔽 |
|
||||
| 其他工具 | 原样保留 |
|
||||
|
||||
## 七、消息格式转换
|
||||
|
||||
`convert.ts` 将 CCB 内部的 Message 类型转换为 Langfuse 期望的 OpenAI 兼容格式:
|
||||
|
||||
- **输入**: `UserMessage | AssistantMessage[]` + 可选 system prompt → `{ role, content }[]`
|
||||
- **输出**: `AssistantMessage[]` → `{ role: 'assistant', content }`
|
||||
- **Content Block 映射**:
|
||||
- `text` → `{ type: 'text', text }`
|
||||
- `thinking` / `redacted_thinking` → `{ type: 'thinking', thinking }`
|
||||
- `tool_use` → `{ type: 'tool_use', id, name, input }`
|
||||
- `tool_result` → `{ type: 'tool_result', tool_use_id, content }`
|
||||
- `image` / `document` → 占位标记 `[image]` / `[document: name]`
|
||||
|
||||
## 八、生命周期
|
||||
|
||||
1. **初始化** — `initLangfuse()` 在 `src/entrypoints/init.ts` 启动时调用,创建 `LangfuseSpanProcessor` 和 `BasicTracerProvider`
|
||||
2. **运行时** — 各追踪函数通过 `isLangfuseEnabled()` 检查,未配置时直接返回 `null`/跳过
|
||||
3. **关闭** — `shutdownLangfuse()` 在进程退出时调用,强制 flush 并关闭 Processor
|
||||
|
||||
## 九、自部署 Langfuse
|
||||
|
||||
Langfuse 是开源项目,支持 Docker / Kubernetes 自部署:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name langfuse \
|
||||
-p 3000:3000 \
|
||||
-e DATABASE_URL=postgresql://... \
|
||||
langfuse/langfuse:latest
|
||||
```
|
||||
|
||||
自部署后,将 `LANGFUSE_BASE_URL` 指向你的实例地址即可。详见 [Langfuse 自部署文档](https://langfuse.com/docs/deployment/self-host)。
|
||||
|
||||
如果没有自部署需求,可以直接使用 [Langfuse Cloud](https://cloud.langfuse.com),提供免费额度可用于测试。
|
||||
|
||||
## 十、相关文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `src/services/langfuse/client.ts` | OTel Provider 初始化、生命周期管理 |
|
||||
| `src/services/langfuse/tracing.ts` | Trace/Span 创建和观察记录 |
|
||||
| `src/services/langfuse/convert.ts` | Message 格式转换 |
|
||||
| `src/services/langfuse/sanitize.ts` | 数据脱敏 |
|
||||
| `src/services/langfuse/__tests__/langfuse.test.ts` | 测试(568 行) |
|
||||
| `src/query.ts` | 主查询流程中的 Trace 集成 |
|
||||
| `src/services/tools/toolExecution.ts` | 工具执行中的观察记录 |
|
||||
| `packages/builtin-tools/src/tools/AgentTool/runAgent.ts` | 子 Agent Trace 创建 |
|
||||
@@ -41,7 +41,7 @@ getMcpSkillCommands() 过滤 → SkillTool 调用
|
||||
|
||||
### 2.2 技能筛选
|
||||
|
||||
文件:`src/commands.ts:547-558`
|
||||
文件:`src/commands.ts:604-616`
|
||||
|
||||
`getMcpSkillCommands(mcpCommands)` 过滤条件:
|
||||
|
||||
@@ -54,7 +54,7 @@ feature('MCP_SKILLS') // feature flag 必须开启
|
||||
|
||||
### 2.3 条件加载
|
||||
|
||||
文件:`src/services/mcp/client.ts:117-121`
|
||||
文件:`src/services/mcp/client.ts:129-133`
|
||||
|
||||
`fetchMcpSkillsForClient` 通过 `require()` 条件加载,feature flag 关闭时不加载任何模块:
|
||||
|
||||
@@ -79,8 +79,8 @@ const fetchMcpSkillsForClient = feature('MCP_SKILLS')
|
||||
|
||||
| 文件 | 行 | 说明 |
|
||||
|------|------|------|
|
||||
| `src/commands.ts` | 547-558, 561-608 | 命令过滤和 SkillTool 命令收集 |
|
||||
| `src/services/mcp/client.ts` | 117-121, 1394, 1672, 2173-2181, 2346-2358 | 技能获取、缓存清除、连接时获取 |
|
||||
| `src/commands.ts` | 604-616, 620-633 | 命令过滤和 SkillTool 命令收集 |
|
||||
| `src/services/mcp/client.ts` | 129-133, 1394, 1672, 2176 | 技能获取、缓存清除、连接时获取 |
|
||||
| `src/services/mcp/useManageMCPConnections.ts` | 22-26, 682-740 | 实时刷新(prompts/resources 变化) |
|
||||
|
||||
## 三、关键设计决策
|
||||
|
||||
342
docs/features/pipes-and-lan.md
Normal file
342
docs/features/pipes-and-lan.md
Normal file
@@ -0,0 +1,342 @@
|
||||
# Pipes + LAN Pipes 完整功能指南
|
||||
|
||||
## 概述
|
||||
|
||||
Pipes 系统提供 Claude Code CLI 实例之间的通讯能力,分两层:
|
||||
|
||||
1. **Pipes(本机)**:同一台机器上的多个 CLI 实例通过 UDS(Unix Domain Socket / Windows Named Pipe)协作
|
||||
2. **LAN Pipes(局域网)**:不同机器上的 CLI 实例通过 TCP + UDP Multicast 协作
|
||||
|
||||
两层使用同一套协议(NDJSON)和同一套命令(`/pipes`、`/attach`、`/send` 等),对用户透明。
|
||||
|
||||
## Feature Flags
|
||||
|
||||
| Flag | 控制范围 | 默认 |
|
||||
|------|----------|------|
|
||||
| `UDS_INBOX` | 本机 Pipe IPC 全部功能 | dev/build 启用 |
|
||||
| `LAN_PIPES` | 局域网 TCP + beacon 扩展 | dev/build 启用 |
|
||||
|
||||
手动启用:`FEATURE_UDS_INBOX=1 FEATURE_LAN_PIPES=1 bun run dev`
|
||||
|
||||
## 快速上手
|
||||
|
||||
### 本机多实例
|
||||
|
||||
```bash
|
||||
# 终端 1
|
||||
bun run dev
|
||||
# 启动后自动注册为 main
|
||||
|
||||
# 终端 2
|
||||
bun run dev
|
||||
# 自动注册为 sub-1,被 main 自动 attach
|
||||
```
|
||||
|
||||
在终端 1 中输入 `/pipes`,可以看到两个实例。选中 sub-1 后,输入的消息会自动转发到 sub-1 执行。
|
||||
|
||||
### 局域网多机器
|
||||
|
||||
```bash
|
||||
# 机器 A (192.168.50.22)
|
||||
bun run dev
|
||||
|
||||
# 机器 B (192.168.50.27)
|
||||
bun run dev
|
||||
```
|
||||
|
||||
两边启动后等 3-5 秒(beacon 广播间隔),LAN peers 会自动发现并 attach。输入 `/pipes` 可看到标记 `[LAN]` 的远端实例。
|
||||
|
||||
### 防火墙配置(两台机器都需要)
|
||||
|
||||
**Windows**(管理员 PowerShell):
|
||||
```powershell
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Beacon (UDP)" -Direction Inbound -Protocol UDP -LocalPort 7101 -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Pipes (TCP)" -Direction Inbound -Protocol TCP -LocalPort 1024-65535 -Program (Get-Command bun).Source -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Beacon Out (UDP)" -Direction Outbound -Protocol UDP -RemotePort 7101 -Action Allow -Profile Private
|
||||
# 确认网络为"专用":Get-NetConnectionProfile
|
||||
```
|
||||
|
||||
**macOS**(首次运行时系统弹出对话框,点击"允许"即可):
|
||||
```bash
|
||||
# 如果需要手动放行 pf 防火墙:
|
||||
echo "pass in proto udp from any to any port 7101" | sudo pfctl -ef -
|
||||
```
|
||||
|
||||
**Linux**(firewalld / iptables):
|
||||
```bash
|
||||
# firewalld
|
||||
sudo firewall-cmd --zone=trusted --add-port=7101/udp --permanent
|
||||
sudo firewall-cmd --zone=trusted --add-port=1024-65535/tcp --permanent
|
||||
sudo firewall-cmd --reload
|
||||
|
||||
# 或 iptables
|
||||
sudo iptables -A INPUT -p udp --dport 7101 -j ACCEPT
|
||||
sudo iptables -A INPUT -p tcp --dport 1024:65535 -m owner --uid-owner $(id -u) -j ACCEPT
|
||||
```
|
||||
|
||||
确认:网络为局域网(非公共 WiFi),路由器未开启 AP 隔离。
|
||||
|
||||
## 交互面板与快捷键
|
||||
|
||||
### 状态栏
|
||||
|
||||
执行 `/pipes` 后,输入框底部出现 pipe 状态栏(单行):
|
||||
|
||||
```
|
||||
pipe: cli-a91bad56 (main) 192.168.50.22 2/3 selected selected pipes only · ←/→ or m switch · Shift+↓ edit
|
||||
```
|
||||
|
||||
状态栏始终可见(直到会话结束),显示:当前 pipe 名、角色、IP、已选数/总数、路由模式。
|
||||
|
||||
### 展开选择面板
|
||||
|
||||
按 **Shift+↓**(Shift + 下箭头)展开选择面板:
|
||||
|
||||
```
|
||||
pipe: cli-a91bad56 (main) 192.168.50.22 ↑↓ move Space select ←/→ or m route Enter/Esc close Shift+↓ toggle
|
||||
当前普通 prompt 走 已选 sub;切换不会清空选择
|
||||
☑ cli-da029538 (sub-1 XC/192.168.50.22)
|
||||
☐ cli-04d67950 (main vmwin11/192.168.50.27)
|
||||
☑ cli-893747d3 [offline] (sub-2 vmwin11/192.168.50.27)
|
||||
```
|
||||
|
||||
### 面板内快捷键
|
||||
|
||||
| 快捷键 | 场景 | 作用 |
|
||||
|--------|------|------|
|
||||
| **Shift+↓** | 状态栏可见时 | 展开/收起选择面板 |
|
||||
| **↑ / ↓** | 面板展开时 | 上下移动光标 |
|
||||
| **Space** | 面板展开时 | 切换当前光标所在 pipe 的选中状态(☑ ↔ ☐) |
|
||||
| **Enter** | 面板展开时 | 确认并关闭面板 |
|
||||
| **Esc** | 面板展开时 | 取消并关闭面板 |
|
||||
| **← / → 或 M** | 状态栏可见且有选中 pipe 时 | 切换路由模式(`selected pipes only` ↔ `local main`) |
|
||||
|
||||
### M 键 — 路由模式切换
|
||||
|
||||
M 键(或 ← / →)用于在两种路由模式之间切换,**无需展开面板**:
|
||||
|
||||
| 模式 | 状态栏显示 | 行为 |
|
||||
|------|-----------|------|
|
||||
| `selected pipes only` | 绿色高亮 | 输入的 prompt **仅**发送到选中的 pipe,本地不执行 |
|
||||
| `local main` | 灰色 | 输入的 prompt 在**本地 main** 执行,不转发到任何 pipe |
|
||||
|
||||
切换路由模式**不会清空选择**。你可以在 `local main` 模式下保持选择,随时按 M 切回 `selected pipes only` 继续向远端发送。
|
||||
|
||||
### 完整操作流程示例
|
||||
|
||||
```
|
||||
1. 输入 /pipes → 状态栏出现,显示发现的实例
|
||||
2. 按 Shift+↓ → 展开选择面板
|
||||
3. 按 ↓ 移动到目标 pipe → 光标移到 cli-04d67950
|
||||
4. 按 Space → 选中 ☑ cli-04d67950
|
||||
5. 按 Enter → 确认,面板收起
|
||||
6. 输入 "帮我检查 git status" → prompt 自动发送到 cli-04d67950 执行
|
||||
7. 按 M → 切换到 local main 模式
|
||||
8. 输入 "本地做点什么" → 仅在本地执行
|
||||
9. 按 M → 切回 selected pipes only
|
||||
10. 输入 "继续远端任务" → 又发送到 cli-04d67950
|
||||
```
|
||||
|
||||
## 命令参考
|
||||
|
||||
### /pipes
|
||||
|
||||
显示所有发现的实例,管理选择状态。再次执行 `/pipes` 切换面板展开/收起。
|
||||
|
||||
```
|
||||
/pipes — 显示所有实例 + 切换选择面板
|
||||
/pipes select <name> — 选中某实例(消息会广播到它)
|
||||
/pipes deselect <name> — 取消选中
|
||||
/pipes all — 全选
|
||||
/pipes none — 全部取消
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
Your pipe: cli-a91bad56
|
||||
Role: main
|
||||
Machine ID: 205d6c3a...
|
||||
IP: 192.168.50.22
|
||||
Host: XC
|
||||
|
||||
Main machine: 205d6c3a... (this machine)
|
||||
[main] cli-a91bad56 XC/192.168.50.22 [alive] (you)
|
||||
☑ [sub-1] cli-da029538 XC/192.168.50.22 [alive] [connected]
|
||||
|
||||
LAN Peers:
|
||||
☐ [main] cli-04d67950 vmwin11/192.168.50.27 tcp:192.168.50.27:58853 [LAN]
|
||||
|
||||
Selected: cli-da029538
|
||||
```
|
||||
|
||||
### /attach <name>
|
||||
|
||||
手动 attach 到一个实例,使其成为你的 slave。
|
||||
|
||||
```
|
||||
/attach cli-04d67950 — 连接到指定 pipe(自动解析 LAN TCP 端点)
|
||||
```
|
||||
|
||||
attach 后,对方变为 slave,你变为 master。可以向它发送 prompt。通常不需要手动 attach——heartbeat 会自动发现并连接。
|
||||
|
||||
### /detach <name>
|
||||
|
||||
断开与某个 slave 的连接。
|
||||
|
||||
```
|
||||
/detach cli-04d67950
|
||||
```
|
||||
|
||||
### /send <name> <message>
|
||||
|
||||
向指定 pipe 发送消息(不依赖选择状态,直接指定目标)。
|
||||
|
||||
```
|
||||
/send cli-04d67950 请帮我检查一下日志
|
||||
/send tcp:192.168.50.27:58853 hello — 直接通过 TCP 地址发送
|
||||
```
|
||||
|
||||
### /claim-main
|
||||
|
||||
强制声明当前机器为 main(用于 main 意外退出后的恢复)。
|
||||
|
||||
## 消息路由
|
||||
|
||||
### 选中 pipe 后的自动路由
|
||||
|
||||
1. 通过 `/pipes select` 或 Shift+Down 面板选中一个或多个 pipe
|
||||
2. 在输入框中正常输入消息
|
||||
3. 消息自动发送到所有选中的已连接 pipe
|
||||
4. 每个 pipe 独立执行,结果流式回传到 main 的消息列表
|
||||
|
||||
### 路由模式
|
||||
|
||||
| 模式 | 行为 |
|
||||
|------|------|
|
||||
| `selected`(默认) | 消息发送到选中的 pipe |
|
||||
| `local` | 消息仅在本地执行,不转发 |
|
||||
|
||||
## 架构
|
||||
|
||||
### 通信协议
|
||||
|
||||
所有通讯使用 NDJSON(Newline-Delimited JSON),每行一个消息:
|
||||
|
||||
```json
|
||||
{"type":"ping","from":"cli-abc","ts":"2026-04-11T00:00:00.000Z"}
|
||||
{"type":"prompt","data":"帮我查看 git status","from":"cli-abc","ts":"..."}
|
||||
{"type":"stream","data":"正在执行...","from":"cli-def","ts":"..."}
|
||||
{"type":"done","data":"","from":"cli-def","ts":"..."}
|
||||
```
|
||||
|
||||
### 消息类型
|
||||
|
||||
| 类型 | 方向 | 说明 |
|
||||
|------|------|------|
|
||||
| `ping`/`pong` | 双向 | 健康检查 |
|
||||
| `attach_request`/`accept`/`reject` | M→S/S→M | 连接控制 |
|
||||
| `detach` | M→S | 断开连接 |
|
||||
| `prompt` | M→S | 主向从发送 prompt |
|
||||
| `prompt_ack` | S→M | 从确认接收 |
|
||||
| `stream` | S→M | 从流式回传 AI 输出 |
|
||||
| `tool_start`/`tool_result` | S→M | 工具执行通知 |
|
||||
| `done` | S→M | 本轮完成 |
|
||||
| `error` | 双向 | 错误通知 |
|
||||
| `permission_request`/`response`/`cancel` | 双向 | 权限审批转发 |
|
||||
|
||||
### 传输层
|
||||
|
||||
```
|
||||
本机 LAN
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ PipeServer │ │ PipeServer │
|
||||
│ UDS sock │ │ UDS sock │
|
||||
│ TCP :rand │◄───TCP───►│ TCP :rand │
|
||||
├──────────────┤ ├──────────────┤
|
||||
│ LanBeacon │◄──UDP────►│ LanBeacon │
|
||||
│ 224.0.71.67 │ mcast │ 224.0.71.67 │
|
||||
└──────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
- **UDS**:本机实例间通讯,通过文件系统路径寻址(`~/.claude/pipes/cli-xxx.sock`)
|
||||
- **TCP**:LAN 实例间通讯,动态端口,通过 beacon 发现
|
||||
- **UDP Multicast**:peer 发现,3 秒广播一次 announce 包
|
||||
|
||||
### 角色模型
|
||||
|
||||
| 角色 | 说明 |
|
||||
|------|------|
|
||||
| `main` | 首个启动的实例,管理 registry |
|
||||
| `sub` | 后续启动的同机实例(或被 attach 的 LAN 实例) |
|
||||
| `master` | attach 了至少一个 slave 的实例 |
|
||||
| `slave` | 被 master attach 控制的实例 |
|
||||
|
||||
角色转换:
|
||||
- 首个启动 → `main`
|
||||
- 同机后续启动 → `sub`(自动被 main attach → `slave`)
|
||||
- LAN 发现 → 两边都是 `main`,heartbeat 自动互相 attach
|
||||
- 被 attach → 变为 `slave`(可通过 `/detach` 恢复)
|
||||
|
||||
### 发现机制
|
||||
|
||||
**本机**:通过 `~/.claude/pipes/registry.json` 文件(带文件锁),`machineId` 绑定主机身份。
|
||||
|
||||
**LAN**:通过 UDP multicast beacon:
|
||||
1. 每 3 秒广播 `{ proto, pipeName, machineId, ip, tcpPort, role }`
|
||||
2. 收到其他实例的 announce → 记入 peers Map
|
||||
3. 15 秒未收到 → 标记 peer lost
|
||||
4. Heartbeat 合并 local registry + beacon peers → 统一 attach 目标列表
|
||||
|
||||
### Heartbeat 循环(5 秒间隔)
|
||||
|
||||
```
|
||||
main/master 角色:
|
||||
1. cleanupStaleEntries() — 清理 registry 中死掉的条目
|
||||
2. getAliveSubs() — 获取存活的本地 subs
|
||||
3. refreshDiscoveredPipes() — 刷新 discoveredPipes(包含 LAN peers)
|
||||
4. 合并 LAN peers 到 state
|
||||
5. 构建统一 attach 目标列表 — 本地 subs + LAN peers
|
||||
6. 遍历未连接的目标 → 自动 attach
|
||||
7. 清理断开的 slave 连接 — 同时检查 local registry 和 beacon
|
||||
|
||||
sub 角色:
|
||||
1. 检测 main 是否存活
|
||||
2. main 死亡 → 同机则接管 main 角色,跨机则独立
|
||||
```
|
||||
|
||||
## 关键文件
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/utils/pipeTransport.ts` | PipeServer(双模 UDS+TCP)、PipeClient、类型定义 |
|
||||
| `src/utils/lanBeacon.ts` | UDP multicast beacon、singleton 管理 |
|
||||
| `src/utils/pipeRegistry.ts` | Registry CRUD、角色判定、machineId、LAN merge |
|
||||
| `src/utils/peerAddress.ts` | 地址解析(uds:/bridge:/tcp: scheme) |
|
||||
| `src/screens/REPL.tsx` | Bootstrap、heartbeat、cleanup、prompt 路由 |
|
||||
| `src/hooks/useMasterMonitor.ts` | Slave client registry、消息订阅 |
|
||||
| `src/hooks/useSlaveNotifications.ts` | Slave 端通知处理 |
|
||||
| `src/commands/pipes/pipes.ts` | /pipes 命令 |
|
||||
| `src/commands/attach/attach.ts` | /attach 命令 |
|
||||
| `src/commands/send/send.ts` | /send 命令 |
|
||||
| `packages/builtin-tools/src/tools/SendMessageTool/SendMessageTool.ts` | AI 发消息工具(含 tcp: 支持) |
|
||||
|
||||
## 后续优化方向
|
||||
|
||||
### 安全(P0)
|
||||
|
||||
1. **TCP 认证**:首次连接时交换 HMAC-SHA256 token(基于 machineId + session secret),防止未授权设备连接
|
||||
2. **JSON schema 验证**:在所有 `JSON.parse` 入口点增加 Zod 校验,防止 prototype pollution
|
||||
3. **Beacon 信息脱敏**:hash machineId 后再广播,不暴露硬件序列号
|
||||
|
||||
### 可靠性(P1)
|
||||
|
||||
4. **多网卡选择**:`getLocalIp()` 应优先选择 RFC 1918 地址,排除 VPN/Docker 接口
|
||||
5. **TCP target 验证**:`parseTcpTarget()` 应限制目标为已知 beacon peers 或 RFC 1918 范围
|
||||
6. **PipeServer close()**:改为 `Promise.allSettled` 并行关闭 UDS + TCP,加 `_closing` guard
|
||||
|
||||
### 功能(P2)
|
||||
|
||||
7. **mDNS/DNS-SD**:作为 multicast 受限环境下的 beacon 替代方案
|
||||
8. **固定端口配置**:允许用户指定 TCP 端口范围,便于防火墙精确配置
|
||||
9. **TLS 加密**:TCP 传输加密,防中间人窃听
|
||||
10. **双向 prompt**:当前只有 master → slave 方向,可考虑 slave 主动向 master 发送结果/请求
|
||||
@@ -1,7 +1,7 @@
|
||||
# PROACTIVE — 主动模式
|
||||
|
||||
> Feature Flag: `FEATURE_PROACTIVE=1`(与 `FEATURE_KAIROS=1` 共享功能)
|
||||
> 实现状态:核心模块全部 Stub,布线完整
|
||||
> 实现状态:核心循环与 SleepTool 已落地,部分外围文档仍在补齐
|
||||
> 引用数:37
|
||||
|
||||
## 一、功能概述
|
||||
@@ -21,13 +21,13 @@ PROACTIVE 实现 Tick 驱动的自主代理。CLI 在用户不输入时也能持
|
||||
|
||||
| 模块 | 文件 | 状态 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 核心逻辑 | `src/proactive/index.ts` | **Stub** | `activateProactive()`、`deactivateProactive()`、`isProactiveActive() => false` |
|
||||
| 核心逻辑 | `src/proactive/index.ts` | **已实现** | `activateProactive()`、`deactivateProactive()`、`pause/resume`、`nextTickAt` 调度状态 |
|
||||
| SleepTool 提示 | `src/tools/SleepTool/prompt.ts` | **完整** | 工具提示定义(工具名:`Sleep`) |
|
||||
| 命令注册 | `src/commands.ts:62-65` | **布线** | 动态加载 `./commands/proactive.js` |
|
||||
| 工具注册 | `src/tools.ts:26-28` | **布线** | SleepTool 动态加载 |
|
||||
| REPL 集成 | `src/screens/REPL.tsx` | **布线** | tick 驱动逻辑、占位符、页脚 UI |
|
||||
| 系统提示 | `src/constants/prompts.ts:860-914` | **完整** | 自主工作行为指令(~55 行详细 prompt) |
|
||||
| 会话存储 | `src/utils/sessionStorage.ts:4892-4912` | **布线** | tick 消息注入对话流 |
|
||||
| REPL 集成 | `src/screens/REPL.tsx` | **已实现** | tick 驱动、standby/sleeping 状态、页脚与 bridge automation metadata 上报 |
|
||||
| 系统提示 | `src/constants/prompts.ts:864-918` | **完整** | 自主工作行为指令(~55 行详细 prompt) |
|
||||
| 远控状态镜像 | `src/utils/sessionState.ts` | **已实现** | 向 remote-control/CCR 暴露 `automation_state` 元数据 |
|
||||
|
||||
### 2.2 系统提示内容
|
||||
|
||||
@@ -46,7 +46,7 @@ PROACTIVE 实现 Tick 驱动的自主代理。CLI 在用户不输入时也能持
|
||||
### 2.3 数据流
|
||||
|
||||
```
|
||||
activateProactive() [需要实现]
|
||||
activateProactive()
|
||||
│
|
||||
▼
|
||||
Tick 调度器启动
|
||||
@@ -62,20 +62,22 @@ Tick 调度器启动
|
||||
└── 无事可做 → 必须调用 SleepTool
|
||||
│
|
||||
▼
|
||||
SleepTool 等待 [需要实现]
|
||||
SleepTool 等待
|
||||
│
|
||||
├── 用户插入新工作 / 队列中有命令 → 立即唤醒
|
||||
├── proactive 被关闭 → 立即中断
|
||||
└── 进入休眠时向远端 surfaces 上报 `automation_state = sleeping`
|
||||
│
|
||||
▼
|
||||
下一个 tick 到达
|
||||
```
|
||||
|
||||
## 三、需要补全的内容
|
||||
## 三、当前行为补充
|
||||
|
||||
| 优先级 | 模块 | 工作量 | 说明 |
|
||||
|--------|------|--------|------|
|
||||
| 1 | `src/proactive/index.ts` | 中 | Tick 调度器、activate/deactivate 状态机、pause/resume |
|
||||
| 2 | `src/tools/SleepTool/SleepTool.ts` | 小 | 工具执行(等待指定时间后触发 tick) |
|
||||
| 3 | `src/commands/proactive.js` | 小 | `/proactive` 斜杠命令处理器 |
|
||||
| 4 | `src/hooks/useProactive.ts` | 中 | React hook(REPL 引用但不存在) |
|
||||
- `standby`:proactive 已开启,当前没有执行中的 turn,且已调度下一个 tick。
|
||||
- `sleeping`:模型显式调用 `SleepTool` 进入等待窗口。
|
||||
- remote-control/CCR 通过 `external_metadata.automation_state` 接收这两个状态,用于 Web UI 的 Autopilot 状态显示。
|
||||
- `SleepTool` 现在不是纯定时器;它会在共享命令队列出现新工作时提前醒来。
|
||||
|
||||
## 四、关键设计决策
|
||||
|
||||
@@ -101,9 +103,11 @@ FEATURE_PROACTIVE=1 FEATURE_KAIROS=1 FEATURE_KAIROS_BRIEF=1 bun run dev
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/proactive/index.ts` | 核心逻辑(stub) |
|
||||
| `src/proactive/index.ts` | 核心逻辑与 next-tick 状态 |
|
||||
| `src/tools/SleepTool/prompt.ts` | SleepTool 工具提示 |
|
||||
| `src/constants/prompts.ts:860-914` | 自主工作系统提示 |
|
||||
| `src/screens/REPL.tsx` | REPL tick 集成 |
|
||||
| `src/tools/SleepTool/SleepTool.ts` | 休眠/唤醒执行逻辑 |
|
||||
| `src/constants/prompts.ts:864-918` | 自主工作系统提示 |
|
||||
| `src/screens/REPL.tsx` | REPL tick 集成与 automation 状态上报 |
|
||||
| `src/utils/sessionStorage.ts:4892-4912` | Tick 消息注入 |
|
||||
| `src/utils/sessionState.ts` | bridge/CCR metadata 镜像 |
|
||||
| `src/components/PromptInput/PromptInputFooterLeftSide.tsx` | 页脚 UI 状态 |
|
||||
|
||||
352
docs/features/remote-control-self-hosting.md
Normal file
352
docs/features/remote-control-self-hosting.md
Normal file
@@ -0,0 +1,352 @@
|
||||
# Remote Control Server 私有化部署指南
|
||||
|
||||
本指南说明如何将 Remote Control Server (RCS) 部署到私有环境,并通过 Claude Code CLI 连接使用。
|
||||
|
||||
## 架构概览
|
||||
|
||||
```
|
||||
┌──────────────────┐ ┌──────────────────────┐
|
||||
│ Claude Code CLI │ ◄── HTTP/SSE/WS ─►│ Remote Control │
|
||||
│ (Bridge Worker) │ 长轮询 + 心跳 │ Server (RCS) │
|
||||
└──────────────────┘ │ │
|
||||
│ ┌──────────────┐ │
|
||||
┌──────────────────┐ HTTP/SSE │ │ In-Memory │ │
|
||||
│ Web UI 控制面板 │ ◄─────────────── │ │ Store │ │
|
||||
│ (/code/*) │ │ └──────────────┘ │
|
||||
│ (React + Vite) │ │ ┌──────────────┐ │
|
||||
└──────────────────┘ │ │ JWT Auth │ │
|
||||
│ └──────────────┘ │
|
||||
┌──────────────────┐ │ ┌──────────────┐ │
|
||||
│ acp-link │ ◄── ACP Relay ─── │ │ ACP Handler │ │
|
||||
│ + ACP Agent │ WebSocket │ └──────────────┘ │
|
||||
└──────────────────┘ └──────────────────────┘
|
||||
```
|
||||
|
||||
**RCS 是一个纯内存的中间服务**,它的职责是:
|
||||
- 接收 Claude Code CLI 的环境注册和工作轮询
|
||||
- 接收 acp-link 的 ACP agent 注册,支持 WebSocket relay 桥接
|
||||
- 提供 Web UI 供操作者远程监控和审批
|
||||
- 通过 WebSocket/SSE 双向传输消息
|
||||
- 管理会话、环境、权限请求
|
||||
- 提供 ACP SSE event stream 供外部消费者订阅 channel group 事件
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 一台可被 Claude Code CLI 和 Web 浏览器同时访问的服务器(物理机、VM、容器均可)
|
||||
- [Docker](https://www.docker.com/)
|
||||
- 启用 `BRIDGE_MODE` feature flag 的 Claude Code 构建
|
||||
|
||||
## 部署
|
||||
|
||||
### 构建 Docker 镜像
|
||||
|
||||
在项目根目录执行:
|
||||
|
||||
```bash
|
||||
docker build -t rcs:latest -f packages/remote-control-server/Dockerfile .
|
||||
```
|
||||
|
||||
### 启动容器
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name rcs \
|
||||
-p 3000:3000 \
|
||||
-e RCS_API_KEYS=sk-rcs-your-secret-key-here \
|
||||
-e RCS_BASE_URL=https://rcs.example.com \
|
||||
-v rcs-data:/app/data \
|
||||
--restart unless-stopped \
|
||||
rcs:latest
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
rcs:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: packages/remote-control-server/Dockerfile
|
||||
args:
|
||||
VERSION: "0.1.0"
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- RCS_API_KEYS=sk-rcs-your-secret-key-here
|
||||
- RCS_BASE_URL=https://rcs.example.com
|
||||
volumes:
|
||||
- rcs-data:/app/data
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
rcs-data:
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## 环境变量参考
|
||||
|
||||
### 服务器端
|
||||
|
||||
| 变量 | 必填 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `RCS_API_KEYS` | **是** | _(空)_ | API 密钥列表,逗号分隔。用于客户端认证和 JWT 签名。**务必设置强密钥** |
|
||||
| `RCS_PORT` | 否 | `3000` | 服务监听端口 |
|
||||
| `RCS_HOST` | 否 | `0.0.0.0` | 服务监听地址 |
|
||||
| `RCS_BASE_URL` | 否 | `http://localhost:3000` | 外部访问 URL。用于生成 WebSocket 连接地址,必须与客户端实际访问的地址一致 |
|
||||
| `RCS_VERSION` | 否 | `0.1.0` | 版本号,显示在 `/health` 响应中 |
|
||||
| `RCS_POLL_TIMEOUT` | 否 | `8` | V1 工作轮询超时(秒) |
|
||||
| `RCS_HEARTBEAT_INTERVAL` | 否 | `20` | 心跳间隔(秒) |
|
||||
| `RCS_JWT_EXPIRES_IN` | 否 | `3600` | JWT 令牌有效期(秒) |
|
||||
| `RCS_DISCONNECT_TIMEOUT` | 否 | `300` | 断线判定超时(秒) |
|
||||
| `RCS_WS_IDLE_TIMEOUT` | 否 | `30` | WebSocket 空闲超时(秒),Bun 发送协议级 ping |
|
||||
| `RCS_WS_KEEPALIVE_INTERVAL` | 否 | `20` | 服务端→客户端 keep_alive 帧间隔(秒),防止反向代理关闭空闲连接 |
|
||||
|
||||
### 客户端(Claude Code CLI)
|
||||
|
||||
| 变量 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `CLAUDE_BRIDGE_BASE_URL` | **是** | RCS 服务器地址,例如 `https://rcs.example.com`。设置此变量即启用自托管模式,跳过 GrowthBook 门控 |
|
||||
| `CLAUDE_BRIDGE_OAUTH_TOKEN` | **是** | 认证令牌,必须与服务器端 `RCS_API_KEYS` 中的某个值匹配 |
|
||||
| `CLAUDE_BRIDGE_SESSION_INGRESS_URL` | 否 | WebSocket 入口地址(默认与 `CLAUDE_BRIDGE_BASE_URL` 相同) |
|
||||
| `CLAUDE_CODE_REMOTE` | 否 | 设为 `1` 时标记为远程执行模式 |
|
||||
|
||||
## Claude Code 客户端连接
|
||||
|
||||
### 1. 设置环境变量
|
||||
|
||||
在运行 Claude Code 的机器上设置:
|
||||
|
||||
```bash
|
||||
export CLAUDE_BRIDGE_BASE_URL="https://rcs.example.com"
|
||||
export CLAUDE_BRIDGE_OAUTH_TOKEN="sk-rcs-your-secret-key-here"
|
||||
```
|
||||
|
||||
### 2. 启动 Claude Code
|
||||
|
||||
```bash
|
||||
# 使用 dev 模式(BRIDGE_MODE 默认启用)
|
||||
bun run dev
|
||||
|
||||
# 或使用构建产物
|
||||
bun run dist/cli.js
|
||||
```
|
||||
|
||||
### 3. 执行 /remote-control 命令
|
||||
|
||||
在 Claude Code 的 REPL 中输入:
|
||||
|
||||
```
|
||||
/remote-control
|
||||
```
|
||||
|
||||
环境型 Remote Control(例如 `claude remote-control` 子命令)会向 RCS 注册环境,注册成功后在终端显示连接 URL:
|
||||
|
||||
```
|
||||
https://rcs.example.com/code?bridge=<environmentId>
|
||||
```
|
||||
|
||||
交互式 REPL 方式(`--remote-control` 或 `/remote-control`)在某些桥接模式下也可能直接给出会话 URL:
|
||||
|
||||
```
|
||||
https://rcs.example.com/code/session_<id>
|
||||
```
|
||||
|
||||
两种 URL 都可以直接在浏览器打开并远程操控当前会话;只有 environment 模式才会出现在 Web UI 的环境列表中。
|
||||
|
||||
若已连接,再次执行 `/remote-control` 会显示对话框,包含以下选项:
|
||||
- **Disconnect this session** — 断开远程连接
|
||||
- **Show QR code** — 显示/隐藏二维码
|
||||
- **Continue** — 保持连接,继续使用
|
||||
|
||||
也可通过 CLI 参数直接启动:
|
||||
|
||||
```bash
|
||||
claude remote-control
|
||||
# 或简写
|
||||
claude rc
|
||||
# 或
|
||||
claude bridge
|
||||
```
|
||||
|
||||
## Web UI 控制面板
|
||||
|
||||
通过 `/remote-control` 命令获取 URL 后,在浏览器打开即可使用。
|
||||
|
||||
### 技术栈(v2,2026-04-18 重构)
|
||||
|
||||
Web UI 已从原生 JS 重构为 **React + Vite + Radix UI**:
|
||||
|
||||
- **框架**: React 19 + Vite 构建,TypeScript
|
||||
- **UI 组件**: Radix UI primitives(Dialog、Tabs、Select、Popover 等)
|
||||
- **聊天组件**: 完整的 ACP 聊天界面,支持 Plan 可视化、工具调用展示、权限审批
|
||||
- **AI Elements**: 独立的 AI 交互组件库(message、reasoning、tool、code-block、prompt-input 等)
|
||||
- **ACP 直连**: 支持 QR 码扫描自动跳转 ACP 直连视图(`ACPDirectView`)
|
||||
- **主题系统**: 暗色/亮色主题切换,遵循 Impeccable 设计系统
|
||||
|
||||
### 功能
|
||||
|
||||
- 查看已注册的运行环境(environment 模式),区分 ACP Agent 和 Claude Code 类型
|
||||
- 创建和管理会话
|
||||
- 实时查看对话消息和工具调用
|
||||
- 查看 Autopilot 状态(`standby` / `sleeping`)和自动运行指示
|
||||
- 查看 authoritative task snapshots 驱动的 Tasks 面板
|
||||
- 审批 Claude Code 的工具权限请求
|
||||
- 权限模式选择器(6 种模式:默认/自动接受编辑/跳过权限/规划/不询问/自动判断)
|
||||
- 模型选择器(可选可用模型)
|
||||
- Plan 可视化(进度条、状态图标、优先级标签)
|
||||
- ACP QR 扫描自动跳转到 ACP 聊天界面
|
||||
|
||||
Web UI 使用 UUID 认证(无需用户账户),适合受信任网络环境。
|
||||
|
||||
## ACP 支持
|
||||
|
||||
RCS 支持 ACP (Agent Client Protocol) agent 通过 `acp-link` 包接入。
|
||||
|
||||
### 架构
|
||||
|
||||
```
|
||||
acp-link ──REST注册──► RCS POST /v1/environments/bridge
|
||||
acp-link ──WS identify──► RCS WebSocket (携带 agentId)
|
||||
acp-link ◄──ACP relay──► RCS ◄──Web UI WS──► 浏览器
|
||||
```
|
||||
|
||||
### 后端组件
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/routes/acp/index.ts` | ACP REST 路由:agents 列表、channel groups、relay |
|
||||
| `src/transport/acp-ws-handler.ts` | ACP WebSocket 处理:agent 注册、心跳、消息转发 |
|
||||
| `src/transport/acp-relay-handler.ts` | 前端 WS → acp-link 透传 + EventBus inbound 转发 |
|
||||
| `src/transport/acp-sse-writer.ts` | SSE event stream 供外部消费者订阅 |
|
||||
|
||||
### acp-link 连接
|
||||
|
||||
详见 [acp-link 文档](./acp-link.md)。
|
||||
|
||||
```bash
|
||||
# 在 RCS 环境中启动 acp-link
|
||||
# 注意:claude 本身不支持 ACP,需要用 ccb-bun --acp
|
||||
ACP_RCS_URL=http://localhost:3000 \
|
||||
ACP_RCS_TOKEN=sk-rcs-your-key \
|
||||
acp-link ccb-bun -- --acp
|
||||
```
|
||||
|
||||
ACP session 在 Web UI 中显示品牌色标签,与普通 Claude Code session 区分。
|
||||
|
||||
## 工作流程详解
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 完整工作流程 │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
|
||||
1. Claude Code CLI 启动,设置环境变量指向自托管 RCS
|
||||
|
||||
2. 用户执行 /remote-control 命令
|
||||
|
||||
3. 注册环境
|
||||
CLI ──POST /v1/environments/bridge──► RCS
|
||||
CLI ◄── { environment_id, environment_secret } ── RCS
|
||||
|
||||
4. 终端显示连接 URL
|
||||
https://rcs.example.com/code?bridge=<environmentId>
|
||||
|
||||
5. 开始工作轮询(循环)
|
||||
CLI ──GET /v1/environments/:id/work/poll──► RCS
|
||||
(长轮询,等待任务分配,超时 8 秒后重试)
|
||||
|
||||
6. 浏览器打开 URL → Web UI 创建任务
|
||||
Browser ──POST /web/sessions──► RCS
|
||||
RCS 分配 work 给正在轮询的 CLI
|
||||
|
||||
7. CLI 收到任务并确认
|
||||
CLI ◄── { id, data: { type, sessionId } } ── RCS
|
||||
CLI ──POST /v1/environments/:id/work/:workId/ack──► RCS
|
||||
|
||||
8. 建立会话连接
|
||||
CLI ──WebSocket /v1/session_ingress──► RCS
|
||||
(或使用 V2 的 SSE + HTTP POST)
|
||||
|
||||
9. 双向通信
|
||||
CLI ──消息/工具调用结果──► RCS ──► Browser
|
||||
CLI ◄──权限审批/指令───── RCS ◄──── Browser
|
||||
CLI ──automation_state / task_state──► RCS ──► Browser
|
||||
|
||||
10. 心跳保活(每 20 秒)
|
||||
CLI ──POST /v1/environments/:id/work/:workId/heartbeat──► RCS
|
||||
|
||||
11. 任务完成 → 归档会话 → 注销环境
|
||||
```
|
||||
|
||||
## 故障排查
|
||||
|
||||
### Web UI 看不到当前 Autopilot 状态
|
||||
|
||||
- `standby`:proactive 已开启,正在等待下一个 tick
|
||||
- `sleeping`:模型正在 `SleepTool` 等待窗口中
|
||||
|
||||
这两个状态通过 worker `external_metadata.automation_state` 上报。如果页面只显示普通 working spinner,优先检查 CLI 和 RCS 之间的 worker metadata PUT 是否成功。
|
||||
|
||||
### CLI 无法连接
|
||||
|
||||
```
|
||||
Error: Remote Control is not available in this build.
|
||||
```
|
||||
|
||||
**原因**:`BRIDGE_MODE` feature flag 未启用。
|
||||
|
||||
**解决**:使用 dev 模式(默认启用)或确保构建时包含 `BRIDGE_MODE` flag。
|
||||
|
||||
### 认证失败 (401)
|
||||
|
||||
```
|
||||
Error: Unauthorized
|
||||
```
|
||||
|
||||
**检查项**:
|
||||
1. `CLAUDE_BRIDGE_OAUTH_TOKEN` 是否与 `RCS_API_KEYS` 中的值匹配
|
||||
2. API Key 是否包含多余的空格或换行
|
||||
3. 两个环境变量是否都已正确设置
|
||||
|
||||
### WebSocket 连接中断
|
||||
|
||||
**检查项**:
|
||||
1. 如果使用反向代理,确认已正确配置 WebSocket 升级(`Upgrade` / `Connection` 头)
|
||||
2. 代理的 `proxy_read_timeout` 是否足够大(建议 86400 秒)
|
||||
3. 网络防火墙是否允许 WebSocket 流量
|
||||
|
||||
### 健康检查
|
||||
|
||||
```bash
|
||||
curl https://rcs.example.com/health
|
||||
# 预期: {"status":"ok","version":"0.1.0"}
|
||||
```
|
||||
|
||||
## 限制与注意事项
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 存储 | 纯内存存储(Map),服务器重启后所有会话和环境数据丢失 |
|
||||
| 扩展 | 不支持水平扩展(无共享状态),单实例部署 |
|
||||
| 并发 | 适合中小规模使用,大量并发会话可能需要性能调优 |
|
||||
| 数据持久化 | `/app/data` 卷已预留但当前未使用,未来可能用于持久化 |
|
||||
| Web UI 认证 | 基于 UUID,无用户账户系统,适合受信任网络环境 |
|
||||
|
||||
## 与云端模式对比
|
||||
|
||||
| 特性 | 云端 (Anthropic CCR) | 自托管 (RCS) |
|
||||
|------|---------------------|--------------|
|
||||
| 认证方式 | claude.ai OAuth 订阅 | API Key |
|
||||
| GrowthBook 门控 | 需要 `tengu_ccr_bridge` 通过 | 自动跳过 |
|
||||
| 功能标志 | 需要 `BRIDGE_MODE=1` | 同样需要 |
|
||||
| 部署位置 | Anthropic 云端 | 用户自有服务器 |
|
||||
| 数据流经 | Anthropic 基础设施 | 用户私有网络 |
|
||||
| 依赖 | claude.ai 订阅 + OAuth | 仅需 API Key |
|
||||
|
||||
自托管模式的核心优势是:设置 `CLAUDE_BRIDGE_BASE_URL` 后,代码自动调用 `isSelfHostedBridge()` 返回 `true`,跳过所有 GrowthBook 和订阅检查,无需 claude.ai 账户即可使用。
|
||||
310
docs/features/stub-recovery-design-1-4.md
Normal file
310
docs/features/stub-recovery-design-1-4.md
Normal file
@@ -0,0 +1,310 @@
|
||||
# Stub 恢复设计 1-4
|
||||
|
||||
> 日期:2026-04-12
|
||||
> 目标:基于当前代码边界,为下一阶段 4 个 stub/半 stub 命令面给出可实施的设计方案。
|
||||
> 排序原则:按建议实施顺序排序,不按问题严重性排序。
|
||||
|
||||
## 设计原则
|
||||
|
||||
- 先做能独立闭环、收益明确、改动边界清晰的项。
|
||||
- 大项拆成 `MVP` 和 `Phase 2+`,避免一次性掉进大范围恢复。
|
||||
- 优先复用已有状态、传输层、日志与配置能力,不重造协议。
|
||||
- 设计以当前仓库实际代码为准,不以旧文档的理想状态为准。
|
||||
|
||||
## 1. `claude daemon status` / `claude daemon stop`
|
||||
|
||||
### 现状
|
||||
|
||||
- `start` 路径已有完整 supervisor + worker 生命周期:
|
||||
`src/daemon/main.ts`
|
||||
`src/daemon/workerRegistry.ts`
|
||||
- `status` / `stop` 目前只是占位输出:
|
||||
`src/daemon/main.ts`
|
||||
- `/remote-control-server` 有自己的命令内 UI 状态,但只维护当前进程内的 `daemonProcess`,并不适合作为跨进程 CLI 管理基础:
|
||||
`src/commands/remoteControlServer/remoteControlServer.tsx`
|
||||
|
||||
### 目标
|
||||
|
||||
- 让 `claude daemon status` 和 `claude daemon stop` 在另一个 CLI 进程中也能正确工作。
|
||||
- 不依赖 TUI 内存态,不要求当前命令进程就是启动 daemon 的那个进程。
|
||||
|
||||
### MVP 方案
|
||||
|
||||
- 新增 daemon 状态文件,例如:
|
||||
`~/.claude/daemon/remote-control.json`
|
||||
- `start` 时写入:
|
||||
- supervisor pid
|
||||
- cwd
|
||||
- startedAt
|
||||
- worker kinds
|
||||
- 最近状态
|
||||
- `status`:
|
||||
- 读取状态文件
|
||||
- 用现有进程探测能力验证 pid 是否存活
|
||||
- 输出 `running / stopped / stale`
|
||||
- stale 时自动清理状态文件
|
||||
- `stop`:
|
||||
- 读取 pid
|
||||
- 发送 `SIGTERM`
|
||||
- 等待退出
|
||||
- 超时后 `SIGKILL`
|
||||
- 清理状态文件
|
||||
|
||||
### 代码范围
|
||||
|
||||
- 新增 `src/daemon/state.ts`
|
||||
- 修改 `src/daemon/main.ts`
|
||||
- 轻量修改 `src/commands/remoteControlServer/remoteControlServer.tsx`,让 UI 尽量读取同一份状态文件
|
||||
|
||||
### 验证
|
||||
|
||||
1. `claude daemon start`
|
||||
2. 新开终端执行 `claude daemon status`
|
||||
3. 执行 `claude daemon stop`
|
||||
4. 再次执行 `claude daemon status`,确认返回 `stopped` 或清晰的 `stale cleaned`
|
||||
|
||||
### 风险
|
||||
|
||||
- Windows 信号模型和 Unix 不同,`stop` 需要超时兜底。
|
||||
- 当前设计默认单 supervisor,不处理多实例并发。
|
||||
|
||||
### 工作量判断
|
||||
|
||||
- 小
|
||||
- 适合作为下一步的首选实现项
|
||||
|
||||
## 2. `BG_SESSIONS`
|
||||
|
||||
### 现状
|
||||
|
||||
- fast-path 已接好:
|
||||
`src/entrypoints/cli.tsx`
|
||||
- session registry 已有真实实现:
|
||||
`src/utils/concurrentSessions.ts`
|
||||
- `exit` 在 bg session 内已会 `tmux detach-client`:
|
||||
`src/commands/exit/exit.tsx`
|
||||
- 但 CLI handler 仍全空:
|
||||
`src/cli/bg.ts`
|
||||
- task summary 仍然是 stub:
|
||||
`src/utils/taskSummary.ts`
|
||||
|
||||
### 目标
|
||||
|
||||
- 先把 `ps` / `logs` / `kill` 做成真正有用的 session 管理命令。
|
||||
- 不在第一阶段就强行补完 `attach` / `--bg`。
|
||||
|
||||
### Phase 2A:MVP
|
||||
|
||||
- 实现 `ps`
|
||||
- 从 registry 读取 live sessions
|
||||
- 展示 pid、kind、sessionId、cwd、name、startedAt、bridgeSessionId
|
||||
- 如果有 activity/status,则一并展示
|
||||
- 实现 `logs`
|
||||
- 支持按 `sessionId / pid / name` 查找
|
||||
- 优先复用本地 transcript/log 读取能力
|
||||
- 如果 registry 里存在 `logPath`,支持 tail 文件
|
||||
- 实现 `kill`
|
||||
- 解析目标 session
|
||||
- 发退出信号
|
||||
- 清理 stale registry
|
||||
|
||||
### Phase 2B:后续
|
||||
|
||||
- 实现 `attach`
|
||||
- 实现 `--bg`
|
||||
- 实现 `taskSummary` 的中途状态更新
|
||||
|
||||
### 为什么要拆
|
||||
|
||||
- 现有 registry 记录了 `pid / sessionId / name / logPath`
|
||||
- 但没有可靠的 tmux attach target
|
||||
- 所以 `attach` 和 `--bg` 不是简单补 handler,而是需要补启动/附着元数据设计
|
||||
|
||||
### 代码范围
|
||||
|
||||
- 修改 `src/cli/bg.ts`
|
||||
- 修改 `src/utils/concurrentSessions.ts` 以便后续 attach/--bg 扩展
|
||||
- 修改 `src/utils/taskSummary.ts`
|
||||
- 复用:
|
||||
`src/utils/sessionStorage.ts`
|
||||
`src/utils/udsClient.ts`
|
||||
|
||||
### 验证
|
||||
|
||||
1. `ps` 能列出 live sessions
|
||||
2. `logs <sessionId|pid|name>` 能输出对应日志
|
||||
3. `kill <sessionId|pid|name>` 能结束目标 session
|
||||
|
||||
### 风险
|
||||
|
||||
- `attach` / `--bg` 第二阶段需要 tmux 元数据设计
|
||||
- Windows 下 tmux 路径需要明确降级策略
|
||||
|
||||
### 工作量判断
|
||||
|
||||
- `ps/logs/kill` 中等
|
||||
- `attach/--bg` 明显更大,应分阶段
|
||||
|
||||
## 3. `TEMPLATES`
|
||||
|
||||
### 现状
|
||||
|
||||
- 命令入口只有 fast-path:
|
||||
`src/entrypoints/cli.tsx`
|
||||
- handler 是空的:
|
||||
`src/cli/handlers/templateJobs.ts`
|
||||
- `markdownConfigLoader` 已把 `templates` 纳入配置目录:
|
||||
`src/utils/markdownConfigLoader.ts`
|
||||
- `query / stopHooks` 已预留 job classifier 链路:
|
||||
`src/query/stopHooks.ts`
|
||||
- `jobs/classifier.ts` 仍是 stub:
|
||||
`src/jobs/classifier.ts`
|
||||
|
||||
### 目标
|
||||
|
||||
- 把 `new / list / reply` 做成可用的模板任务系统。
|
||||
- 第一阶段不碰复杂的自动分类与自动执行。
|
||||
|
||||
### MVP 方案
|
||||
|
||||
- 模板来源:
|
||||
`.claude/templates/*.md`
|
||||
- 模板格式:
|
||||
复用现有 markdown + frontmatter 解析,不另外设计 DSL
|
||||
- `list`
|
||||
- 列出所有模板
|
||||
- 显示模板名、description、路径
|
||||
- `new <template> [args...]`
|
||||
- 解析模板
|
||||
- 在 `~/.claude/jobs/<job-id>/` 下创建 job 目录
|
||||
- 写入 `template.md`、`input.txt`、`state.json`
|
||||
- 返回 job id 与目录
|
||||
- `reply <job-id> <text>`
|
||||
- 将回复写入 `replies.jsonl` 或 `input.txt`
|
||||
- 更新 `state.json`
|
||||
|
||||
### Phase 2
|
||||
|
||||
- 恢复 `src/jobs/classifier.ts`
|
||||
- 让带 `CLAUDE_JOB_DIR` 的 job session 在 turn 完成后自动更新 `state.json`
|
||||
- 再决定是否补自动 job runner
|
||||
|
||||
### 为什么要拆
|
||||
|
||||
- 当前证据表明这是“template job commands”,不是单纯模板列表
|
||||
- 但自动 job 运行链路没有足够现成实现,先做文件系统 job lifecycle 更稳
|
||||
|
||||
### 代码范围
|
||||
|
||||
- 修改 [src/cli/handlers/templateJobs.ts](</e:/Source_code/Claude-code-bast/src/cli/handlers/templateJobs.ts:1>)
|
||||
- 新增 `src/jobs/state.ts`
|
||||
- 新增 `src/jobs/templates.ts`
|
||||
- Phase 2 再改 [src/jobs/classifier.ts](</e:/Source_code/Claude-code-bast/src/jobs/classifier.ts:1>)
|
||||
|
||||
### 验证
|
||||
|
||||
1. `list` 能列出 `.claude/templates`
|
||||
2. `new` 能创建 job 目录和状态文件
|
||||
3. `reply` 能更新 job 内容和状态
|
||||
4. Phase 2 再验证 classifier 写状态
|
||||
|
||||
### 风险
|
||||
|
||||
- frontmatter schema 需要先定义最小字段集
|
||||
- 一旦扩展到“自动运行 job”,范围会明显膨胀
|
||||
|
||||
### 工作量判断
|
||||
|
||||
- MVP 中等
|
||||
- 完整 job 系统偏大
|
||||
|
||||
## 4. `assistant [sessionId]`
|
||||
|
||||
### 现状
|
||||
|
||||
- attach 主流程其实已经存在:
|
||||
[src/main.tsx](</e:/Source_code/Claude-code-bast/src/main.tsx:4708>)
|
||||
- 远端 viewer 所需基础模块已存在:
|
||||
[src/remote/RemoteSessionManager.ts](</e:/Source_code/Claude-code-bast/src/remote/RemoteSessionManager.ts:1>)
|
||||
[src/hooks/useAssistantHistory.ts](</e:/Source_code/Claude-code-bast/src/hooks/useAssistantHistory.ts:1>)
|
||||
[src/assistant/sessionHistory.ts](</e:/Source_code/Claude-code-bast/src/assistant/sessionHistory.ts:1>)
|
||||
- 真正 stub 的主要是:
|
||||
[src/assistant/sessionDiscovery.ts](</e:/Source_code/Claude-code-bast/src/assistant/sessionDiscovery.ts:1>)
|
||||
[src/assistant/AssistantSessionChooser.ts](</e:/Source_code/Claude-code-bast/src/assistant/AssistantSessionChooser.ts:1>)
|
||||
[src/commands/assistant/assistant.ts](</e:/Source_code/Claude-code-bast/src/commands/assistant/assistant.ts:7>)
|
||||
[src/assistant/index.ts](</e:/Source_code/Claude-code-bast/src/assistant/index.ts:1>)
|
||||
|
||||
### 目标
|
||||
|
||||
- 不一次性恢复整个 KAIROS 助手系统。
|
||||
- 先做“明确 sessionId 的 viewer attach 可用”,再逐步补 discovery / chooser / install。
|
||||
|
||||
### Phase 4A:MVP
|
||||
|
||||
- 只支持 `claude assistant <sessionId>`
|
||||
- 对 `claude assistant` 无参数模式,先返回明确提示:
|
||||
- 当前版本需要显式 `sessionId`
|
||||
- discovery 尚未启用
|
||||
- 这样可以直接复用现有 attach 分支,不必先恢复 chooser/install wizard
|
||||
|
||||
### Phase 4B
|
||||
|
||||
- 恢复 `discoverAssistantSessions()`
|
||||
- 数据来源优先复用现有 sessions / bridge / teleport API,而不是新协议
|
||||
- 让 `claude assistant` 无参数时能拿到候选 session 列表
|
||||
|
||||
### Phase 4C
|
||||
|
||||
- 恢复 `AssistantSessionChooser`
|
||||
- 多 session 时可交互选择
|
||||
|
||||
### Phase 4D
|
||||
|
||||
- 最后考虑 install wizard 辅助函数
|
||||
- 这部分属于“没有 session 时如何引导”,不是 attach 核心路径
|
||||
|
||||
### 为什么要拆
|
||||
|
||||
- attach 渲染层与远端消息通道大部分已经在
|
||||
- 真正缺的是“如何发现目标 session”和“如何交互选择”
|
||||
- 如果把 `src/assistant/index.ts` 的整套 KAIROS 正常模式也一起拉进来,范围会失控
|
||||
|
||||
### 代码范围
|
||||
|
||||
- Phase 4A:
|
||||
- [src/main.tsx](</e:/Source_code/Claude-code-bast/src/main.tsx:4708>)
|
||||
- [src/commands/assistant/index.ts](</e:/Source_code/Claude-code-bast/src/commands/assistant/index.ts:1>)
|
||||
- Phase 4B:
|
||||
- [src/assistant/sessionDiscovery.ts](</e:/Source_code/Claude-code-bast/src/assistant/sessionDiscovery.ts:1>)
|
||||
- Phase 4C:
|
||||
- [src/assistant/AssistantSessionChooser.ts](</e:/Source_code/Claude-code-bast/src/assistant/AssistantSessionChooser.ts:1>)
|
||||
- Phase 4D:
|
||||
- [src/commands/assistant/assistant.ts](</e:/Source_code/Claude-code-bast/src/commands/assistant/assistant.ts:7>)
|
||||
|
||||
### 验证
|
||||
|
||||
1. `claude assistant <sessionId>` 能进入 remote viewer
|
||||
2. 历史懒加载工作正常
|
||||
3. 无参数模式先给出明确提示
|
||||
4. 后续阶段再分别验证 discovery / chooser / install
|
||||
|
||||
### 风险
|
||||
|
||||
- 这是四项里范围最大的
|
||||
- 一旦把 KAIROS 正常模式整体拉入,会从“viewer attach”膨胀成“完整 assistant mode 恢复”
|
||||
|
||||
### 工作量判断
|
||||
|
||||
- Phase 4A 中等
|
||||
- 4A-4D 全做完很大
|
||||
|
||||
## 建议执行顺序
|
||||
|
||||
1. `claude daemon status` / `claude daemon stop`
|
||||
2. `BG_SESSIONS` 先做 `ps/logs/kill`
|
||||
3. `TEMPLATES` 先做 job 文件系统 MVP
|
||||
4. `assistant [sessionId]` 先做显式 sessionId attach,再补 discovery/chooser/install
|
||||
|
||||
## 简短结论
|
||||
|
||||
这四项里,最适合立刻实现的是 `daemon status/stop`。`BG_SESSIONS` 和 `TEMPLATES` 适合按 MVP 先补 handler 与文件系统闭环。`assistant [sessionId]` 不能整块硬上,应该按“attach → discovery → chooser → install”拆开恢复。
|
||||
@@ -7,51 +7,13 @@
|
||||
|
||||
| Feature | 引用 | 状态 | 类别 | 简要说明 |
|
||||
|---------|------|------|------|---------|
|
||||
| CHICAGO_MCP | 16 | N/A | 内部基础设施 | Anthropic 内部 MCP 基础设施,非外部可用 |
|
||||
| UDS_INBOX | 17 | Stub | 消息通信 | Unix 域套接字对等消息,进程间消息传递 |
|
||||
| MONITOR_TOOL | 13 | Stub | 工具 | 文件/进程监控工具,检测变更并通知 |
|
||||
| BG_SESSIONS | 11 | Stub | 会话管理 | 后台会话管理,支持多会话并行 |
|
||||
| SHOT_STATS | 10 | 无实现 | 统计 | 逐 prompt 统计信息收集 |
|
||||
| EXTRACT_MEMORIES | 7 | 无实现 | 记忆 | 自动从对话中提取重要信息作为记忆 |
|
||||
| TEMPLATES | 6 | Stub | 项目管理 | 项目/提示模板系统 |
|
||||
| LODESTONE | 6 | N/A | 内部基础设施 | 内部基础设施模块 |
|
||||
| STREAMLINED_OUTPUT | 1 | — | 输出 | 精简输出模式,减少终端输出量 |
|
||||
| HOOK_PROMPTS | 1 | — | 钩子 | Hook 提示词,自定义钩子的提示注入 |
|
||||
| CCR_AUTO_CONNECT | 3 | — | 远程控制 | CCR 自动连接,自动建立远程控制会话 |
|
||||
| CCR_MIRROR | 4 | — | 远程控制 | CCR 镜像模式,会话状态同步 |
|
||||
| CCR_REMOTE_SETUP | 1 | — | 远程控制 | CCR 远程设置,初始化远程控制配置 |
|
||||
| NATIVE_CLIPBOARD_IMAGE | 2 | — | 系统集成 | 原生剪贴板图片,从剪贴板读取图片 |
|
||||
| CONNECTOR_TEXT | 7 | — | 连接器 | 连接器文本,外部系统文本适配 |
|
||||
| COMMIT_ATTRIBUTION | 12 | — | Git | Commit 归因,标记 commit 来源 |
|
||||
| CACHED_MICROCOMPACT | 12 | — | 压缩 | 缓存微压缩,优化 compaction 性能 |
|
||||
| PROMPT_CACHE_BREAK_DETECTION | 9 | — | 性能 | Prompt cache 中断检测,监控 cache miss |
|
||||
| MEMORY_SHAPE_TELEMETRY | 3 | — | 遥测 | 记忆形态遥测,记忆使用模式追踪 |
|
||||
| MCP_RICH_OUTPUT | 3 | — | MCP | MCP 富输出,增强 MCP 工具输出格式 |
|
||||
| FILE_PERSISTENCE | 3 | — | 持久化 | 文件持久化,跨会话保持状态 |
|
||||
| TREE_SITTER_BASH_SHADOW | 5 | Shadow | 安全 | Bash AST Shadow 模式(见 tree-sitter-bash.md) |
|
||||
| QUICK_SEARCH | 5 | — | 搜索 | 快速搜索,优化的文件/内容搜索 |
|
||||
| MESSAGE_ACTIONS | 5 | — | UI | 消息操作,对消息执行后处理动作 |
|
||||
| DOWNLOAD_USER_SETTINGS | 5 | — | 配置 | 下载用户设置,从服务端同步配置 |
|
||||
| DIRECT_CONNECT | 5 | — | 网络 | 直连模式,绕过代理直接连接 API |
|
||||
| VERIFICATION_AGENT | 4 | — | Agent | 验证 Agent,专门用于验证代码变更 |
|
||||
| TERMINAL_PANEL | 4 | — | UI | 终端面板,嵌入式终端输出显示 |
|
||||
| SSH_REMOTE | 4 | — | 远程 | SSH 远程,通过 SSH 连接远程 Claude |
|
||||
| REVIEW_ARTIFACT | 4 | — | 审查 | Review Artifact,代码审查产出物 |
|
||||
| REACTIVE_COMPACT | 4 | — | 压缩 | 响应式压缩,基于上下文变化触发 compaction |
|
||||
| HISTORY_PICKER | 4 | — | UI | 历史选择器,浏览和选择历史对话 |
|
||||
| UPLOAD_USER_SETTINGS | 2 | — | 配置 | 上传用户设置,同步配置到服务端 |
|
||||
| POWERSHELL_AUTO_MODE | 2 | — | 平台 | PowerShell 自动模式,Windows 权限自动化 |
|
||||
| OVERFLOW_TEST_TOOL | 2 | — | 测试 | 溢出测试工具,测试上下文溢出处理 |
|
||||
| NEW_INIT | 2 | — | 初始化 | 新版初始化流程 |
|
||||
| HARD_FAIL | 2 | — | 错误处理 | 硬失败模式,不可恢复错误直接终止 |
|
||||
| ENHANCED_TELEMETRY_BETA | 2 | — | 遥测 | 增强遥测 Beta,详细的性能指标收集 |
|
||||
| COWORKER_TYPE_TELEMETRY | 2 | — | 遥测 | 协作者类型遥测,追踪协作模式 |
|
||||
| BREAK_CACHE_COMMAND | 2 | — | 缓存 | 中断缓存命令,强制刷新 prompt cache |
|
||||
| AWAY_SUMMARY | 2 | — | 摘要 | 离开摘要,用户返回时总结期间工作 |
|
||||
| AUTO_THEME | 2 | — | UI | 自动主题,根据终端设置切换主题 |
|
||||
| ALLOW_TEST_VERSIONS | 2 | — | 版本 | 允许测试版本,跳过版本检查 |
|
||||
| AGENT_TRIGGERS_REMOTE | 2 | — | Agent | Agent 远程触发,从远程触发 Agent 任务 |
|
||||
| AGENT_MEMORY_SNAPSHOT | 2 | — | Agent | Agent 记忆快照,保存/恢复 Agent 状态 |
|
||||
| CHICAGO_MCP | 16 | 已实现 | 工具 | Computer Use + Chrome MCP 控制(build 默认启用) |
|
||||
| MONITOR_TOOL | 13 | 已实现 | 工具 | 后台监控工具,持续监视 shell 输出(build 默认启用) |
|
||||
| BG_SESSIONS | 11 | 部分实现 | 会话管理 | 后台会话注册/清理已实现,任务摘要是 stub(dev 默认启用) |
|
||||
| SHOT_STATS | 10 | 已实现 | 统计 | API 调用统计面板(build 默认启用) |
|
||||
| EXTRACT_MEMORIES | 7 | 已实现 | 记忆 | 自动记忆提取(build 默认启用,受 GrowthBook 门控) |
|
||||
| TEMPLATES | 6 | 部分实现 | 项目管理 | 项目/提示模板系统(dev 默认启用) |
|
||||
| LODESTONE | 6 | 已实现 | 深度链接 | URL 协议处理器(build 默认启用) |
|
||||
|
||||
## 单引用 Feature(40+ 个)
|
||||
|
||||
@@ -67,10 +29,9 @@ BUILDING_CLAUDE_APPS, ANTI_DISTILLATION_CC, AGENT_TRIGGERS, ABLATION_BASELINE
|
||||
|
||||
这些 feature 被列为 Tier 3 的原因:
|
||||
|
||||
1. **内部基础设施**(CHICAGO_MCP, LODESTONE):Anthropic 内部使用,外部无法运行
|
||||
2. **纯 Stub 且引用低**(UDS_INBOX, MONITOR_TOOL, BG_SESSIONS):需要大量工作才能实现
|
||||
3. **实验性功能**(SHOT_STATS, EXTRACT_MEMORIES):尚在概念阶段
|
||||
4. **辅助功能**(STREAMLINED_OUTPUT, HOOK_PROMPTS):影响范围小
|
||||
5. **CCR 系列**:依赖远程控制基础设施,需要 BRIDGE_MODE 先完善
|
||||
1. **已实现但影响范围小**(CHICAGO_MCP, LODESTONE, SHOT_STATS, EXTRACT_MEMORIES, MONITOR_TOOL):已在 build/dev 默认启用,主要作为其他功能的基础设施
|
||||
2. **部分实现**(BG_SESSIONS, TEMPLATES):核心注册已实现,但部分功能如任务摘要仍是 stub
|
||||
3. **辅助功能**(STREAMLINED_OUTPUT, HOOK_PROMPTS):影响范围小
|
||||
4. **CCR 系列**:依赖远程控制基础设施,需要 BRIDGE_MODE 先完善
|
||||
|
||||
如需深入了解某个 Tier 3 feature,可以在代码库中搜索 `feature('FEATURE_NAME')` 查看具体使用场景。
|
||||
|
||||
@@ -191,7 +191,7 @@ FEATURE_TOKEN_BUDGET=1 bun run dev
|
||||
| `src/query/tokenBudget.ts` | 93 | 预算追踪器 + continue/stop 决策 |
|
||||
| `src/bootstrap/state.ts:724-743` | 20 | turn 级 token 快照状态 |
|
||||
| `src/constants/prompts.ts:538-551` | 14 | 系统提示注入 |
|
||||
| `src/utils/attachments.ts:3829-3845` | 17 | API attachment 附加 |
|
||||
| `src/utils/attachments.ts:3830-3844` | 17 | API attachment 附加 |
|
||||
| `src/query.ts:280,1311-1358` | 48 | 主循环集成 |
|
||||
| `src/screens/REPL.tsx:2897,2963,2138` | 20 | REPL 提交/完成/取消处理 |
|
||||
| `src/components/Spinner.tsx:319-338` | 20 | 进度条 UI |
|
||||
|
||||
@@ -158,4 +158,4 @@ FEATURE_TREE_SITTER_BASH_SHADOW=1 bun run dev
|
||||
| `src/utils/bash/bashParser.ts` | 4437 | 纯 TS bash 解析器 |
|
||||
| `src/utils/bash/ast.ts` | 2680 | 安全分析器(核心) |
|
||||
| `src/utils/bash/treeSitterAnalysis.ts` | 507 | AST 分析辅助 |
|
||||
| `src/tools/BashTool/bashPermissions.ts:1670-1810` | ~140 | 权限集成 + Shadow 遥测 |
|
||||
| `packages/builtin-tools/src/tools/BashTool/bashPermissions.ts` | ~140 | 权限集成 + Shadow 遥测 |
|
||||
|
||||
114
docs/features/uds-inbox.md
Normal file
114
docs/features/uds-inbox.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# UDS_INBOX / pipes
|
||||
|
||||
## 概述
|
||||
|
||||
`UDS_INBOX` 现在不是一个“空壳 flag”,而是一套已经落地的本机 IPC 能力。但它同时承载了两层不同目标,必须拆开理解:
|
||||
|
||||
1. **UDS peer messaging**
|
||||
- 面向任意 Claude Code 进程。
|
||||
- 使用 `src/utils/udsMessaging.ts` 和 `src/utils/udsClient.ts`。
|
||||
- 对外入口是 `/peers` 和 `SendMessageTool` 的 `uds:<socket-path>` 地址。
|
||||
2. **pipes control plane**
|
||||
- 面向交互式 REPL 会话之间的主从协作。
|
||||
- 使用 `src/utils/pipeTransport.ts`、`src/utils/pipeRegistry.ts` 和 `src/screens/REPL.tsx` 中的内联 bootstrap。
|
||||
- 对外入口是 `/pipes`、`/attach`、`/detach`、`/send`、`/pipe-status`、`/history`、`/claim-main`。
|
||||
|
||||
这两层都依赖本机 socket,但职责不同。`/peers` 解决“找到其他会话并发消息”,`/pipes` 解决“把一个 REPL 变成另一个 REPL 的受控 worker”。
|
||||
|
||||
## 为什么要有单独的 `pipes`
|
||||
|
||||
单独的 `pipes` 层有三个实际理由:
|
||||
|
||||
1. **命名与角色模型不同**
|
||||
- UDS peer 层按 `messagingSocketPath` 寻址。
|
||||
- pipes 层按 `cli-xxxxxxxx` 会话名、`main/sub/master/slave` 角色和 `machineId` 注册表工作。
|
||||
2. **交互语义不同**
|
||||
- peer 层是通用消息投递。
|
||||
- pipes 层需要 attach、detach、历史收集、选择性广播、状态栏和 REPL 快捷键。
|
||||
3. **UI 集成不同**
|
||||
- peer 层主要服务工具调用。
|
||||
- pipes 层直接影响 REPL 提交路径和 PromptInput 页脚。
|
||||
|
||||
如果把两者硬合并,`SendMessageTool` 的通用寻址和 REPL 的主从控制会互相污染,命令语义也会变得混乱。
|
||||
|
||||
## 当前通信模型
|
||||
|
||||
### 1. UDS peer messaging
|
||||
|
||||
- 服务端:`src/utils/udsMessaging.ts`
|
||||
- 客户端:`src/utils/udsClient.ts`
|
||||
- 发现方式:读取 `~/.claude/sessions/*.json`
|
||||
- 地址方式:`uds:<socket-path>`
|
||||
- 传输方式:**本机 Unix socket / Windows named pipe**
|
||||
|
||||
这层是真正的“通用收件箱”。
|
||||
|
||||
### 2. pipes control plane
|
||||
|
||||
- 服务端/客户端:`src/utils/pipeTransport.ts`
|
||||
- 注册表:`src/utils/pipeRegistry.ts`
|
||||
- 生效入口:`src/screens/REPL.tsx`
|
||||
- 发现方式:扫描 `~/.claude/pipes/` + `registry.json`
|
||||
- 会话名:`cli-${sessionId.slice(0, 8)}`
|
||||
- 传输方式:**本机 Unix socket / Windows named pipe**
|
||||
|
||||
这层是真正的“主从 REPL 协调平面”。
|
||||
|
||||
## 关于“局域网通信”的事实
|
||||
|
||||
当前实现**不是**真正的局域网传输。
|
||||
|
||||
代码里虽然保存了这些字段:
|
||||
|
||||
- `localIp`
|
||||
- `hostname`
|
||||
- `machineId`
|
||||
- `mac`
|
||||
|
||||
但这些字段当前只用于:
|
||||
|
||||
1. 注册表展示
|
||||
2. main/sub 身份判定
|
||||
3. `claim-main` 的机器级归属切换
|
||||
4. 状态输出与排障信息
|
||||
|
||||
它们**没有**被用于创建 TCP/WebSocket 连接。真正的传输仍然是 `getPipePath(name)` 返回的本机 socket 路径。
|
||||
|
||||
所以目前更准确的描述应该是:
|
||||
|
||||
- `pipes` 支持 **本机多实例协作**
|
||||
- `registry` 带有 **机器身份元数据**
|
||||
- 但 **尚未实现跨机器局域网 transport**
|
||||
|
||||
如果未来要做真局域网版本,至少还需要:
|
||||
|
||||
1. TCP/WebSocket transport
|
||||
2. 认证与会话授权
|
||||
3. 发现与地址交换
|
||||
4. 超时、重连和安全边界
|
||||
|
||||
## 当前 REPL 行为
|
||||
|
||||
当前线上行为由 `src/screens/REPL.tsx` 的内联实现负责:
|
||||
|
||||
1. 启动时创建当前 REPL 的 pipe server
|
||||
2. 通过 `pipeRegistry` 判定 `main` / `sub`
|
||||
3. 处理 `attach_request` / `detach` / `prompt`
|
||||
4. 主实例心跳探测并维护 `slaves`
|
||||
5. `/pipes` 打开状态栏并维护选择器
|
||||
6. 提交普通消息时,仅向**已连接**的 selected pipes 广播
|
||||
|
||||
最近的收敛点:
|
||||
|
||||
- 过去遗留了一套未接线的 hook 方案
|
||||
- 当前已明确以 `REPL.tsx` 内联 bootstrap 为唯一生效实现
|
||||
- 选中但未连接的 pipe 不再导致本地处理被错误跳过
|
||||
|
||||
## 文档与代码对齐约定
|
||||
|
||||
后续关于 `UDS_INBOX` / `pipes` 的说明应遵守以下表述:
|
||||
|
||||
1. 默认称为“本机 IPC / 本机多实例协作”
|
||||
2. 不把 `localIp` / `hostname` 元数据表述成已完成的 LAN transport
|
||||
3. 明确区分 `/peers` 和 `/pipes` 的两层职责
|
||||
4. 以 `src/screens/REPL.tsx`、`src/utils/pipeTransport.ts`、`src/utils/pipeRegistry.ts` 为事实来源
|
||||
@@ -22,16 +22,16 @@ ULTRAPLAN 在用户输入中检测 "ultraplan" 关键字时,自动进入增强
|
||||
|
||||
| 模块 | 文件 | 行数 | 状态 |
|
||||
|------|------|------|------|
|
||||
| 命令处理器 | `src/commands/ultraplan.tsx` | 472 | **完整** |
|
||||
| CCR 会话 | `src/utils/ultraplan/ccrSession.ts` | 350 | **完整** |
|
||||
| 关键字检测 | `src/utils/ultraplan/keyword.ts` | 128 | **完整** |
|
||||
| 命令处理器 | `src/commands/ultraplan.tsx` | 525 | **完整** |
|
||||
| CCR 会话 | `src/utils/ultraplan/ccrSession.ts` | 349 | **完整** |
|
||||
| 关键字检测 | `src/utils/ultraplan/keyword.ts` | 127 | **完整** |
|
||||
| 嵌入式提示 | `src/utils/ultraplan/prompt.txt` | 1 | **完整** |
|
||||
| REPL 对话框 | `src/screens/REPL.tsx` | — | **布线** |
|
||||
| 关键字高亮 | `src/components/PromptInput/PromptInput.tsx` | — | **布线** |
|
||||
|
||||
### 2.2 关键字检测
|
||||
|
||||
文件:`src/utils/ultraplan/keyword.ts`(128 行)
|
||||
文件:`src/utils/ultraplan/keyword.ts`(127 行)
|
||||
|
||||
`findUltraplanTriggerPositions(text)` 智能过滤:
|
||||
- 排除引号内的 "ultraplan"
|
||||
@@ -41,7 +41,7 @@ ULTRAPLAN 在用户输入中检测 "ultraplan" 关键字时,自动进入增强
|
||||
|
||||
### 2.3 CCR 远程会话
|
||||
|
||||
文件:`src/utils/ultraplan/ccrSession.ts`(350 行)
|
||||
文件:`src/utils/ultraplan/ccrSession.ts`(349 行)
|
||||
|
||||
`ExitPlanModeScanner` 类实现完整的事件状态机:
|
||||
- `pollForApprovedExitPlanMode()` — 3 秒轮询间隔
|
||||
@@ -99,9 +99,9 @@ FEATURE_ULTRAPLAN=1 bun run dev
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `src/commands/ultraplan.tsx` | 472 | 斜杠命令处理器 |
|
||||
| `src/utils/ultraplan/ccrSession.ts` | 350 | CCR 远程会话管理 |
|
||||
| `src/utils/ultraplan/keyword.ts` | 128 | 关键字检测和替换 |
|
||||
| `src/commands/ultraplan.tsx` | 525 | 斜杠命令处理器 |
|
||||
| `src/utils/ultraplan/ccrSession.ts` | 349 | CCR 远程会话管理 |
|
||||
| `src/utils/ultraplan/keyword.ts` | 127 | 关键字检测和替换 |
|
||||
| `src/utils/ultraplan/prompt.txt` | 1 | 嵌入式提示 |
|
||||
| `src/utils/processUserInput/processUserInput.ts:468` | — | 关键字重定向 |
|
||||
| `src/components/PromptInput/PromptInput.tsx` | — | 彩虹高亮 |
|
||||
|
||||
@@ -120,6 +120,6 @@ FEATURE_VOICE_MODE=1 bun run dev
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `src/voice/voiceModeEnabled.ts` | 55 | 三层门控逻辑 |
|
||||
| `src/voice/voiceModeEnabled.ts` | 54 | 三层门控逻辑 |
|
||||
| `src/hooks/useVoice.ts` | — | React hook(录音状态 + WebSocket) |
|
||||
| `src/services/voiceStreamSTT.ts` | — | STT WebSocket 流式传输 |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# WEB_BROWSER_TOOL — 浏览器工具
|
||||
|
||||
> Feature Flag: `FEATURE_WEB_BROWSER_TOOL=1`
|
||||
> 实现状态:核心实现缺失,面板为 Stub,布线完整
|
||||
> 实现状态:核心工具已实现,面板为 Stub,布线完整
|
||||
> 引用数:4
|
||||
|
||||
## 一、功能概述
|
||||
@@ -14,8 +14,8 @@ WEB_BROWSER_TOOL 让模型可以启动浏览器实例、导航网页、与页面
|
||||
|
||||
| 模块 | 文件 | 状态 |
|
||||
|------|------|------|
|
||||
| 浏览器面板 | `src/tools/WebBrowserTool/WebBrowserPanel.ts` | **Stub** — 返回 null |
|
||||
| 浏览器工具 | `src/tools/WebBrowserTool/WebBrowserTool.ts` | **缺失** |
|
||||
| 浏览器面板 | `packages/builtin-tools/src/tools/WebBrowserTool/WebBrowserPanel.ts` | **Stub** — 返回 null |
|
||||
| 浏览器工具 | `packages/builtin-tools/src/tools/WebBrowserTool/WebBrowserTool.ts` | **已实现** |
|
||||
| REPL 集成 | `src/screens/REPL.tsx` | **布线** — 渲染 WebBrowserPanel |
|
||||
| 工具注册 | `src/tools.ts` | **布线** — 动态加载 |
|
||||
| WebView 检测 | `src/main.tsx` | **布线** — `'WebView' in Bun` 检测 |
|
||||
@@ -44,8 +44,8 @@ WebBrowserPanel 在 REPL 侧边显示浏览器状态
|
||||
|
||||
| 模块 | 工作量 | 说明 |
|
||||
|------|--------|------|
|
||||
| `WebBrowserTool.ts` | 大 | 工具 schema + Bun WebView API 执行 |
|
||||
| `WebBrowserPanel.tsx` | 中 | REPL 侧边栏浏览器状态面板 |
|
||||
| `WebBrowserTool.ts` | ✅ 已实现 | 工具 schema + Bun WebView API 执行 |
|
||||
| `WebBrowserPanel.tsx` | 中 | REPL 侧边栏浏览器状态面板(仍为 Stub) |
|
||||
|
||||
## 四、关键设计决策
|
||||
|
||||
@@ -63,7 +63,7 @@ FEATURE_WEB_BROWSER_TOOL=1 bun run dev
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/tools/WebBrowserTool/WebBrowserPanel.ts` | 面板组件(stub) |
|
||||
| `src/tools/WebBrowserTool/WebBrowserTool.ts` | 工具实现(缺失) |
|
||||
| `src/screens/REPL.tsx:273,4582` | 面板渲染 |
|
||||
| `packages/builtin-tools/src/tools/WebBrowserTool/WebBrowserPanel.ts` | 面板组件(stub) |
|
||||
| `packages/builtin-tools/src/tools/WebBrowserTool/WebBrowserTool.ts` | 工具实现(已实现) |
|
||||
| `src/screens/REPL.tsx:471,5676` | 面板渲染 |
|
||||
| `src/tools.ts:115-116` | 工具注册 |
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# WEB_SEARCH_TOOL — 网页搜索工具
|
||||
|
||||
> 实现状态:适配器架构完成,Bing 适配器为当前默认后端
|
||||
> 实现状态:适配器架构完成,支持 API / Bing / Brave 三种后端
|
||||
> 引用数:核心工具,无 feature flag 门控(始终启用)
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
WebSearchTool 让模型可以搜索互联网获取最新信息。原始实现仅支持 Anthropic API 服务端搜索(`web_search_20250305` server tool),在第三方代理端点下不可用。现已重构为适配器架构,新增 Bing 搜索页面解析作为 fallback,确保任何 API 端点都能使用搜索功能。
|
||||
WebSearchTool 让模型可以搜索互联网获取最新信息。原始实现仅支持 Anthropic API 服务端搜索(`web_search_20250305` server tool),在第三方代理端点下不可用。现已重构为适配器架构,支持 API 服务端搜索,以及 Bing / Brave 两个 HTML 解析后端,确保任何 API 端点都能使用搜索功能。
|
||||
|
||||
## 二、实现架构
|
||||
|
||||
@@ -21,24 +21,29 @@ WebSearchTool.call()
|
||||
│ └── 使用 web_search_20250305 server tool
|
||||
│ 通过 queryModelWithStreaming 二次调用 API
|
||||
│
|
||||
└── BingSearchAdapter — Bing HTML 抓取 + 正则提取(当前默认)
|
||||
└── 直接抓取 Bing 搜索页 HTML
|
||||
正则提取 b_algo 块中的标题/URL/摘要
|
||||
├── BingSearchAdapter — Bing HTML 抓取 + 正则提取
|
||||
│ └── 直接抓取 Bing 搜索页 HTML
|
||||
│ 正则提取 b_algo 块中的标题/URL/摘要
|
||||
│
|
||||
└── BraveSearchAdapter — Brave LLM Context API
|
||||
└── 调用 Brave HTTPS GET 接口
|
||||
将 grounding payload 映射为标题/URL/摘要
|
||||
```
|
||||
|
||||
### 2.2 模块结构
|
||||
|
||||
| 模块 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 工具入口 | `src/tools/WebSearchTool/WebSearchTool.ts` | `buildTool()` 定义:schema、权限、执行、输出格式化 |
|
||||
| 工具 prompt | `src/tools/WebSearchTool/prompt.ts` | 搜索工具的系统提示词 |
|
||||
| UI 渲染 | `src/tools/WebSearchTool/UI.tsx` | 搜索结果的终端渲染组件 |
|
||||
| 适配器接口 | `src/tools/WebSearchTool/adapters/types.ts` | `WebSearchAdapter` 接口、`SearchResult`/`SearchOptions`/`SearchProgress` 类型 |
|
||||
| 适配器工厂 | `src/tools/WebSearchTool/adapters/index.ts` | `createAdapter()` 工厂函数,选择后端 |
|
||||
| API 适配器 | `src/tools/WebSearchTool/adapters/apiAdapter.ts` | 封装原有 `queryModelWithStreaming` 逻辑,使用 server tool |
|
||||
| Bing 适配器 | `src/tools/WebSearchTool/adapters/bingAdapter.ts` | Bing HTML 抓取 + 正则解析 |
|
||||
| 单元测试 | `src/tools/WebSearchTool/__tests__/bingAdapter.test.ts` | 32 个测试用例 |
|
||||
| 集成测试 | `src/tools/WebSearchTool/__tests__/bingAdapter.integration.ts` | 真实网络请求验证 |
|
||||
| 工具入口 | `packages/builtin-tools/src/tools/WebSearchTool/WebSearchTool.ts` | `buildTool()` 定义:schema、权限、执行、输出格式化 |
|
||||
| 工具 prompt | `packages/builtin-tools/src/tools/WebSearchTool/prompt.ts` | 搜索工具的系统提示词 |
|
||||
| UI 渲染 | `packages/builtin-tools/src/tools/WebSearchTool/UI.tsx` | 搜索结果的终端渲染组件 |
|
||||
| 适配器接口 | `packages/builtin-tools/src/tools/WebSearchTool/adapters/types.ts` | `WebSearchAdapter` 接口、`SearchResult`/`SearchOptions`/`SearchProgress` 类型 |
|
||||
| 适配器工厂 | `packages/builtin-tools/src/tools/WebSearchTool/adapters/index.ts` | `createAdapter()` 工厂函数,选择后端 |
|
||||
| API 适配器 | `packages/builtin-tools/src/tools/WebSearchTool/adapters/apiAdapter.ts` | 封装原有 `queryModelWithStreaming` 逻辑,使用 server tool |
|
||||
| Bing 适配器 | `packages/builtin-tools/src/tools/WebSearchTool/adapters/bingAdapter.ts` | Bing HTML 抓取 + 正则解析 |
|
||||
| Brave 适配器 | `packages/builtin-tools/src/tools/WebSearchTool/adapters/braveAdapter.ts` | Brave LLM Context API 适配与结果映射 |
|
||||
| 单元测试 | `packages/builtin-tools/src/tools/WebSearchTool/__tests__/bingAdapter.test.ts`, `packages/builtin-tools/src/tools/WebSearchTool/__tests__/braveAdapter*.test.ts`, `packages/builtin-tools/src/tools/WebSearchTool/__tests__/adapterFactory.test.ts` | Bing / Brave 解析与工厂逻辑测试 |
|
||||
| 集成测试 | `packages/builtin-tools/src/tools/WebSearchTool/__tests__/bingAdapter.integration.ts`, `packages/builtin-tools/src/tools/WebSearchTool/__tests__/braveAdapter.integration.ts` | 真实网络请求验证 |
|
||||
|
||||
### 2.3 数据流
|
||||
|
||||
@@ -49,20 +54,18 @@ WebSearchTool.call()
|
||||
validateInput() — 校验 query 非空、allowed/block 不共存
|
||||
│
|
||||
▼
|
||||
createAdapter() → BingSearchAdapter(当前硬编码)
|
||||
createAdapter() → ApiSearchAdapter | BingSearchAdapter | BraveSearchAdapter
|
||||
│
|
||||
▼
|
||||
adapter.search(query, { allowedDomains, blockedDomains, signal, onProgress })
|
||||
│
|
||||
├── onProgress({ type: 'query_update', query })
|
||||
│
|
||||
├── axios.get(bing.com/search?q=...&setmkt=en-US)
|
||||
│ └── 13 个 Edge 浏览器请求头
|
||||
├── axios.get(search-engine-url)
|
||||
│ └── API 鉴权请求头
|
||||
│
|
||||
├── extractBingResults(html) — 正则提取 <li class="b_algo"> 块
|
||||
│ ├── resolveBingUrl() — 解码 base64 重定向 URL
|
||||
│ ├── extractSnippet() — 三级降级摘要提取
|
||||
│ └── decodeHtmlEntities() — he.decode
|
||||
├── extractResults(payload) — 按后端提取结果
|
||||
│ └── grounding → SearchResult[] 映射
|
||||
│
|
||||
├── 客户端域名过滤 (allowedDomains / blockedDomains)
|
||||
│
|
||||
@@ -117,19 +120,18 @@ Bing 返回的重定向 URL 格式:`bing.com/ck/a?...&u=a1aHR0cHM6Ly9...`
|
||||
|
||||
## 四、适配器选择逻辑
|
||||
|
||||
当前 `createAdapter()` 硬编码返回 `BingSearchAdapter`,原逻辑已注释保留:
|
||||
`createAdapter()` 按以下优先级选择后端,并按选中的后端 key 缓存适配器实例:
|
||||
|
||||
```typescript
|
||||
export function createAdapter(): WebSearchAdapter {
|
||||
return new BingSearchAdapter()
|
||||
// 注释保留的选择逻辑:
|
||||
// 1. WEB_SEARCH_ADAPTER 环境变量强制指定 api|bing
|
||||
// 2. isFirstPartyAnthropicBaseUrl() → API 适配器
|
||||
// 3. 第三方端点 → Bing 适配器
|
||||
// 1. WEB_SEARCH_ADAPTER=api|bing|brave 显式指定
|
||||
// 2. Anthropic 官方 API Base URL → ApiSearchAdapter
|
||||
// 3. 第三方代理 / 非官方端点 → BingSearchAdapter
|
||||
}
|
||||
```
|
||||
|
||||
恢复自动选择:取消 `index.ts` 中的注释即可。
|
||||
显式指定 `WEB_SEARCH_ADAPTER=brave` 时,会改用 Brave LLM Context API 后端,并要求
|
||||
`BRAVE_SEARCH_API_KEY` 或 `BRAVE_API_KEY`。
|
||||
|
||||
## 五、接口定义
|
||||
|
||||
@@ -174,13 +176,13 @@ interface SearchProgress {
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/tools/WebSearchTool/WebSearchTool.ts` | 工具定义入口 |
|
||||
| `src/tools/WebSearchTool/prompt.ts` | 搜索工具 prompt |
|
||||
| `src/tools/WebSearchTool/UI.tsx` | 终端 UI 渲染 |
|
||||
| `src/tools/WebSearchTool/adapters/types.ts` | 适配器接口 |
|
||||
| `src/tools/WebSearchTool/adapters/index.ts` | 适配器工厂 |
|
||||
| `src/tools/WebSearchTool/adapters/apiAdapter.ts` | API 服务端搜索适配器 |
|
||||
| `src/tools/WebSearchTool/adapters/bingAdapter.ts` | Bing HTML 解析适配器 |
|
||||
| `src/tools/WebSearchTool/__tests__/bingAdapter.test.ts` | 单元测试 (32 cases) |
|
||||
| `src/tools/WebSearchTool/__tests__/bingAdapter.integration.ts` | 集成测试 |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/WebSearchTool.ts` | 工具定义入口 |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/prompt.ts` | 搜索工具 prompt |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/UI.tsx` | 终端 UI 渲染 |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/adapters/types.ts` | 适配器接口 |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/adapters/index.ts` | 适配器工厂 |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/adapters/apiAdapter.ts` | API 服务端搜索适配器 |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/adapters/bingAdapter.ts` | Bing HTML 解析适配器 |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/__tests__/bingAdapter.test.ts` | 单元测试 (32 cases) |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/__tests__/bingAdapter.integration.ts` | 集成测试 |
|
||||
| `src/tools.ts` | 工具注册 |
|
||||
|
||||
@@ -14,17 +14,17 @@ WORKFLOW_SCRIPTS 实现基于文件的多步自动化工作流。用户可以定
|
||||
|
||||
| 模块 | 文件 | 状态 |
|
||||
|------|------|------|
|
||||
| WorkflowTool | `src/tools/WorkflowTool/WorkflowTool.ts` | **Stub** — 空对象 |
|
||||
| Workflow 权限 | `src/tools/WorkflowTool/WorkflowPermissionRequest.ts` | **Stub** — 返回 null |
|
||||
| 常量 | `src/tools/WorkflowTool/constants.ts` | **Stub** — 空工具名 |
|
||||
| 命令创建 | `src/tools/WorkflowTool/createWorkflowCommand.ts` | **Stub** — 空操作 |
|
||||
| 捆绑工作流 | `src/tools/WorkflowTool/bundled/` | **缺失** — 目录不存在 |
|
||||
| WorkflowTool | `packages/builtin-tools/src/tools/WorkflowTool/WorkflowTool.ts` | **部分实现** — tool schema + 渲染完整,call 返回运行时缺失提示 |
|
||||
| Workflow 权限 | `packages/builtin-tools/src/tools/WorkflowTool/WorkflowPermissionRequest.tsx` | **部分实现** — 权限请求组件 |
|
||||
| 常量 | `packages/builtin-tools/src/tools/WorkflowTool/constants.ts` | **实现** — 工具名 + 目录名 + 文件扩展名常量 |
|
||||
| 命令创建 | `packages/builtin-tools/src/tools/WorkflowTool/createWorkflowCommand.ts` | **实现** — 扫描 .claude/workflows/ 目录创建 Command 对象 |
|
||||
| 捆绑工作流 | `packages/builtin-tools/src/tools/WorkflowTool/bundled/index.ts` | **实现** — 内置工作流初始化 |
|
||||
| 本地工作流任务 | `src/tasks/LocalWorkflowTask/LocalWorkflowTask.ts` | **Stub** — 类型 + 空操作 |
|
||||
| UI 任务组件 | `src/components/tasks/src/tasks/LocalWorkflowTask/` | **Stub** — 空导出 |
|
||||
| 详情对话框 | `src/components/tasks/WorkflowDetailDialog.ts` | **Stub** — 返回 null |
|
||||
| 任务注册 | `src/tasks.ts` | **布线** — 动态加载 |
|
||||
| 工具注册 | `src/tools.ts` | **布线** — 包含 bundled 工作流初始化 |
|
||||
| 命令注册 | `src/commands.ts` | **布线** — `/workflows` 命令 |
|
||||
| 工具注册 | `src/tools.ts` | **布线** — 动态加载 + bundled 工作流初始化 (行 131-134,235) |
|
||||
| 命令注册 | `src/commands.ts` | **布线** — `/workflows` 命令 (行 93-95,395,460) |
|
||||
|
||||
### 2.2 预期数据流
|
||||
|
||||
@@ -69,13 +69,9 @@ steps:
|
||||
|
||||
| 优先级 | 模块 | 工作量 | 说明 |
|
||||
|--------|------|--------|------|
|
||||
| 1 | `WorkflowTool.ts` | 大 | Schema 定义 + 多步执行引擎 |
|
||||
| 2 | `bundled/index.js` | 中 | 内置工作流定义(initBundledWorkflows) |
|
||||
| 3 | `createWorkflowCommand.ts` | 中 | 从文件解析创建命令对象 |
|
||||
| 4 | `LocalWorkflowTask.ts` | 大 | 步骤协调、kill/skip/retry |
|
||||
| 5 | `WorkflowDetailDialog.ts` | 中 | 进度详情 UI |
|
||||
| 6 | `WorkflowPermissionRequest.ts` | 小 | 权限对话框 |
|
||||
| 7 | `constants.ts` | 小 | 工具名常量 |
|
||||
| 1 | `WorkflowTool.ts` call 方法 | 中 | 实际工作流执行逻辑(当前返回运行时缺失提示) |
|
||||
| 2 | `LocalWorkflowTask.ts` | 大 | 步骤协调、kill/skip/retry |
|
||||
| 3 | `WorkflowDetailDialog.ts` | 中 | 进度详情 UI |
|
||||
|
||||
## 四、关键设计决策
|
||||
|
||||
@@ -95,11 +91,12 @@ FEATURE_WORKFLOW_SCRIPTS=1 bun run dev
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/tools/WorkflowTool/WorkflowTool.ts` | 工具定义(stub) |
|
||||
| `src/tools/WorkflowTool/WorkflowPermissionRequest.ts` | 权限对话框(stub) |
|
||||
| `src/tools/WorkflowTool/constants.ts` | 常量(stub) |
|
||||
| `src/tools/WorkflowTool/createWorkflowCommand.ts` | 命令创建(stub) |
|
||||
| `packages/builtin-tools/src/tools/WorkflowTool/WorkflowTool.ts` | 工具定义(部分实现) |
|
||||
| `packages/builtin-tools/src/tools/WorkflowTool/WorkflowPermissionRequest.tsx` | 权限请求组件 |
|
||||
| `packages/builtin-tools/src/tools/WorkflowTool/constants.ts` | 常量定义 |
|
||||
| `packages/builtin-tools/src/tools/WorkflowTool/createWorkflowCommand.ts` | 命令创建(已实现) |
|
||||
| `packages/builtin-tools/src/tools/WorkflowTool/bundled/index.ts` | 内置工作流初始化 |
|
||||
| `src/tasks/LocalWorkflowTask/LocalWorkflowTask.ts` | 任务协调(stub) |
|
||||
| `src/components/tasks/WorkflowDetailDialog.ts` | 详情对话框(stub) |
|
||||
| `src/tools.ts:127-132` | 工具注册 |
|
||||
| `src/commands.ts:86-89` | 命令注册 |
|
||||
| `src/tools.ts:131-134,235` | 工具注册 |
|
||||
| `src/commands.ts:93-95,395,460` | 命令注册 |
|
||||
|
||||
@@ -17,7 +17,7 @@ keywords: ["Ant 特权", "USER_TYPE", "身份门控", "内部功能", "Anthropic
|
||||
|
||||
`BUILD_TARGET` 等构建时常量在反编译版本中已被移除。`USER_TYPE` 通过 Bun 的 `--define` 或环境变量注入,Bun 会进行**常量折叠**——所有 `process.env.USER_TYPE === 'ant'` 在外部构建中直接变为 `false`,后续代码被 DCE 移除。但在反编译版本中,这些代码保留完整。
|
||||
|
||||
`USER_TYPE === 'ant'` 在代码库中出现 **377+ 次**(含 `=== 'ant'` 291 次、`(process.env.USER_TYPE) === 'ant'` 86 次),另有 `!== 'ant'` 53 次、其他引用约 35 次,总计 **465 处引用**,控制着工具、命令、API、UI 等方方面面。
|
||||
`USER_TYPE === 'ant'` 在代码库中出现 **351+ 次**(跨 163 个文件),另有 `!== 'ant'` 59 次(跨 38 个文件),总计 **410+ 处引用**,控制着工具、命令、API、UI 等方方面面。
|
||||
|
||||
## Ant-Only 工具
|
||||
|
||||
@@ -25,10 +25,10 @@ keywords: ["Ant 特权", "USER_TYPE", "身份门控", "内部功能", "Anthropic
|
||||
|
||||
| 工具 | 代码位置 | 用途 |
|
||||
|------|---------|------|
|
||||
| **REPLTool** | `src/tools/REPLTool/` | 高级 REPL 模式——在 VM 中包装 Bash/Read/Edit/Glob/Grep/Agent 等工具 |
|
||||
| **SuggestBackgroundPRTool** | `src/tools/SuggestBackgroundPRTool/` | 建议在后台创建 PR |
|
||||
| **ConfigTool** | `src/tools/ConfigTool/` | 交互式配置编辑器,包含 Gates 标签页用于覆盖 GrowthBook flags |
|
||||
| **TungstenTool** | `src/tools/TungstenTool/` | 基于 tmux 的终端面板工具(反编译版中已 stub) |
|
||||
| **REPLTool** | `packages/builtin-tools/src/tools/REPLTool/` | 高级 REPL 模式——在 VM 中包装 Bash/Read/Edit/Glob/Grep/Agent 等工具 |
|
||||
| **SuggestBackgroundPRTool** | `packages/builtin-tools/src/tools/SuggestBackgroundPRTool/` | 建议在后台创建 PR |
|
||||
| **ConfigTool** | `packages/builtin-tools/src/tools/ConfigTool/` | 交互式配置编辑器,包含 Gates 标签页用于覆盖 GrowthBook flags |
|
||||
| **TungstenTool** | `packages/builtin-tools/src/tools/TungstenTool/` | 基于 tmux 的终端面板工具(反编译版中已 stub) |
|
||||
|
||||
```typescript
|
||||
// src/tools.ts 第 14-24 行——条件导入 + Dead Code Elimination 标记
|
||||
@@ -36,18 +36,18 @@ keywords: ["Ant 特权", "USER_TYPE", "身份门控", "内部功能", "Anthropic
|
||||
/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
|
||||
const REPLTool =
|
||||
process.env.USER_TYPE === 'ant'
|
||||
? require('./tools/REPLTool/REPLTool.js').REPLTool
|
||||
? require('@claude-code-best/builtin-tools/tools/REPLTool/REPLTool.js').REPLTool
|
||||
: null
|
||||
const SuggestBackgroundPRTool =
|
||||
process.env.USER_TYPE === 'ant'
|
||||
? require('./tools/SuggestBackgroundPRTool/SuggestBackgroundPRTool.js')
|
||||
? require('@claude-code-best/builtin-tools/tools/SuggestBackgroundPRTool/SuggestBackgroundPRTool.js')
|
||||
.SuggestBackgroundPRTool
|
||||
: null
|
||||
```
|
||||
|
||||
## Ant-Only 命令
|
||||
|
||||
`src/commands.ts` 注册了 **28** 个仅在内部构建中可用的斜杠命令(`INTERNAL_ONLY_COMMANDS`,lines 225-254),在 `USER_TYPE === 'ant' && !IS_DEMO` 时才加载(line 343-345):
|
||||
`src/commands.ts` 注册了 **24+** 个仅在内部构建中可用的斜杠命令(`INTERNAL_ONLY_COMMANDS`,lines 267-295),在 `USER_TYPE === 'ant' && !IS_DEMO` 时才加载(line 400-401):
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="调试类">
|
||||
@@ -74,7 +74,7 @@ const SuggestBackgroundPRTool =
|
||||
- `summary` — 生成摘要
|
||||
- `subscribePr` — 订阅 PR(需要 `KAIROS_GITHUB_WEBHOOKS` feature flag)
|
||||
- `forceSnip` — 强制截断历史(需要 `HISTORY_SNIP` feature flag)
|
||||
- `ultraplan` — 超级规划(需要 `ULTRAPLAN` feature flag)
|
||||
- `ultraplan` — 超级规划(需要 `ULTRAPLAN` feature flag,单独注册于 `commands.ts:396`)
|
||||
</Accordion>
|
||||
<Accordion title="基础设施类">
|
||||
- `backfillSessions` — 回填会话数据
|
||||
|
||||
@@ -15,17 +15,19 @@ Claude Code 使用 Bun 打包器的 `bun:bundle` 模块提供编译时特性门
|
||||
import { feature } from 'bun:bundle'
|
||||
|
||||
const SleepTool = feature('PROACTIVE') || feature('KAIROS')
|
||||
? require('./tools/SleepTool/SleepTool.js').SleepTool
|
||||
? require('@claude-code-best/builtin-tools/tools/SleepTool/SleepTool.js').SleepTool
|
||||
: null
|
||||
```
|
||||
|
||||
在 Anthropic 的内部构建中,`feature()` 在打包时被求值——返回 `true` 的代码会被保留,返回 `false` 的代码会被 **Dead Code Elimination (DCE)** 彻底移除。
|
||||
|
||||
在我们的反编译版本中,这个函数被兜底为:
|
||||
在我们的反编译版本中,`feature` 从 `bun:bundle` 导入(声明在 `src/types/internal-modules.d.ts`),在运行时始终返回 `false`:
|
||||
|
||||
```typescript
|
||||
// src/entrypoints/cli.tsx 第 3 行
|
||||
const feature = (_name: string) => false;
|
||||
// src/types/internal-modules.d.ts
|
||||
declare module 'bun:bundle' {
|
||||
export function feature(name: string): boolean;
|
||||
}
|
||||
```
|
||||
|
||||
这意味着所有 88+ 个 feature flag 后的代码**在运行时永远不会执行**,但代码本身完整保留,可以阅读和分析。
|
||||
@@ -79,7 +81,7 @@ Feature flags 在代码中主要有三种使用模式:
|
||||
```typescript
|
||||
// src/tools.ts — 最常见的模式
|
||||
const MonitorTool = feature('MONITOR_TOOL')
|
||||
? require('./tools/MonitorTool/MonitorTool.js').MonitorTool
|
||||
? require('@claude-code-best/builtin-tools/tools/MonitorTool/MonitorTool.js').MonitorTool
|
||||
: null
|
||||
```
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ keywords: ["GrowthBook", "A/B 测试", "运行时门控", "tengu", "渐进式发
|
||||
|
||||
## 集成架构
|
||||
|
||||
GrowthBook 的完整实现位于 `src/services/analytics/growthbook.ts`(1156 行),工作流程如下:
|
||||
GrowthBook 的完整实现位于 `src/services/analytics/growthbook.ts`(1258 行),工作流程如下:
|
||||
|
||||
<Steps>
|
||||
<Step title="启动时获取远程配置">
|
||||
|
||||
@@ -84,7 +84,7 @@ keywords: ["隐藏功能", "未公开功能", "秘密功能", "Claude Code 彩
|
||||
<Accordion title="VOICE_MODE:语音交互">
|
||||
**门控**: `feature('VOICE_MODE')`
|
||||
|
||||
代码中存在语音输入模式的注册点,但核心实现依赖于 `audio-napi` 包(在反编译版本中已 stub):
|
||||
代码中存在语音输入模式的注册点,核心实现依赖 `audio-capture-napi` 包(已恢复):
|
||||
|
||||
- 通过 `/voice` 命令激活
|
||||
- "按住说话"(hold-to-talk)交互模式
|
||||
|
||||
@@ -64,24 +64,27 @@ Claude Code 从上到下分为五个层次,每一层职责清晰、边界分
|
||||
needsFollowUp ? continue : return { reason }
|
||||
```
|
||||
|
||||
完整的状态机通过 `State` 类型(`src/query.ts:204`)在迭代间传递,包含 10 个字段(messages、autoCompactTracking、maxOutputTokensRecoveryCount 等)。
|
||||
完整的状态机通过 `State` 类型(`src/query.ts:207`)在迭代间传递,包含 10 个字段(messages、autoCompactTracking、maxOutputTokensRecoveryCount 等)。
|
||||
|
||||
### 4. 工具层(`src/tools.ts` → `src/Tool.ts`)
|
||||
|
||||
`getAllBaseTools()`(`src/tools.ts:191`)组装 50+ 工具列表,经过 `filterToolsByDenyRules()` 权限过滤后传给 API。
|
||||
`getAllBaseTools()`(`src/tools.ts:195`)组装 50+ 工具列表,经过 `filterToolsByDenyRules()` 权限过滤后传给 API。
|
||||
|
||||
每个工具实现 `Tool<Input, Output, Progress>` 接口(`src/Tool.ts:362`),核心方法链:
|
||||
每个工具实现 `Tool<Input, Output, Progress>` 接口(`src/Tool.ts:368`),核心方法链:
|
||||
```
|
||||
validateInput() → canUseTool()(UI 层)→ checkPermissions() → call() → ToolResult
|
||||
```
|
||||
|
||||
### 5. 通信层(`src/services/api/claude.ts`)
|
||||
|
||||
API 客户端支持 4 种 Provider:
|
||||
- **Anthropic Direct**:默认
|
||||
API 客户端支持 7 种 Provider:
|
||||
- **Anthropic Direct (firstParty)**:默认
|
||||
- **AWS Bedrock**:`ANTHROPIC_BEDROCK_BASE_URL`
|
||||
- **Google Vertex**:`ANTHROPIC_VERTEX_PROJECT_ID`
|
||||
- **Azure**:通过自定义 base URL
|
||||
- **Foundry**:`ANTHROPIC_CODE_USE_FOUNDRY`
|
||||
- **OpenAI**:兼容层
|
||||
- **Gemini**:兼容层
|
||||
- **Grok (xAI)**:兼容层
|
||||
|
||||
`deps.callModel()` 发起流式请求,返回 `BetaRawMessageStreamEvent` 事件流。支持 Prompt Cache(`cache_control`)、thinking blocks、multi-turn tool use。
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ Claude Code 是一个**运行在本地终端中的 agentic coding system**。它
|
||||
│ 实际执行: 读文件、运行命令、搜索代码... │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 6. 通信层 (claude.ts → Anthropic API) │
|
||||
│ 流式 HTTP, 支持 Bedrock/Vertex/Azure 多 provider │
|
||||
│ 流式 HTTP, 支持 Bedrock/Vertex/Foundry 等 7 种 provider │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ AI 没有真正的"记忆",Claude Code 通过精心分层营造了这个幻觉
|
||||
|
||||
### 3. 工具系统的权限双轨制
|
||||
|
||||
`src/tools/BashTool/shouldUseSandbox.ts` 展示了一个精巧的双重安全模型:
|
||||
`packages/builtin-tools/src/tools/BashTool/shouldUseSandbox.ts` 展示了一个精巧的双重安全模型:
|
||||
|
||||
- **应用层**:权限规则决定"能不能执行"(白名单/黑名单/用户确认)
|
||||
- **OS 层**:沙箱决定"执行时能做什么"(文件系统/网络/进程隔离)
|
||||
|
||||
@@ -65,7 +65,7 @@ ENABLE_LSP_TOOL=1 bun run dev
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ LSP Tool │
|
||||
│ src/tools/LSPTool/LSPTool.ts │
|
||||
│ packages/builtin-tools/src/tools/LSPTool/LSPTool.ts│
|
||||
│ (Claude 可调用的工具,9 种操作) │
|
||||
└──────────────────────┬──────────────────────────────┘
|
||||
│
|
||||
@@ -128,10 +128,10 @@ LSP 服务器会异步推送 `textDocument/publishDiagnostics` 通知,经去
|
||||
| `src/services/lsp/config.ts` | 从插件加载 LSP 服务器配置 |
|
||||
| `src/services/lsp/LSPDiagnosticRegistry.ts` | 诊断信息注册、去重、容量限制 |
|
||||
| `src/services/lsp/passiveFeedback.ts` | 注册 `publishDiagnostics` 通知处理器 |
|
||||
| `src/tools/LSPTool/LSPTool.ts` | LSP Tool 实现(暴露给 Claude) |
|
||||
| `src/tools/LSPTool/schemas.ts` | 输入 schema(9 种操作的 discriminated union) |
|
||||
| `src/tools/LSPTool/formatters.ts` | 各操作结果的格式化 |
|
||||
| `src/tools/LSPTool/prompt.ts` | Tool 描述文本 |
|
||||
| `packages/builtin-tools/src/tools/LSPTool/LSPTool.ts` | LSP Tool 实现(暴露给 Claude) |
|
||||
| `packages/builtin-tools/src/tools/LSPTool/schemas.ts` | 输入 schema(9 种操作的 discriminated union) |
|
||||
| `packages/builtin-tools/src/tools/LSPTool/formatters.ts` | 各操作结果的格式化 |
|
||||
| `packages/builtin-tools/src/tools/LSPTool/prompt.ts` | Tool 描述文本 |
|
||||
| `src/utils/plugins/lspPluginIntegration.ts` | 从插件加载、验证、环境变量解析、作用域管理 |
|
||||
|
||||
## LSP Tool 支持的操作
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
# OpenAI兼容模型中task工具使用指南
|
||||
|
||||
## 问题描述
|
||||
|
||||
当使用OpenAI兼容模型(如DeepSeek、Ollama、vLLM等)时,调用task工具(TaskGet、TaskCreate、TaskUpdate、TaskList)可能会出现以下错误:
|
||||
|
||||
```
|
||||
Error: InputValidationError: TaskGet failed due to the following issues:
|
||||
The required parameter `taskId` is missing
|
||||
An unexpected parameter `task_id` was provided
|
||||
|
||||
This tool's schema was not sent to the API — it was not in the discovered-tool set derived from message history. Without the schema in your prompt, typed parameters (arrays, numbers, booleans) get emitted as strings and the client-side parser rejects them. Load the tool first: call ToolSearch with query "select:TaskGet", then retry this call.
|
||||
```
|
||||
|
||||
## 问题原因
|
||||
|
||||
### 1. 延迟加载工具(Deferred Tools)
|
||||
task工具都是延迟加载的(`shouldDefer: true`),这意味着:
|
||||
- 工具的模式(schema)不会在初始API调用中发送
|
||||
- 需要先通过`ToolSearch`工具发现
|
||||
- 只有在被发现后,工具模式才会被发送给API
|
||||
|
||||
### 2. 参数名转换问题
|
||||
- task工具使用驼峰命名:`taskId`
|
||||
- OpenAI兼容模型可能输出蛇形命名:`task_id`
|
||||
- 当工具模式没有被发送时,模型会猜测参数名,可能导致不匹配
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案1:先使用ToolSearch(推荐)
|
||||
在使用task工具之前,先调用`ToolSearch`工具:
|
||||
|
||||
```javascript
|
||||
// 第一步:发现task工具
|
||||
ToolSearch("select:TaskGet,TaskCreate,TaskUpdate,TaskList")
|
||||
|
||||
// 第二步:正常使用task工具
|
||||
TaskCreate({ subject: "任务标题", description: "任务描述" })
|
||||
TaskGet({ taskId: "1" })
|
||||
TaskUpdate({ taskId: "1", status: "completed" })
|
||||
TaskList()
|
||||
```
|
||||
|
||||
### 方案2:批量发现所有task工具
|
||||
```javascript
|
||||
// 一次性发现所有task工具
|
||||
ToolSearch("select:TaskGet,TaskCreate,TaskUpdate,TaskList")
|
||||
|
||||
// 然后可以任意使用task工具
|
||||
const task = await TaskCreate({ subject: "新任务", description: "任务描述" })
|
||||
console.log(`创建的任务ID: ${task.id}`)
|
||||
|
||||
const taskList = await TaskList()
|
||||
console.log(`当前有 ${taskList.tasks.length} 个任务`)
|
||||
```
|
||||
|
||||
### 方案3:单独发现特定工具
|
||||
```javascript
|
||||
// 只发现需要的工具
|
||||
ToolSearch("select:TaskGet")
|
||||
|
||||
// 然后使用该工具
|
||||
TaskGet({ taskId: "1" })
|
||||
```
|
||||
|
||||
## 参数名注意事项
|
||||
|
||||
在使用OpenAI兼容模型时,请注意参数名格式:
|
||||
|
||||
### ✅ 正确(驼峰命名)
|
||||
```javascript
|
||||
TaskGet({ taskId: "1" })
|
||||
TaskCreate({ subject: "标题", description: "描述" })
|
||||
TaskUpdate({ taskId: "1", status: "completed" })
|
||||
```
|
||||
|
||||
### ❌ 错误(蛇形命名)
|
||||
```javascript
|
||||
TaskGet({ task_id: "1" }) // 错误:应该使用taskId
|
||||
TaskCreate({ subject: "标题", description: "描述" }) // 正确
|
||||
TaskUpdate({ task_id: "1", status: "completed" }) // 错误:应该使用taskId
|
||||
```
|
||||
|
||||
## 常见问题解答
|
||||
|
||||
### Q1: 为什么需要先使用ToolSearch?
|
||||
A: task工具是延迟加载的,它们的模式只有在被`ToolSearch`工具发现后才会发送给API。没有工具模式,模型无法知道正确的参数名和类型。
|
||||
|
||||
### Q2: 每次会话都需要使用ToolSearch吗?
|
||||
A: 是的,每次新的会话都需要先使用ToolSearch发现工具。工具发现状态不会在会话之间保留。
|
||||
|
||||
### Q3: 使用Anthropic官方模型也需要这样吗?
|
||||
A: 通常不需要。Anthropic官方模型对延迟加载工具的处理更智能,但为了兼容性,建议在使用task工具前都先使用ToolSearch。
|
||||
|
||||
### Q4: 可以一次性发现所有工具吗?
|
||||
A: 可以,使用`ToolSearch("select:TaskGet,TaskCreate,TaskUpdate,TaskList")`可以一次性发现所有task工具。
|
||||
|
||||
### Q5: 如果忘记使用ToolSearch会怎样?
|
||||
A: 会收到参数验证错误,提示需要先使用ToolSearch。按照错误信息的指导操作即可。
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **会话开始时发现工具**:在开始使用task工具前,先调用ToolSearch
|
||||
2. **批量发现**:一次性发现所有需要的task工具
|
||||
3. **检查参数名**:确保使用正确的驼峰命名参数
|
||||
4. **查看错误信息**:如果遇到错误,仔细阅读错误信息中的指导
|
||||
|
||||
## 示例工作流
|
||||
|
||||
```javascript
|
||||
// 1. 开始新会话
|
||||
// 2. 发现task工具
|
||||
ToolSearch("select:TaskGet,TaskCreate,TaskUpdate,TaskList")
|
||||
|
||||
// 3. 创建任务
|
||||
const newTask = await TaskCreate({
|
||||
subject: "修复OpenAI兼容性问题",
|
||||
description: "解决task工具在OpenAI兼容模型下的参数名问题"
|
||||
})
|
||||
|
||||
// 4. 获取任务详情
|
||||
const taskDetails = await TaskGet({ taskId: newTask.id })
|
||||
|
||||
// 5. 更新任务状态
|
||||
await TaskUpdate({
|
||||
taskId: newTask.id,
|
||||
status: "in_progress",
|
||||
activeForm: "修复OpenAI兼容性问题"
|
||||
})
|
||||
|
||||
// 6. 查看所有任务
|
||||
const allTasks = await TaskList()
|
||||
console.log(`当前有 ${allTasks.tasks.length} 个任务`)
|
||||
|
||||
// 7. 完成任务
|
||||
await TaskUpdate({
|
||||
taskId: newTask.id,
|
||||
status: "completed"
|
||||
})
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 错误:参数名不匹配
|
||||
**症状**:`taskId`参数缺失,发现`task_id`参数
|
||||
**解决**:确保使用驼峰命名的`taskId`,而不是蛇形命名的`task_id`
|
||||
|
||||
### 错误:工具模式未发送
|
||||
**症状**:`This tool's schema was not sent to the API`
|
||||
**解决**:先使用`ToolSearch("select:工具名")`发现工具
|
||||
|
||||
### 错误:工具不可用
|
||||
**症状**:工具调用失败,没有具体错误信息
|
||||
**解决**:检查工具是否启用(通过`isTodoV2Enabled()`),确保环境变量设置正确
|
||||
|
||||
## 相关配置
|
||||
|
||||
### 环境变量
|
||||
```bash
|
||||
# 启用OpenAI兼容模式
|
||||
export CLAUDE_CODE_USE_OPENAI=1
|
||||
export OPENAI_API_KEY=your-api-key
|
||||
export OPENAI_BASE_URL=https://api.deepseek.com
|
||||
|
||||
# 配置模型映射
|
||||
export OPENAI_DEFAULT_SONNET_MODEL=deepseek-chat
|
||||
export OPENAI_DEFAULT_OPUS_MODEL=deepseek-chat
|
||||
export OPENAI_DEFAULT_HAIKU_MODEL=deepseek-chat
|
||||
```
|
||||
|
||||
### 设置文件
|
||||
通过`/login`命令配置OpenAI兼容模式后,设置会保存在`~/.claude/settings.json`:
|
||||
```json
|
||||
{
|
||||
"modelType": "openai",
|
||||
"openai": {
|
||||
"baseURL": "https://api.deepseek.com",
|
||||
"apiKey": "your-api-key",
|
||||
"models": {
|
||||
"haiku": "deepseek-chat",
|
||||
"sonnet": "deepseek-chat",
|
||||
"opus": "deepseek-chat"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
在使用OpenAI兼容模型时,task工具需要先通过`ToolSearch`发现才能正常使用。遵循"先发现,后使用"的原则,并注意参数名的正确格式(驼峰命名),可以确保task工具在OpenAI兼容模型下正常工作。
|
||||
@@ -1,425 +0,0 @@
|
||||
# OpenAI 协议兼容层
|
||||
|
||||
## 概述
|
||||
|
||||
claude-code 支持通过 OpenAI Chat Completions API(`/v1/chat/completions`)兼容任意 OpenAI 协议端点,包括 Ollama、DeepSeek、vLLM、One API、LiteLLM 等。
|
||||
|
||||
核心策略为**流适配器模式**:在 `queryModel()` 中插入提前返回分支,将 Anthropic 格式请求转为 OpenAI 格式,调用 OpenAI SDK,再将 SSE 流转换回 `BetaRawMessageStreamEvent` 格式。下游代码(流处理循环、query.ts、QueryEngine.ts、REPL)**完全不改**。
|
||||
|
||||
## 环境变量
|
||||
|
||||
| 变量 | 必需 | 说明 |
|
||||
|---|---|---|
|
||||
| `CLAUDE_CODE_USE_OPENAI` | 是 | 设为 `1` 启用 OpenAI 后端 |
|
||||
| `OPENAI_API_KEY` | 是 | API key(Ollama 等可设为任意值) |
|
||||
| `OPENAI_BASE_URL` | 推荐 | 端点 URL(如 `http://localhost:11434/v1`) |
|
||||
| `OPENAI_MODEL` | 可选 | 覆盖所有请求的模型名(跳过映射) |
|
||||
| `OPENAI_DEFAULT_OPUS_MODEL` | 可选 | 覆盖 opus 家族对应的模型(如 `o3`, `o3-mini`, `o1-pro`) |
|
||||
| `OPENAI_DEFAULT_SONNET_MODEL` | 可选 | 覆盖 sonnet 家族对应的模型(如 `gpt-4o`, `gpt-4.1`) |
|
||||
| `OPENAI_DEFAULT_HAIKU_MODEL` | 可选 | 覆盖 haiku 家族对应的模型(如 `gpt-4o-mini`, `gpt-4.0-mini`) |
|
||||
| `OPENAI_ORG_ID` | 可选 | Organization ID |
|
||||
| `OPENAI_PROJECT_ID` | 可选 | Project ID |
|
||||
|
||||
### 使用示例
|
||||
|
||||
```bash
|
||||
# Ollama
|
||||
CLAUDE_CODE_USE_OPENAI=1 \
|
||||
OPENAI_API_KEY=ollama \
|
||||
OPENAI_BASE_URL=http://localhost:11434/v1 \
|
||||
OPENAI_MODEL=qwen2.5-coder-32b \
|
||||
bun run dev
|
||||
|
||||
# DeepSeek(自动支持 Thinking)
|
||||
CLAUDE_CODE_USE_OPENAI=1 \
|
||||
OPENAI_API_KEY=sk-xxx \
|
||||
OPENAI_BASE_URL=https://api.deepseek.com/v1 \
|
||||
OPENAI_MODEL=deepseek-chat \
|
||||
bun run dev
|
||||
|
||||
# vLLM
|
||||
CLAUDE_CODE_USE_OPENAI=1 \
|
||||
OPENAI_API_KEY=token-abc123 \
|
||||
OPENAI_BASE_URL=http://localhost:8000/v1 \
|
||||
OPENAI_MODEL=Qwen/Qwen2.5-Coder-32B-Instruct \
|
||||
bun run dev
|
||||
|
||||
# One API / LiteLLM
|
||||
CLAUDE_CODE_USE_OPENAI=1 \
|
||||
OPENAI_API_KEY=sk-your-key \
|
||||
OPENAI_BASE_URL=https://your-one-api.example.com/v1 \
|
||||
OPENAI_MODEL=gpt-4o \
|
||||
bun run dev
|
||||
|
||||
# 自定义模型映射(使用家族变量)
|
||||
CLAUDE_CODE_USE_OPENAI=1 \
|
||||
OPENAI_API_KEY=sk-xxx \
|
||||
OPENAI_BASE_URL=https://my-gateway.example.com/v1 \
|
||||
OPENAI_DEFAULT_SONNET_MODEL="gpt-4o-2024-11-20" \
|
||||
OPENAI_DEFAULT_HAIKU_MODEL="gpt-4o-mini" \
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## 架构
|
||||
|
||||
### 请求流程
|
||||
|
||||
```
|
||||
queryModel() [claude.ts]
|
||||
├── 共享预处理(消息归一化、工具过滤、媒体裁剪)
|
||||
└── if (getAPIProvider() === 'openai')
|
||||
└── queryModelOpenAI() [openai/index.ts]
|
||||
├── resolveOpenAIModel() → 解析模型名
|
||||
├── normalizeMessagesForAPI() → 共享消息预处理
|
||||
├── toolToAPISchema() → 构建工具 schema
|
||||
├── anthropicMessagesToOpenAI() → 消息格式转换
|
||||
├── anthropicToolsToOpenAI() → 工具格式转换
|
||||
├── openai.chat.completions.create({ stream: true })
|
||||
└── adaptOpenAIStreamToAnthropic() → 流格式转换
|
||||
├── delta.reasoning_content → thinking 块
|
||||
├── delta.content → text 块
|
||||
├── delta.tool_calls → tool_use 块
|
||||
├── usage.cached_tokens → cache_read_input_tokens
|
||||
└── yield BetaRawMessageStreamEvent
|
||||
```
|
||||
|
||||
### 模型名解析优先级
|
||||
|
||||
`resolveOpenAIModel()` 的解析顺序:
|
||||
|
||||
1. `OPENAI_MODEL` 环境变量 → 直接使用,覆盖所有
|
||||
2. `OPENAI_DEFAULT_{FAMILY}_MODEL` 变量(如 `OPENAI_DEFAULT_SONNET_MODEL`)→ 按模型家族覆盖
|
||||
3. `ANTHROPIC_DEFAULT_{FAMILY}_MODEL` 变量(向后兼容)
|
||||
4. 内置默认映射(见下表)
|
||||
5. 以上都不匹配 → 原名透传
|
||||
|
||||
### 内置模型映射
|
||||
|
||||
| Anthropic 模型 | OpenAI 映射 |
|
||||
|---|---|
|
||||
| `claude-sonnet-4-6` | `gpt-4o` |
|
||||
| `claude-sonnet-4-5-20250929` | `gpt-4o` |
|
||||
| `claude-sonnet-4-20250514` | `gpt-4o` |
|
||||
| `claude-3-7-sonnet-20250219` | `gpt-4o` |
|
||||
| `claude-3-5-sonnet-20241022` | `gpt-4o` |
|
||||
| `claude-opus-4-6` | `o3` |
|
||||
| `claude-opus-4-5-20251101` | `o3` |
|
||||
| `claude-opus-4-1-20250805` | `o3` |
|
||||
| `claude-opus-4-20250514` | `o3` |
|
||||
| `claude-haiku-4-5-20251001` | `gpt-4o-mini` |
|
||||
| `claude-3-5-haiku-20241022` | `gpt-4o-mini` |
|
||||
|
||||
同时会自动剥离 `[1m]` 后缀(Claude 特有的 modifier)。
|
||||
|
||||
## 文件结构
|
||||
|
||||
### 新增文件
|
||||
|
||||
```
|
||||
src/services/api/openai/
|
||||
├── client.ts # OpenAI SDK 客户端工厂(~50 行)
|
||||
├── convertMessages.ts # Anthropic → OpenAI 消息格式转换(~190 行)
|
||||
├── convertTools.ts # Anthropic → OpenAI 工具格式转换(~70 行)
|
||||
├── streamAdapter.ts # SSE 流转换核心,含 thinking + caching(~270 行)
|
||||
├── modelMapping.ts # 模型名解析(~60 行)
|
||||
├── index.ts # 公共入口 queryModelOpenAI()(~110 行)
|
||||
└── __tests__/
|
||||
├── convertMessages.test.ts # 10 个测试
|
||||
├── convertTools.test.ts # 7 个测试
|
||||
├── modelMapping.test.ts # 6 个测试
|
||||
└── streamAdapter.test.ts # 14 个测试(含 thinking + caching)
|
||||
```
|
||||
|
||||
### 修改文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|---|---|
|
||||
| `src/utils/model/providers.ts` | 添加 `'openai'` provider 类型 + `CLAUDE_CODE_USE_OPENAI` 检查(最高优先级) |
|
||||
| `src/utils/model/configs.ts` | 每个 ModelConfig 添加 `openai` 键 |
|
||||
| `src/services/api/claude.ts` | 在 `stripExcessMediaItems()` 后插入 OpenAI 提前返回分支(~8 行) |
|
||||
| `package.json` | 添加 `"openai": "^4.73.0"` 依赖 |
|
||||
|
||||
## 消息转换规则
|
||||
|
||||
### Anthropic → OpenAI
|
||||
|
||||
| Anthropic | OpenAI |
|
||||
|---|---|
|
||||
| `system` prompt(`string[]`) | `role: "system"` 消息(`\n\n` 拼接) |
|
||||
| `user` + `text` 块 | `role: "user"` 消息 |
|
||||
| `assistant` + `text` 块 | `role: "assistant"` + `content` |
|
||||
| `assistant` + `tool_use` 块 | `role: "assistant"` + `tool_calls[]` |
|
||||
| `user` + `tool_result` 块 | `role: "tool"` + `tool_call_id` |
|
||||
| `thinking` 块 | 静默丢弃(请求侧) |
|
||||
|
||||
### 工具转换
|
||||
|
||||
| Anthropic | OpenAI |
|
||||
|---|---|
|
||||
| `{ name, description, input_schema }` | `{ type: "function", function: { name, description, parameters } }` |
|
||||
| `cache_control`, `defer_loading` 等字段 | 剥离 |
|
||||
| `tool_choice: { type: "auto" }` | `"auto"` |
|
||||
| `tool_choice: { type: "any" }` | `"required"` |
|
||||
| `tool_choice: { type: "tool", name }` | `{ type: "function", function: { name } }` |
|
||||
|
||||
### 消息转换示例
|
||||
|
||||
```
|
||||
Anthropic: OpenAI:
|
||||
[
|
||||
system: ["You are helpful."], [
|
||||
{ role: "system",
|
||||
{ role: "user", content: "You are helpful." },
|
||||
content: [ { role: "user",
|
||||
{ type: "text", text: "Run ls" } content: "Run ls"
|
||||
] },
|
||||
}, { role: "assistant",
|
||||
{ role: "assistant", content: "I'll check.",
|
||||
content: [ tool_calls: [{
|
||||
{ type: "text", text: "I'll check."}, id: "tu_123",
|
||||
{ type: "tool_use", type: "function",
|
||||
id: "tu_123", name: "bash", function: {
|
||||
input: { command: "ls" } } name: "bash",
|
||||
] arguments: '{"command":"ls"}'
|
||||
}, }] }
|
||||
{ role: "user", { role: "tool",
|
||||
content: [ tool_call_id: "tu_123",
|
||||
{ type: "tool_result", content: "file1\nfile2"
|
||||
tool_use_id: "tu_123", }
|
||||
content: "file1\nfile2" ]
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 流转换规则
|
||||
|
||||
### SSE Chunk → Anthropic Event 映射
|
||||
|
||||
| OpenAI Chunk | Anthropic Event |
|
||||
|---|---|
|
||||
| 首个 chunk | `message_start`(含 usage) |
|
||||
| `delta.reasoning_content` | `content_block_start(thinking)` + `thinking_delta` |
|
||||
| `delta.content` | `content_block_start(text)` + `text_delta` |
|
||||
| `delta.tool_calls` | `content_block_start(tool_use)` + `input_json_delta` |
|
||||
| `finish_reason: "stop"` | `message_delta(stop_reason: "end_turn")` |
|
||||
| `finish_reason: "tool_calls"` | `message_delta(stop_reason: "tool_use")` |
|
||||
| `finish_reason: "length"` | `message_delta(stop_reason: "max_tokens")` |
|
||||
|
||||
### 块顺序
|
||||
|
||||
当模型返回 `reasoning_content` 时(如 DeepSeek),块顺序与 Anthropic 一致:
|
||||
|
||||
```
|
||||
thinking block (index 0) ← delta.reasoning_content
|
||||
text block (index 1) ← delta.content
|
||||
```
|
||||
|
||||
或:
|
||||
|
||||
```
|
||||
thinking block (index 0) ← delta.reasoning_content
|
||||
tool_use block (index 1) ← delta.tool_calls
|
||||
```
|
||||
|
||||
无 `reasoning_content` 时:
|
||||
|
||||
```
|
||||
text block (index 0) ← delta.content
|
||||
tool_use block (index 1) ← delta.tool_calls(如果有)
|
||||
```
|
||||
|
||||
### finish_reason 映射
|
||||
|
||||
| OpenAI | Anthropic |
|
||||
|---|---|
|
||||
| `stop` | `end_turn` |
|
||||
| `tool_calls` | `tool_use` |
|
||||
| `length` | `max_tokens` |
|
||||
| `content_filter` | `end_turn` |
|
||||
|
||||
### 事件序列示例
|
||||
|
||||
**纯文本响应**:
|
||||
```
|
||||
OpenAI chunks:
|
||||
delta.content = "Hello"
|
||||
delta.content = " world"
|
||||
finish_reason = "stop"
|
||||
|
||||
→ Anthropic events:
|
||||
message_start { message: { id, role: 'assistant', usage: {...} } }
|
||||
content_block_start { index: 0, content_block: { type: 'text' } }
|
||||
content_block_delta { index: 0, delta: { type: 'text_delta', text: 'Hello' } }
|
||||
content_block_delta { index: 0, delta: { type: 'text_delta', text: ' world' } }
|
||||
content_block_stop { index: 0 }
|
||||
message_delta { delta: { stop_reason: 'end_turn' } }
|
||||
message_stop
|
||||
```
|
||||
|
||||
**Thinking + 文本(DeepSeek 风格)**:
|
||||
```
|
||||
OpenAI chunks:
|
||||
delta.reasoning_content = "Let me think..."
|
||||
delta.reasoning_content = " step by step."
|
||||
delta.content = "The answer is 42."
|
||||
finish_reason = "stop"
|
||||
|
||||
→ Anthropic events:
|
||||
message_start { ... }
|
||||
content_block_start { index: 0, content_block: { type: 'thinking', signature: '' } }
|
||||
content_block_delta { index: 0, delta: { type: 'thinking_delta', thinking: 'Let me think...' } }
|
||||
content_block_delta { index: 0, delta: { type: 'thinking_delta', thinking: ' step by step.' } }
|
||||
content_block_stop { index: 0 }
|
||||
content_block_start { index: 1, content_block: { type: 'text' } }
|
||||
content_block_delta { index: 1, delta: { type: 'text_delta', text: 'The answer is 42.' } }
|
||||
content_block_stop { index: 1 }
|
||||
message_delta { delta: { stop_reason: 'end_turn' } }
|
||||
message_stop
|
||||
```
|
||||
|
||||
**工具调用**:
|
||||
```
|
||||
OpenAI chunks:
|
||||
delta.tool_calls[0] = { id: 'call_xxx', function: { name: 'bash', arguments: '' } }
|
||||
delta.tool_calls[0].function.arguments = '{"comm'
|
||||
delta.tool_calls[0].function.arguments = 'and":"ls"}'
|
||||
finish_reason = "tool_calls"
|
||||
|
||||
→ Anthropic events:
|
||||
message_start { ... }
|
||||
content_block_start { index: 0, content_block: { type: 'tool_use', id: 'call_xxx', name: 'bash' } }
|
||||
content_block_delta { index: 0, delta: { type: 'input_json_delta', partial_json: '{"comm' } }
|
||||
content_block_delta { index: 0, delta: { type: 'input_json_delta', partial_json: 'and":"ls"}' } }
|
||||
content_block_stop { index: 0 }
|
||||
message_delta { delta: { stop_reason: 'tool_use' } }
|
||||
message_stop
|
||||
```
|
||||
|
||||
## 功能支持
|
||||
|
||||
### Thinking(思维链)
|
||||
|
||||
**请求侧**:不需要显式配置。支持思维链的模型(DeepSeek 等)会自动返回 `delta.reasoning_content`。
|
||||
|
||||
**响应侧**:`delta.reasoning_content` 被转换为 Anthropic `thinking` content block:
|
||||
|
||||
```ts
|
||||
// content_block_start
|
||||
{ type: 'content_block_start', index: 0,
|
||||
content_block: { type: 'thinking', thinking: '', signature: '' } }
|
||||
|
||||
// content_block_delta
|
||||
{ type: 'content_block_delta', index: 0,
|
||||
delta: { type: 'thinking_delta', thinking: 'Let me analyze...' } }
|
||||
```
|
||||
|
||||
thinking block 在 text/tool_use block 之前自动关闭,保持 Anthropic 的块顺序。
|
||||
|
||||
### Prompt Caching
|
||||
|
||||
**请求侧**:OpenAI 端点使用自动缓存,无需显式设置 `cache_control`。
|
||||
|
||||
**响应侧**:OpenAI 的 `usage.prompt_tokens_details.cached_tokens` 被映射到 Anthropic 的 `cache_read_input_tokens`:
|
||||
|
||||
```
|
||||
OpenAI: usage.prompt_tokens_details.cached_tokens = 800
|
||||
↓
|
||||
Anthropic: message_start.message.usage.cache_read_input_tokens = 800
|
||||
```
|
||||
|
||||
在 `message_start` 的 usage 中报告缓存命中量。
|
||||
|
||||
### 工具调用(Tool Use)
|
||||
|
||||
完整支持 OpenAI function calling 格式。所有本地工具(Bash、FileEdit、Grep、Glob、Agent 等)透明工作——它们通过 JSON 输入输出通信,格式无关。
|
||||
|
||||
工具参数以 `input_json_delta` 形式流式传输,由下游代码拼接解析。
|
||||
|
||||
### 不支持的功能
|
||||
|
||||
| 功能 | 策略 |
|
||||
|---|---|
|
||||
| Beta Headers | 不发送 |
|
||||
| Server Tools (advisor) | 不发送 |
|
||||
| Structured Output | 不发送 |
|
||||
| Fast Mode / Effort | 不发送 |
|
||||
| Tool Search / defer_loading | 不启用,所有工具直接发送 |
|
||||
| Anthropic Signature | thinking block 的 `signature` 字段为空字符串 |
|
||||
| cache_creation_input_tokens | 始终为 0(OpenAI 不区分创建/读取) |
|
||||
|
||||
## 测试
|
||||
|
||||
```bash
|
||||
# 运行所有 OpenAI 适配层测试
|
||||
bun test src/services/api/openai/__tests__/
|
||||
|
||||
# 单独运行
|
||||
bun test src/services/api/openai/__tests__/streamAdapter.test.ts # 14 tests(含 thinking + caching)
|
||||
bun test src/services/api/openai/__tests__/convertMessages.test.ts # 10 tests
|
||||
bun test src/services/api/openai/__tests__/convertTools.test.ts # 7 tests
|
||||
bun test src/services/api/openai/__tests__/modelMapping.test.ts # 6 tests
|
||||
```
|
||||
|
||||
当前测试覆盖:**39 tests / 73 assertions / 0 fail**。
|
||||
|
||||
### 测试覆盖矩阵
|
||||
|
||||
| 功能 | convertMessages | convertTools | streamAdapter | modelMapping |
|
||||
|---|---|---|---|---|
|
||||
| 文本消息转换 | ✅ | | | |
|
||||
| tool_use 转换 | ✅ | | | |
|
||||
| tool_result 转换 | ✅ | | | |
|
||||
| thinking 剥离 | ✅ | | | |
|
||||
| 完整对话流程 | ✅ | | | |
|
||||
| 工具 schema 转换 | | ✅ | | |
|
||||
| tool_choice 映射 | | ✅ | | |
|
||||
| 纯文本流 | | | ✅ | |
|
||||
| 工具调用流 | | | ✅ | |
|
||||
| 混合文本+工具 | | | ✅ | |
|
||||
| finish_reason 映射 | | | ✅ | |
|
||||
| thinking 流 | | | ✅ | |
|
||||
| thinking+text 切换 | | | ✅ | |
|
||||
| thinking+tool_use 切换 | | | ✅ | |
|
||||
| 块索引正确性 | | | ✅ | |
|
||||
| cached_tokens 映射 | | | ✅ | |
|
||||
| OPENAI_MODEL 覆盖 | | | | ✅ |
|
||||
| 默认模型映射 | | | | ✅ |
|
||||
| 未知模型透传 | | | | ✅ |
|
||||
| [1m] 后缀剥离 | | | | ✅ |
|
||||
|
||||
## 端到端验证
|
||||
|
||||
```bash
|
||||
# 1. 安装依赖
|
||||
bun install
|
||||
|
||||
# 2. 运行单元测试
|
||||
bun test src/services/api/openai/__tests__/
|
||||
|
||||
# 3. 连接实际端点(以 Ollama 为例)
|
||||
CLAUDE_CODE_USE_OPENAI=1 \
|
||||
OPENAI_API_KEY=ollama \
|
||||
OPENAI_BASE_URL=http://localhost:11434/v1 \
|
||||
OPENAI_MODEL=qwen2.5-coder-32b \
|
||||
bun run dev
|
||||
|
||||
# 4. 连接 DeepSeek(测试 thinking 支持)
|
||||
CLAUDE_CODE_USE_OPENAI=1 \
|
||||
OPENAI_API_KEY=sk-xxx \
|
||||
OPENAI_BASE_URL=https://api.deepseek.com/v1 \
|
||||
OPENAI_MODEL=deepseek-reasoner \
|
||||
bun run dev
|
||||
|
||||
# 5. 确认现有测试不受影响
|
||||
bun test # 无 CLAUDE_CODE_USE_OPENAI 时走原有路径
|
||||
```
|
||||
|
||||
## 代码统计
|
||||
|
||||
| 类别 | 行数 |
|
||||
|---|---|
|
||||
| 新增源码 | ~620 行 |
|
||||
| 新增测试 | ~450 行 |
|
||||
| 改动现有代码 | ~25 行 |
|
||||
| **总计** | **~1100 行** |
|
||||
@@ -1,35 +0,0 @@
|
||||
# 社区项目 & Blog 合集
|
||||
|
||||
> 每日更新,欢迎自荐!
|
||||
|
||||
## 工具 & 应用
|
||||
|
||||
| 项目 | 描述 | 作者 |
|
||||
|------|------|------|
|
||||
| [4qtask.vercel.app](https://4qtask.vercel.app/) | 免费四象限时间管理工具 | @kevinhuky |
|
||||
| [kaying.studio](https://kaying.studio/) | 个人 AI 工具箱 | @kayingai |
|
||||
| [supsub.ai](https://supsub.ai/) | 高效阅读工具 | @hidumou |
|
||||
| [x-video-download.net](https://x-video-download.net/) | 视频下载工具 | @syakadou |
|
||||
| [1openapi.com](https://1openapi.com/) | API 中转站 | @thinker007 |
|
||||
| [claw-z.com](https://claw-z.com/) | 一键部署 OpenClaw AI Agent(场景驱动、全面管理) | @uhhc |
|
||||
| [gemini-watermark-remover.net](https://gemini-watermark-remover.net/) | Gemini 水印移除工具 | @syakadou |
|
||||
|
||||
## GitHub 开源项目
|
||||
|
||||
| 项目 | 描述 | 作者 |
|
||||
|------|------|------|
|
||||
| [VersperClaw](https://github.com/versperai/VersperClaw) | 全自动科研流 | @versperai |
|
||||
| [claude-reviews-claude](https://github.com/openedclaude/claude-reviews-claude) | 原汤化原食——Claude 如何看待眼中的老己 | @openedclaude |
|
||||
| [agentica](https://github.com/shibing624/agentica) | 自研 Agent 框架,借鉴 claude-code 多 Agent 处理 | @shibing624 |
|
||||
| [macman](https://github.com/tonngw/macman) | Mac 从 0 到 1 保姆级配置教程 | @tonngw |
|
||||
| [SuperSpec](https://github.com/asasugar/SuperSpec) | SDD / Spec-Driven Development | @asasugar |
|
||||
| [adnify](https://github.com/adnaan-worker/adnify) | 高颜值高定制化 AI 编辑器 | @adnaan-worker |
|
||||
| [another-rule-engine](https://github.com/eatmoreduck/another-rule-engine) | 基于 Groovy 的开源多功能决策引擎 | @eatmoreduck |
|
||||
| [creative_master](https://github.com/chatabc/creative_master) | AI 驱动的创意灵感管理工具 | @chatabc |
|
||||
| [RapidDoc](https://github.com/RapidAI/RapidDoc) | Office 文件解析工具转 Markdown(支持 PDF/Image/Word/PPT/Excel) | @hzkitt |
|
||||
|
||||
## Blog
|
||||
|
||||
| 链接 | 作者 |
|
||||
|------|------|
|
||||
| [blog.xiaohuangyu.space](https://blog.xiaohuangyu.space/) | @eatmoreduck |
|
||||
@@ -137,7 +137,7 @@ Auto mode 可通过以下方式激活:
|
||||
|
||||
### 进入时(Full Instructions)
|
||||
|
||||
注入到对话中的指令(`messages.ts:3464`):
|
||||
注入到对话中的指令(`messages.ts:3481`):
|
||||
|
||||
> Auto mode is active. The user chose continuous, autonomous execution. You should:
|
||||
>
|
||||
|
||||
@@ -18,17 +18,19 @@ keywords: ["权限模型", "Allow Ask Deny", "PermissionRule", "checkPermissions
|
||||
|
||||
这些行为由 `PermissionResult` 类型定义(`src/utils/permissions/PermissionResult.ts`)。
|
||||
|
||||
## 权限规则的五层来源
|
||||
## 权限规则的来源
|
||||
|
||||
规则从 5 个来源汇聚(`PERMISSION_RULE_SOURCES`,`permissions.ts:109`),优先级从高到低:
|
||||
规则从 8 个来源汇聚(`PERMISSION_RULE_SOURCES`,`permissions.ts:109`),优先级从低到高(后者覆盖前者):
|
||||
|
||||
```
|
||||
1. session — 用户在当前对话中手动授权("Always allow")
|
||||
2. cliArg — 命令行 --allow/--deny 参数
|
||||
3. command — Skill 工具的 allowedTools 白名单
|
||||
4. projectSettings — .claude/settings.json(团队共享)
|
||||
5. userSettings — ~/.claude/settings.json(跨项目)
|
||||
6. policySettings — 企业管理员下发的策略(用户不可覆盖)
|
||||
1. userSettings — ~/.claude/settings.json(跨项目)
|
||||
2. projectSettings — .claude/settings.json(团队共享)
|
||||
3. localSettings — .claude/settings.local.json(gitignored,个人覆盖)
|
||||
4. flagSettings — --settings 命令行参数
|
||||
5. policySettings — 企业管理员下发的策略(用户不可覆盖)
|
||||
6. cliArg — 命令行 --allow/--deny 参数
|
||||
7. command — Skill 工具的 allowedTools 白名单
|
||||
8. session — 用户在当前对话中手动授权("Always allow")
|
||||
```
|
||||
|
||||
每个来源维护三个数组:`alwaysAllowRules[source]`、`alwaysAskRules[source]`、`alwaysDenyRules[source]`。
|
||||
@@ -65,7 +67,7 @@ MCP 工具使用 `getToolNameForPermissionCheck()` 获取匹配名称,支持
|
||||
|
||||
**2. 命令模式匹配**(BashTool 的 `checkPermissions()`)
|
||||
|
||||
BashTool 通过 `preparePermissionMatcher()`(`Tool.ts:514`)解析命令模式:
|
||||
BashTool 通过 `preparePermissionMatcher()`(`Tool.ts:520`)解析命令模式:
|
||||
```json
|
||||
{"tool": "Bash", "ruleContent": "git *"} → 匹配 "git commit -m 'fix'"
|
||||
```
|
||||
@@ -120,7 +122,9 @@ Read/Edit/Write 工具通过 `getPath()` 提取文件路径,与 `ruleContent`
|
||||
|------|---------------------|---------|------|
|
||||
| **Default** | `'default'` | 日常使用 | 敏感操作逐一确认 |
|
||||
| **Plan Mode** | `'plan'` | 探索阶段 | 只能读不能写(`isReadOnly()` 检查) |
|
||||
| **Auto** | `'auto'` | 信任 AI | 通过 transcript classifier 自动决策 |
|
||||
| **Accept Edits** | `'acceptEdits'` | 快速迭代 | 工作区内文件编辑自动放行,其他操作仍需确认 |
|
||||
| **Don't Ask** | `'dontAsk'` | 减少打断 | 尽量自动决策,减少确认弹窗 |
|
||||
| **Auto** | `'auto'` | 信任 AI | 通过 transcript classifier 自动决策(需 `TRANSCRIPT_CLASSIFIER` feature flag) |
|
||||
| **Bypass** | `'bypassPermissions'` | 完全信任 | 所有操作自动放行(需显式 `--dangerously-skip-permissions`) |
|
||||
|
||||
Plan Mode 切换由 `EnterPlanModeTool.call()` 触发:
|
||||
@@ -143,8 +147,8 @@ context.setAppState(prev => ({
|
||||
|
||||
```typescript
|
||||
const DENIAL_LIMITS = {
|
||||
maxDenialsPerTool: 3, // 同一工具连续拒绝上限
|
||||
cooldownPeriodMs: 30_000, // 冷却期 30 秒
|
||||
maxConsecutive: 3, // 同一工具连续拒绝上限
|
||||
maxTotal: 20, // 总拒绝上限
|
||||
}
|
||||
```
|
||||
|
||||
@@ -162,9 +166,12 @@ const DENIAL_LIMITS = {
|
||||
|
||||
```typescript
|
||||
type PermissionUpdate =
|
||||
| { type: 'addRule', behavior, rule, destination }
|
||||
| { type: 'removeRule', behavior, rule, destination }
|
||||
| { type: 'setMode', mode, destination }
|
||||
| { type: 'addRules', destination, rules, behavior }
|
||||
| { type: 'replaceRules', destination, rules, behavior }
|
||||
| { type: 'removeRules', destination, rules, behavior }
|
||||
| { type: 'setMode', destination, mode }
|
||||
| { type: 'addDirectories', destination, directories }
|
||||
| { type: 'removeDirectories', destination, directories }
|
||||
```
|
||||
|
||||
当用户在 Ask 对话框中选择 "Always allow",系统调用 `persistPermissionUpdates()` 将规则写入对应层级的 settings 文件(project/user/managed),同时更新内存中的 `toolPermissionContext`。
|
||||
|
||||
@@ -16,13 +16,13 @@ keywords: ["Plan Mode", "计划模式", "EnterPlanMode", "ExitPlanMode", "prepar
|
||||
|
||||
<Steps>
|
||||
<Step title="EnterPlanMode — 进入计划模式">
|
||||
AI 自主判断(或用户触发)任务需要规划,调用 `EnterPlanModeTool`(`src/tools/EnterPlanModeTool/EnterPlanModeTool.ts:36`)。该工具需要**用户审批**(`checkPermissions` 返回 `ask`)。
|
||||
AI 自主判断(或用户触发)任务需要规划,调用 `EnterPlanModeTool`(`packages/builtin-tools/src/tools/EnterPlanModeTool/EnterPlanModeTool.ts:36`)。该工具需要**用户审批**(`checkPermissions` 返回 `ask`)。
|
||||
</Step>
|
||||
<Step title="探索阶段 — 只读工具集">
|
||||
权限模式切换为 `'plan'`,AI 只能使用 `isReadOnly()` 为 true 的工具(Read、Grep、Glob、Agent 等)。写操作被自动拒绝。
|
||||
</Step>
|
||||
<Step title="ExitPlanMode — 提交方案审批">
|
||||
AI 完成探索后,调用 `ExitPlanModeV2Tool`(`src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.ts:147`),将计划文件提交给用户审阅。这是第二个**需要用户审批**的节点。
|
||||
AI 完成探索后,调用 `ExitPlanModeV2Tool`(`packages/builtin-tools/src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.ts:147`),将计划文件提交给用户审阅。这是第二个**需要用户审批**的节点。
|
||||
</Step>
|
||||
<Step title="恢复执行 — 全部工具权限">
|
||||
用户批准后,权限模式恢复为进入前的状态,AI 按计划执行。
|
||||
@@ -107,7 +107,7 @@ if (isTeammate()) {
|
||||
|
||||
## 什么时候该用计划模式
|
||||
|
||||
`EnterPlanModeTool` 的 Prompt(`src/tools/EnterPlanModeTool/prompt.ts`)定义了两套触发标准——外部版本更积极(鼓励规划),内部版本更克制(仅在真正模糊时使用):
|
||||
`EnterPlanModeTool` 的 Prompt(`packages/builtin-tools/src/tools/EnterPlanModeTool/prompt.ts`)定义了两套触发标准——外部版本更积极(鼓励规划),内部版本更克制(仅在真正模糊时使用):
|
||||
|
||||
| 场景 | 外部版本 | 内部版本 |
|
||||
|------|---------|---------|
|
||||
|
||||
@@ -166,7 +166,7 @@ keywords: ["沙箱", "sandbox", "权限", "Bash", "PowerShell", "bubblewrap", "s
|
||||
5. 这条命令没有被显式排除
|
||||
6. 这次调用没有被允许以 `dangerouslyDisableSandbox` 绕过
|
||||
|
||||
对应入口在 `src/tools/BashTool/shouldUseSandbox.ts` 和 `src/utils/sandbox/sandbox-adapter.ts`。
|
||||
对应入口在 `packages/builtin-tools/src/tools/BashTool/shouldUseSandbox.ts` 和 `src/utils/sandbox/sandbox-adapter.ts`。
|
||||
|
||||
### 3. PowerShell 只在支持平台上走
|
||||
|
||||
@@ -518,11 +518,11 @@ REPL / CLI 启动
|
||||
|
||||
如果你想继续顺着源码深入,推荐按下面顺序看:
|
||||
|
||||
1. `src/tools/BashTool/shouldUseSandbox.ts`
|
||||
1. `packages/builtin-tools/src/tools/BashTool/shouldUseSandbox.ts`
|
||||
2. `src/utils/Shell.ts`
|
||||
3. `src/utils/sandbox/sandbox-adapter.ts`
|
||||
4. `src/utils/permissions/permissions.ts`
|
||||
5. `src/tools/BashTool/bashPermissions.ts`
|
||||
5. `packages/builtin-tools/src/tools/BashTool/bashPermissions.ts`
|
||||
6. `src/utils/permissions/pathValidation.ts`
|
||||
7. `src/utils/permissions/filesystem.ts`
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ Claude 的 System Prompt 中包含安全指令——这是"软性"约束,依
|
||||
| `deny` | 直接拒绝 | 匹配 deny 规则 |
|
||||
| `ask` | 弹窗确认 | 未匹配任何规则 或 匹配 ask 规则 |
|
||||
|
||||
以 BashTool 为例(`src/tools/BashTool/bashPermissions.ts`),`bashToolHasPermission()` 执行了极其细致的检查链:
|
||||
以 BashTool 为例(`packages/builtin-tools/src/tools/BashTool/bashPermissions.ts`),`bashToolHasPermission()` 执行了极其细致的检查链:
|
||||
|
||||
1. **AST 安全解析**:用 tree-sitter 解析 bash AST,检测命令注入(`$()`、反引号等)
|
||||
2. **语义检查**:识别危险命令(`eval`、`exec`、`source` 等)
|
||||
@@ -169,7 +169,7 @@ Bash("rm -rf node_modules") → ⚠️ 需确认(不可逆)
|
||||
攻击:cd /malicious/dir && git status
|
||||
/malicious/dir 包含 bare repo + 恶意钩子
|
||||
防御:bashToolHasPermission() 检测 cd + git 组合
|
||||
强制 require approval(src/tools/BashTool/bashPermissions.ts:2209)
|
||||
强制 require approval(packages/builtin-tools/src/tools/BashTool/bashPermissions.ts:2209)
|
||||
```
|
||||
|
||||
### 场景3:管道注入
|
||||
@@ -178,5 +178,5 @@ Bash("rm -rf node_modules") → ⚠️ 需确认(不可逆)
|
||||
攻击:echo 'x' | xargs printf '%s' >> /etc/passwd
|
||||
splitCommand 会剥离重定向,导致路径检查遗漏
|
||||
防御:即使管道段独立检查通过,仍对原始命令重新验证路径约束
|
||||
检查重定向目标中的危险模式(反引号、$())(bashPermissions.ts:1992-2056)
|
||||
检查重定向目标中的危险模式(反引号、$())(packages/builtin-tools/src/tools/BashTool/bashPermissions.ts:1992-2056)
|
||||
```
|
||||
|
||||
77
docs/task/task-001-daemon-status-stop.md
Normal file
77
docs/task/task-001-daemon-status-stop.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Task 001: daemon status / stop
|
||||
|
||||
> 来源: [stub-recovery-design-1-4.md](../features/stub-recovery-design-1-4.md) 第 1 项
|
||||
> 优先级: P0 (首选实现项)
|
||||
> 工作量: 小
|
||||
> 状态: DONE
|
||||
|
||||
## 目标
|
||||
|
||||
让 `claude daemon status` 和 `claude daemon stop` 在任意 CLI 进程中都能正确工作,不依赖 TUI 内存态。
|
||||
|
||||
## 背景
|
||||
|
||||
- `start` 路径已有完整 supervisor + worker 生命周期 (`src/daemon/main.ts`, `src/daemon/workerRegistry.ts`)
|
||||
- `status` / `stop` 目前只是占位输出 (`src/daemon/main.ts:49`)
|
||||
- `/remote-control-server` 有自己的命令内 UI 状态,但只维护当前进程内的 `daemonProcess`,不适合跨进程管理
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 新增文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `src/daemon/state.ts` | daemon 状态文件读写模块 |
|
||||
|
||||
### 修改文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `src/daemon/main.ts` | `start` 写入状态文件;`status`/`stop` 调用 state 模块 |
|
||||
| `src/commands/remoteControlServer/remoteControlServer.tsx` | 读取同一份状态文件(轻量改动) |
|
||||
|
||||
### 状态文件
|
||||
|
||||
路径: `~/.claude/daemon/remote-control.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"pid": 12345,
|
||||
"cwd": "/path/to/project",
|
||||
"startedAt": "2026-04-12T10:00:00Z",
|
||||
"workerKinds": ["bridge", "rcs"],
|
||||
"lastStatus": "running"
|
||||
}
|
||||
```
|
||||
|
||||
### status 逻辑
|
||||
|
||||
1. 读取状态文件
|
||||
2. 用进程探测验证 pid 是否存活
|
||||
3. 输出 `running` / `stopped` / `stale`
|
||||
4. stale 时自动清理状态文件
|
||||
|
||||
### stop 逻辑
|
||||
|
||||
1. 读取 pid
|
||||
2. 发送 `SIGTERM`
|
||||
3. 等待退出(超时兜底)
|
||||
4. 超时后 `SIGKILL`
|
||||
5. 清理状态文件
|
||||
|
||||
## 验证步骤
|
||||
|
||||
- [ ] `claude daemon start` 正常启动并写入状态文件
|
||||
- [ ] 新开终端执行 `claude daemon status`,显示 `running`
|
||||
- [ ] 执行 `claude daemon stop`,daemon 正常退出
|
||||
- [ ] 再次执行 `claude daemon status`,返回 `stopped` 或 `stale cleaned`
|
||||
- [ ] Windows 下 stop 超时兜底正常工作
|
||||
|
||||
## 风险
|
||||
|
||||
- Windows 信号模型和 Unix 不同,`stop` 需要超时兜底
|
||||
- 当前设计默认单 supervisor,不处理多实例并发
|
||||
|
||||
## 依赖
|
||||
|
||||
无外部依赖,可独立实施。
|
||||
80
docs/task/task-002-bg-sessions-ps-logs-kill.md
Normal file
80
docs/task/task-002-bg-sessions-ps-logs-kill.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Task 002: BG_SESSIONS — ps / logs / kill
|
||||
|
||||
> 来源: [stub-recovery-design-1-4.md](../features/stub-recovery-design-1-4.md) 第 2 项
|
||||
> 优先级: P1
|
||||
> 工作量: 中等
|
||||
> 状态: DONE
|
||||
> 阶段: Phase 2A (MVP)
|
||||
|
||||
## 目标
|
||||
|
||||
把 `ps` / `logs` / `kill` 做成真正有用的 session 管理命令。不在第一阶段补完 `attach` / `--bg`。
|
||||
|
||||
## 背景
|
||||
|
||||
- fast-path 已接好 (`src/entrypoints/cli.tsx:218`)
|
||||
- session registry 已有真实实现 (`src/utils/concurrentSessions.ts`)
|
||||
- `exit` 在 bg session 内已会 `tmux detach-client` (`src/commands/exit/exit.tsx:20`)
|
||||
- CLI handler 仍全空 (`src/cli/bg.ts`)
|
||||
- task summary 仍然是 stub (`src/utils/taskSummary.ts`)
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 修改文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `src/cli/bg.ts` | 实现 `ps` / `logs` / `kill` handler |
|
||||
| `src/utils/concurrentSessions.ts` | 扩展以便后续 attach/--bg 使用 |
|
||||
| `src/utils/taskSummary.ts` | 补充基础实现 |
|
||||
|
||||
### 复用模块
|
||||
|
||||
- `src/utils/sessionStorage.ts` — session 存储
|
||||
- `src/utils/udsClient.ts` — UDS 通信
|
||||
|
||||
### ps 命令
|
||||
|
||||
- 从 registry 读取 live sessions
|
||||
- 展示: pid, kind, sessionId, cwd, name, startedAt, bridgeSessionId
|
||||
- 如果有 activity/status,一并展示
|
||||
|
||||
### logs 命令
|
||||
|
||||
- 支持按 `sessionId` / `pid` / `name` 查找
|
||||
- 优先复用本地 transcript/log 读取能力
|
||||
- 如果 registry 里存在 `logPath`,支持 tail 文件
|
||||
|
||||
### kill 命令
|
||||
|
||||
- 解析目标 session
|
||||
- 发退出信号
|
||||
- 清理 stale registry
|
||||
|
||||
## 验证步骤
|
||||
|
||||
- [ ] `ps` 能列出当前 live sessions
|
||||
- [ ] `logs <sessionId|pid|name>` 能输出对应日志
|
||||
- [ ] `kill <sessionId|pid|name>` 能结束目标 session 并清理 registry
|
||||
- [ ] 无 live session 时各命令有明确提示
|
||||
|
||||
## Phase 2B (后续)
|
||||
|
||||
- [ ] 实现 `attach`
|
||||
- [ ] 实现 `--bg`
|
||||
- [ ] 实现 `taskSummary` 的中途状态更新
|
||||
|
||||
### 为什么拆分
|
||||
|
||||
- 现有 registry 记录了 `pid / sessionId / name / logPath`
|
||||
- 但没有可靠的 tmux attach target
|
||||
- `attach` 和 `--bg` 需要补启动/附着元数据设计,不是简单补 handler
|
||||
|
||||
## 风险
|
||||
|
||||
- `attach` / `--bg` 第二阶段需要 tmux 元数据设计
|
||||
- Windows 下 tmux 路径需要明确降级策略
|
||||
|
||||
## 依赖
|
||||
|
||||
- Task 001 (daemon 状态管理可复用模式,但非硬性依赖)
|
||||
87
docs/task/task-003-templates-job-mvp.md
Normal file
87
docs/task/task-003-templates-job-mvp.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Task 003: TEMPLATES — job 文件系统 MVP
|
||||
|
||||
> 来源: [stub-recovery-design-1-4.md](../features/stub-recovery-design-1-4.md) 第 3 项
|
||||
> 优先级: P2
|
||||
> 工作量: 中等
|
||||
> 状态: DONE
|
||||
> 阶段: MVP
|
||||
|
||||
## 目标
|
||||
|
||||
把 `new` / `list` / `reply` 做成可用的模板任务系统。第一阶段不碰复杂的自动分类与自动执行。
|
||||
|
||||
## 背景
|
||||
|
||||
- 命令入口只有 fast-path (`src/entrypoints/cli.tsx:272`)
|
||||
- handler 是空的 (`src/cli/handlers/templateJobs.ts`)
|
||||
- `markdownConfigLoader` 已把 `templates` 纳入配置目录 (`src/utils/markdownConfigLoader.ts:35`)
|
||||
- `query/stopHooks` 已预留 job classifier 链路 (`src/query/stopHooks.ts:103`)
|
||||
- `jobs/classifier.ts` 仍是 stub (`src/jobs/classifier.ts`)
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 新增文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `src/jobs/state.ts` | job 状态管理 |
|
||||
| `src/jobs/templates.ts` | 模板解析与列表 |
|
||||
|
||||
### 修改文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `src/cli/handlers/templateJobs.ts` | 实现 `new` / `list` / `reply` handler |
|
||||
|
||||
### 模板来源
|
||||
|
||||
`.claude/templates/*.md`
|
||||
|
||||
### 模板格式
|
||||
|
||||
复用现有 markdown + frontmatter 解析,不另外设计 DSL。
|
||||
|
||||
### list 命令
|
||||
|
||||
- 列出所有模板
|
||||
- 显示: 模板名, description, 路径
|
||||
|
||||
### new 命令
|
||||
|
||||
- 解析模板
|
||||
- 在 `~/.claude/jobs/<job-id>/` 下创建 job 目录
|
||||
- 写入 `template.md`, `input.txt`, `state.json`
|
||||
- 返回 job id 与目录路径
|
||||
|
||||
### reply 命令
|
||||
|
||||
- 将回复写入 `replies.jsonl` 或 `input.txt`
|
||||
- 更新 `state.json`
|
||||
|
||||
## 验证步骤
|
||||
|
||||
- [ ] `list` 能列出 `.claude/templates` 下的所有模板
|
||||
- [ ] `new <template> [args...]` 能创建 job 目录和状态文件
|
||||
- [ ] `reply <job-id> <text>` 能更新 job 内容和状态
|
||||
- [ ] frontmatter schema 最小字段集已定义
|
||||
|
||||
## Phase 2 (后续)
|
||||
|
||||
- [ ] 恢复 `src/jobs/classifier.ts`
|
||||
- [ ] 让带 `CLAUDE_JOB_DIR` 的 job session 在 turn 完成后自动更新 `state.json`
|
||||
- [ ] 再决定是否补自动 job runner
|
||||
|
||||
### 为什么拆分
|
||||
|
||||
- 当前是 "template job commands",不是单纯模板列表
|
||||
- 自动 job 运行链路没有足够现成实现
|
||||
- 先做文件系统 job lifecycle 更稳
|
||||
|
||||
## 风险
|
||||
|
||||
- frontmatter schema 需要先定义最小字段集
|
||||
- 一旦扩展到"自动运行 job",范围会明显膨胀
|
||||
|
||||
## 依赖
|
||||
|
||||
无硬性依赖,可独立实施。
|
||||
103
docs/task/task-004-assistant-session-attach.md
Normal file
103
docs/task/task-004-assistant-session-attach.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Task 004: assistant [sessionId] — 分阶段恢复
|
||||
|
||||
> 来源: [stub-recovery-design-1-4.md](../features/stub-recovery-design-1-4.md) 第 4 项
|
||||
> 优先级: P3
|
||||
> 工作量: Phase 4A 中等,4A-4D 全做完很大
|
||||
> 状态: Phase 4A DONE, 4B-4D TODO
|
||||
|
||||
## 目标
|
||||
|
||||
不一次性恢复整个 KAIROS 助手系统。先做"明确 sessionId 的 viewer attach 可用",再逐步补 discovery / chooser / install。
|
||||
|
||||
## 背景
|
||||
|
||||
- attach 主流程已存在 (`src/main.tsx:4708`)
|
||||
- 远端 viewer 所需基础模块已存在:
|
||||
- `src/remote/RemoteSessionManager.ts`
|
||||
- `src/hooks/useAssistantHistory.ts`
|
||||
- `src/assistant/sessionHistory.ts`
|
||||
- 真正 stub 的主要是:
|
||||
- `src/assistant/sessionDiscovery.ts`
|
||||
- `src/assistant/AssistantSessionChooser.tsx`
|
||||
- `src/commands/assistant/assistant.tsx:7`
|
||||
- `src/assistant/index.ts`
|
||||
|
||||
## 分阶段实现
|
||||
|
||||
### Phase 4A: MVP — 显式 sessionId attach
|
||||
|
||||
**修改文件:**
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `src/main.tsx` | 确保 attach 分支可用 |
|
||||
| `src/commands/assistant/index.ts` | 实现显式 sessionId 参数入口 |
|
||||
|
||||
**行为:**
|
||||
- `claude assistant <sessionId>` — 进入 remote viewer
|
||||
- `claude assistant` (无参数) — 返回明确提示: 当前版本需要显式 sessionId,discovery 尚未启用
|
||||
|
||||
**验证:**
|
||||
- [ ] `claude assistant <sessionId>` 能进入 remote viewer
|
||||
- [ ] 历史懒加载工作正常
|
||||
- [ ] 无参数模式给出明确提示
|
||||
|
||||
### Phase 4B: session discovery
|
||||
|
||||
**修改文件:**
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `src/assistant/sessionDiscovery.ts` | 恢复 `discoverAssistantSessions()` |
|
||||
|
||||
**行为:**
|
||||
- 数据来源优先复用现有 sessions / bridge / teleport API,不新增协议
|
||||
- `claude assistant` 无参数时能拿到候选 session 列表
|
||||
|
||||
**验证:**
|
||||
- [ ] 无参数调用能列出可用 sessions
|
||||
- [ ] 数据来源复用现有通道
|
||||
|
||||
### Phase 4C: session chooser
|
||||
|
||||
**修改文件:**
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `src/assistant/AssistantSessionChooser.ts` | 恢复交互式选择器 |
|
||||
|
||||
**行为:**
|
||||
- 多 session 时可交互选择
|
||||
|
||||
**验证:**
|
||||
- [ ] 多个 session 时弹出选择器
|
||||
- [ ] 选择后正确 attach
|
||||
|
||||
### Phase 4D: install wizard
|
||||
|
||||
**修改文件:**
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `src/commands/assistant/assistant.ts` | 恢复 install wizard 辅助函数 |
|
||||
|
||||
**行为:**
|
||||
- 没有 session 时如何引导用户
|
||||
|
||||
**验证:**
|
||||
- [ ] 无可用 session 时引导用户创建/连接
|
||||
|
||||
## 为什么拆分
|
||||
|
||||
- attach 渲染层与远端消息通道大部分已在
|
||||
- 真正缺的是"如何发现目标 session"和"如何交互选择"
|
||||
- 如果把 `src/assistant/index.ts` 的整套 KAIROS 正常模式也一起拉进来,范围会失控
|
||||
|
||||
## 风险
|
||||
|
||||
- 这是四项里范围最大的
|
||||
- 一旦把 KAIROS 正常模式整体拉入,会从"viewer attach"膨胀成"完整 assistant mode 恢复"
|
||||
|
||||
## 依赖
|
||||
|
||||
- Task 002 的 session registry 模式可复用
|
||||
196
docs/task/task-013-bg-engine-abstraction.md
Normal file
196
docs/task/task-013-bg-engine-abstraction.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# Task 013: BgEngine 跨平台后台引擎抽象
|
||||
|
||||
> 设计文档: [daemon-restructure-design.md](../features/daemon-restructure-design.md) § 四
|
||||
> 依赖: 无
|
||||
> 分支: `feat/integrate-5-branches`
|
||||
|
||||
## 目标
|
||||
|
||||
将 `src/cli/bg.ts` 中硬编码的 tmux 逻辑提取为引擎抽象层,实现 TmuxEngine + DetachedEngine,使后台会话功能在 Windows / macOS / Linux 上都能工作。
|
||||
|
||||
## 背景
|
||||
|
||||
当前 `bg.ts` 中 `handleBgFlag()` 和 `attachHandler()` 直接调用 tmux 命令。Windows 上 `--bg` 直接报错退出。需要一个引擎抽象层,根据平台和可用工具自动选择最佳方案。
|
||||
|
||||
## 文件清单
|
||||
|
||||
### 新增
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `src/cli/bg/engine.ts` | BgEngine 接口 + BgStartOptions/BgStartResult 类型 |
|
||||
| `src/cli/bg/engines/tmux.ts` | TmuxEngine: 从 `bg.ts` 提取 tmux 相关逻辑 |
|
||||
| `src/cli/bg/engines/detached.ts` | DetachedEngine: spawn({ detached }) + logFile 重定向 |
|
||||
| `src/cli/bg/engines/index.ts` | selectEngine() 自动选择 + re-export |
|
||||
| `src/cli/bg/tail.ts` | 跨平台日志 tail: fs.watch + 轮询 fallback |
|
||||
|
||||
### 修改
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `src/cli/bg.ts` | `handleBgFlag()` 改为调用 `selectEngine().start()`;`attachHandler()` 改为调用 `engine.attach()` |
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 1. BgEngine 接口 (`src/cli/bg/engine.ts`)
|
||||
|
||||
```typescript
|
||||
export interface BgEngine {
|
||||
readonly name: string
|
||||
available(): Promise<boolean>
|
||||
start(opts: BgStartOptions): Promise<BgStartResult>
|
||||
attach(session: SessionEntry): Promise<void>
|
||||
}
|
||||
|
||||
export interface BgStartOptions {
|
||||
sessionName: string
|
||||
args: string[] // CLI args (去除 --bg)
|
||||
env: Record<string, string | undefined>
|
||||
logPath: string
|
||||
cwd: string
|
||||
}
|
||||
|
||||
export interface BgStartResult {
|
||||
pid: number
|
||||
sessionName: string
|
||||
logPath: string
|
||||
engineUsed: 'tmux' | 'detached'
|
||||
}
|
||||
```
|
||||
|
||||
### 2. TmuxEngine (`src/cli/bg/engines/tmux.ts`)
|
||||
|
||||
从 `bg.ts:handleBgFlag()` 和 `bg.ts:attachHandler()` 提取:
|
||||
- `available()`: `execFileNoThrow('tmux', ['-V'])` 返回 code === 0
|
||||
- `start()`: `tmux new-session -d -s <name> <cmd>`
|
||||
- `attach()`: `tmux attach-session -t <session.tmuxSessionName>`
|
||||
|
||||
### 3. DetachedEngine (`src/cli/bg/engines/detached.ts`)
|
||||
|
||||
```typescript
|
||||
export class DetachedEngine implements BgEngine {
|
||||
readonly name = 'detached'
|
||||
|
||||
async available(): Promise<boolean> {
|
||||
return true // 总是可用
|
||||
}
|
||||
|
||||
async start(opts: BgStartOptions): Promise<BgStartResult> {
|
||||
const logFd = openSync(opts.logPath, 'a')
|
||||
const child = spawn(process.execPath, [process.argv[1]!, ...opts.args], {
|
||||
detached: true,
|
||||
stdio: ['ignore', logFd, logFd],
|
||||
env: opts.env,
|
||||
cwd: opts.cwd,
|
||||
})
|
||||
child.unref()
|
||||
closeSync(logFd)
|
||||
|
||||
return {
|
||||
pid: child.pid!,
|
||||
sessionName: opts.sessionName,
|
||||
logPath: opts.logPath,
|
||||
engineUsed: 'detached',
|
||||
}
|
||||
}
|
||||
|
||||
async attach(session: SessionEntry): Promise<void> {
|
||||
// 委托给 tail.ts
|
||||
await tailLog(session.logPath!)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 日志 Tail (`src/cli/bg/tail.ts`)
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 跨平台实时日志输出。Ctrl+C 退出,不杀后台进程。
|
||||
*
|
||||
* 策略:
|
||||
* 1. 读取已有内容输出
|
||||
* 2. fs.watch() 监听文件变化 (主方案)
|
||||
* 3. 如果 fs.watch 不可靠 (某些 Windows 网络驱动器),fallback 到 500ms 轮询
|
||||
*/
|
||||
export async function tailLog(logPath: string): Promise<void>
|
||||
```
|
||||
|
||||
### 5. 引擎选择 (`src/cli/bg/engines/index.ts`)
|
||||
|
||||
```typescript
|
||||
export async function selectEngine(): Promise<BgEngine> {
|
||||
if (process.platform === 'win32') {
|
||||
return new DetachedEngine()
|
||||
}
|
||||
const tmux = new TmuxEngine()
|
||||
if (await tmux.available()) {
|
||||
return tmux
|
||||
}
|
||||
return new DetachedEngine()
|
||||
}
|
||||
```
|
||||
|
||||
### 6. bg.ts 重构
|
||||
|
||||
`handleBgFlag()` 改名为 `handleBgStart()`,内部逻辑:
|
||||
```typescript
|
||||
export async function handleBgStart(args: string[]): Promise<void> {
|
||||
const engine = await selectEngine()
|
||||
const sessionName = `claude-bg-${randomUUID().slice(0, 8)}`
|
||||
const logPath = join(getClaudeConfigHomeDir(), 'sessions', 'logs', `${sessionName}.log`)
|
||||
|
||||
const result = await engine.start({
|
||||
sessionName,
|
||||
args: filteredArgs,
|
||||
env: { ...process.env, CLAUDE_CODE_SESSION_KIND: 'bg', ... },
|
||||
logPath,
|
||||
cwd: process.cwd(),
|
||||
})
|
||||
|
||||
console.log(`Background session started: ${result.sessionName}`)
|
||||
console.log(` Engine: ${result.engineUsed}`)
|
||||
console.log(` Log: ${result.logPath}`)
|
||||
console.log(` Use \`claude daemon attach ${result.sessionName}\` to reconnect.`)
|
||||
}
|
||||
```
|
||||
|
||||
`attachHandler()` 根据 `session.engine` 字段选择引擎:
|
||||
```typescript
|
||||
export async function attachHandler(target: string | undefined): Promise<void> {
|
||||
// ... 找到 session
|
||||
if (session.engine === 'tmux' && session.tmuxSessionName) {
|
||||
const tmux = new TmuxEngine()
|
||||
await tmux.attach(session)
|
||||
} else {
|
||||
const detached = new DetachedEngine()
|
||||
await detached.attach(session)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## SessionEntry 扩展
|
||||
|
||||
`sessions/<PID>.json` 新增 `engine` 字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"pid": 12345,
|
||||
"engine": "detached",
|
||||
"logPath": "~/.claude/sessions/logs/claude-bg-a1b2c3d4.log",
|
||||
"sessionId": "...",
|
||||
"cwd": "..."
|
||||
}
|
||||
```
|
||||
|
||||
兼容旧格式: 如果 `engine` 字段缺失,检查 `tmuxSessionName` 存在则为 `tmux`,否则为 `detached`。
|
||||
|
||||
## 验证清单
|
||||
|
||||
- [ ] Windows: `claude daemon bg` 启动后台会话,无 tmux 依赖
|
||||
- [ ] Windows: `claude daemon attach <name>` 以 tail 模式附着,Ctrl+C 退出不杀进程
|
||||
- [ ] macOS/Linux (有 tmux): 行为与当前一致
|
||||
- [ ] macOS/Linux (无 tmux): 自动 fallback 到 detached 引擎
|
||||
- [ ] `claude daemon status` 正确显示 engine 类型
|
||||
- [ ] 旧格式 session JSON (无 engine 字段) 兼容
|
||||
- [ ] tsc --noEmit 零错误
|
||||
- [ ] bun test 通过
|
||||
275
docs/task/task-014-daemon-command-hierarchy.md
Normal file
275
docs/task/task-014-daemon-command-hierarchy.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# Task 014: /daemon 命令层级化
|
||||
|
||||
> 设计文档: [daemon-restructure-design.md](../features/daemon-restructure-design.md) § 三.1
|
||||
> 依赖: Task 013 (BgEngine 抽象)
|
||||
> 分支: `feat/integrate-5-branches`
|
||||
|
||||
## 目标
|
||||
|
||||
将散落的 `daemon start/stop/status` + `ps/logs/attach/kill` + `--bg` 统一收归 `/daemon` 命名空间,实现 CLI + REPL 双注册。
|
||||
|
||||
## 背景
|
||||
|
||||
当前这些命令注册在两个互不关联的位置:
|
||||
- `cli.tsx:203-212`: `daemon [start|status|stop]` → `daemon/main.ts`
|
||||
- `cli.tsx:217-246`: `ps|logs|attach|kill|--bg` → `cli/bg.ts`
|
||||
|
||||
需要合并为统一的 `claude daemon <subcommand>` 入口,并新增 REPL `/daemon` 斜杠命令。
|
||||
|
||||
## 文件清单
|
||||
|
||||
### 新增
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `src/commands/daemon/index.ts` | `/daemon` REPL 斜杠命令注册 (type: local-jsx) |
|
||||
| `src/commands/daemon/daemon.tsx` | `/daemon` 子命令路由 + status UI 组件 |
|
||||
|
||||
### 修改
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `src/entrypoints/cli.tsx` | 统一 daemon 快速路径: `daemon <sub>` 路由到对应 handler。旧命令 `ps/logs/attach/kill` 保留但输出 deprecation 警告后代理 |
|
||||
| `src/commands.ts` | 注册 `/daemon` 斜杠命令 (feature-gated: DAEMON \|\| BG_SESSIONS) |
|
||||
| `src/daemon/main.ts` | `daemonMain()` 扩展: 支持 `bg/attach/logs/kill/ps` 子命令 (委托给 bg.ts handlers) |
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 1. CLI 快速路径统一 (`cli.tsx`)
|
||||
|
||||
**改前** (两段独立路由):
|
||||
```typescript
|
||||
// 段 1: daemon
|
||||
if (feature('DAEMON') && args[0] === 'daemon') {
|
||||
await daemonMain(args.slice(1))
|
||||
}
|
||||
// 段 2: bg sessions
|
||||
if (feature('BG_SESSIONS') && ['ps','logs','attach','kill'].includes(args[0])) {
|
||||
// ...switch/case
|
||||
}
|
||||
```
|
||||
|
||||
**改后** (统一入口):
|
||||
```typescript
|
||||
// 统一 daemon 入口 — 合并 daemon supervisor + bg sessions
|
||||
if (
|
||||
(feature('DAEMON') || feature('BG_SESSIONS')) &&
|
||||
args[0] === 'daemon'
|
||||
) {
|
||||
profileCheckpoint('cli_daemon_path')
|
||||
const { enableConfigs } = await import('../utils/config.js')
|
||||
enableConfigs()
|
||||
const { initSinks } = await import('../utils/sinks.js')
|
||||
initSinks()
|
||||
const { daemonMain } = await import('../daemon/main.js')
|
||||
await daemonMain(args.slice(1))
|
||||
return
|
||||
}
|
||||
|
||||
// --bg 快捷方式 → daemon bg
|
||||
if (
|
||||
feature('BG_SESSIONS') &&
|
||||
(args.includes('--bg') || args.includes('--background'))
|
||||
) {
|
||||
profileCheckpoint('cli_daemon_path')
|
||||
const { enableConfigs } = await import('../utils/config.js')
|
||||
enableConfigs()
|
||||
const bg = await import('../cli/bg.js')
|
||||
await bg.handleBgStart(args.filter(a => a !== '--bg' && a !== '--background'))
|
||||
return
|
||||
}
|
||||
|
||||
// 向后兼容: ps/logs/attach/kill → daemon <sub> (deprecated)
|
||||
if (
|
||||
feature('BG_SESSIONS') &&
|
||||
['ps', 'logs', 'attach', 'kill'].includes(args[0] ?? '')
|
||||
) {
|
||||
const mapped = args[0] === 'ps' ? 'status' : args[0]
|
||||
console.error(`[deprecated] Use: claude daemon ${mapped} ${args.slice(1).join(' ')}`.trim())
|
||||
const { enableConfigs } = await import('../utils/config.js')
|
||||
enableConfigs()
|
||||
const { daemonMain } = await import('../daemon/main.js')
|
||||
await daemonMain([args[0]!, ...args.slice(1)])
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
### 2. daemonMain 扩展 (`daemon/main.ts`)
|
||||
|
||||
```typescript
|
||||
export async function daemonMain(args: string[]): Promise<void> {
|
||||
const subcommand = args[0] || 'status'
|
||||
|
||||
switch (subcommand) {
|
||||
// --- Supervisor 管理 ---
|
||||
case 'start':
|
||||
await runSupervisor(args.slice(1))
|
||||
break
|
||||
case 'stop':
|
||||
await handleDaemonStop()
|
||||
break
|
||||
|
||||
// --- 会话管理 (委托给 bg.ts) ---
|
||||
case 'status':
|
||||
case 'ps':
|
||||
await showUnifiedStatus() // 新: daemon 状态 + 会话列表
|
||||
break
|
||||
case 'bg':
|
||||
const bg = await import('../cli/bg.js')
|
||||
await bg.handleBgStart(args.slice(1))
|
||||
break
|
||||
case 'attach':
|
||||
const bg2 = await import('../cli/bg.js')
|
||||
await bg2.attachHandler(args[1])
|
||||
break
|
||||
case 'logs':
|
||||
const bg3 = await import('../cli/bg.js')
|
||||
await bg3.logsHandler(args[1])
|
||||
break
|
||||
case 'kill':
|
||||
const bg4 = await import('../cli/bg.js')
|
||||
await bg4.killHandler(args[1])
|
||||
break
|
||||
|
||||
case '--help': case '-h': case 'help':
|
||||
printHelp()
|
||||
break
|
||||
default:
|
||||
console.error(`Unknown daemon subcommand: ${subcommand}`)
|
||||
printHelp()
|
||||
process.exitCode = 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 统一状态面板 (`showUnifiedStatus`)
|
||||
|
||||
```typescript
|
||||
async function showUnifiedStatus(): Promise<void> {
|
||||
// 1. Daemon supervisor 状态
|
||||
const daemonResult = queryDaemonStatus()
|
||||
console.log('=== Daemon Supervisor ===')
|
||||
switch (daemonResult.status) {
|
||||
case 'running':
|
||||
console.log(` Status: running (PID: ${daemonResult.state!.pid})`)
|
||||
console.log(` Workers: ${daemonResult.state!.workerKinds.join(', ')}`)
|
||||
break
|
||||
case 'stopped':
|
||||
console.log(' Status: stopped')
|
||||
break
|
||||
case 'stale':
|
||||
console.log(' Status: stale (cleaned up)')
|
||||
break
|
||||
}
|
||||
|
||||
// 2. 后台会话列表
|
||||
console.log('\n=== Background Sessions ===')
|
||||
const bg = await import('../cli/bg.js')
|
||||
await bg.psHandler([])
|
||||
}
|
||||
```
|
||||
|
||||
### 4. REPL 斜杠命令注册
|
||||
|
||||
**`src/commands/daemon/index.ts`**:
|
||||
```typescript
|
||||
import type { Command } from '../../commands.js'
|
||||
import { feature } from 'bun:bundle'
|
||||
|
||||
const daemon = {
|
||||
type: 'local-jsx',
|
||||
name: 'daemon',
|
||||
description: 'Manage background sessions and daemon',
|
||||
argumentHint: '[status|start|stop|bg|attach|logs|kill]',
|
||||
isEnabled: () => {
|
||||
if (feature('DAEMON')) return true
|
||||
if (feature('BG_SESSIONS')) return true
|
||||
return false
|
||||
},
|
||||
load: () => import('./daemon.js'),
|
||||
} satisfies Command
|
||||
|
||||
export default daemon
|
||||
```
|
||||
|
||||
**`src/commands/daemon/daemon.tsx`**:
|
||||
```typescript
|
||||
export async function call(
|
||||
onDone: LocalJSXCommandOnDone,
|
||||
context: LocalJSXCommandContext,
|
||||
args: string,
|
||||
): Promise<React.ReactNode> {
|
||||
const parts = args.trim().split(/\s+/)
|
||||
const sub = parts[0] || 'status'
|
||||
|
||||
switch (sub) {
|
||||
case 'status':
|
||||
case 'ps':
|
||||
// 调用 showUnifiedStatus,捕获输出
|
||||
// 返回文本结果
|
||||
break
|
||||
case 'bg':
|
||||
// REPL 中启动后台会话
|
||||
break
|
||||
case 'start':
|
||||
case 'stop':
|
||||
case 'attach':
|
||||
case 'logs':
|
||||
case 'kill':
|
||||
// 委托给对应 handler
|
||||
break
|
||||
default:
|
||||
onDone(`Unknown: ${sub}. Use: status|start|stop|bg|attach|logs|kill`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**`src/commands.ts`** 添加:
|
||||
```typescript
|
||||
// 条件导入
|
||||
const daemonCmd =
|
||||
feature('DAEMON') || feature('BG_SESSIONS')
|
||||
? require('./commands/daemon/index.js').default
|
||||
: null
|
||||
|
||||
// COMMANDS 数组中添加
|
||||
...(daemonCmd ? [daemonCmd] : []),
|
||||
```
|
||||
|
||||
### 5. 更新 help 文本 (`daemon/main.ts`)
|
||||
|
||||
```
|
||||
Claude Code Daemon — background process management
|
||||
|
||||
USAGE
|
||||
claude daemon [subcommand]
|
||||
|
||||
SUBCOMMANDS
|
||||
status Show daemon and session status (default)
|
||||
start Start the daemon supervisor
|
||||
stop Stop the daemon
|
||||
bg Start a background session
|
||||
attach Attach to a background session
|
||||
logs Show session logs
|
||||
kill Kill a session
|
||||
help Show this help
|
||||
|
||||
REPL
|
||||
/daemon [subcommand] Same commands available in interactive mode
|
||||
```
|
||||
|
||||
## 验证清单
|
||||
|
||||
- [ ] `claude daemon` (无参数) 显示统一状态面板
|
||||
- [ ] `claude daemon status` 显示 supervisor + 会话列表
|
||||
- [ ] `claude daemon start/stop` 与当前行为一致
|
||||
- [ ] `claude daemon bg` 启动后台会话 (调用 BgEngine)
|
||||
- [ ] `claude daemon attach/logs/kill <target>` 功能正常
|
||||
- [ ] `claude ps` 输出 deprecation 警告 + 正常工作
|
||||
- [ ] `claude logs/attach/kill` 同上
|
||||
- [ ] `claude --bg` 快捷方式正常
|
||||
- [ ] REPL 中 `/daemon` 可用,tab 补全显示
|
||||
- [ ] REPL 中 `/daemon status` 显示状态信息
|
||||
- [ ] tsc --noEmit 零错误
|
||||
- [ ] bun test 通过
|
||||
177
docs/task/task-015-job-command-hierarchy.md
Normal file
177
docs/task/task-015-job-command-hierarchy.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# Task 015: /job 命令层级化
|
||||
|
||||
> 设计文档: [daemon-restructure-design.md](../features/daemon-restructure-design.md) § 三.2
|
||||
> 依赖: 无 (可与 Task 013 并行)
|
||||
> 分支: `feat/integrate-5-branches`
|
||||
|
||||
## 目标
|
||||
|
||||
将 `claude new/list/reply` 收归 `/job` 命名空间,实现 CLI + REPL 双注册。
|
||||
|
||||
## 背景
|
||||
|
||||
当前 `new`, `list`, `reply` 是顶级 CLI 命令 (`cli.tsx:250-261`),容易与其他命令冲突(特别是 `list` 这种通用词)。需要收归 `claude job <subcommand>` 并新增 REPL `/job` 入口。
|
||||
|
||||
## 文件清单
|
||||
|
||||
### 新增
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `src/commands/job/index.ts` | `/job` REPL 斜杠命令注册 |
|
||||
| `src/commands/job/job.tsx` | `/job` 子命令路由 |
|
||||
|
||||
### 修改
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `src/entrypoints/cli.tsx` | 新增 `job` 快速路径 + 旧 `new/list/reply` deprecation 代理 |
|
||||
| `src/commands.ts` | 注册 `/job` 斜杠命令 |
|
||||
|
||||
### 不动
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `src/cli/handlers/templateJobs.ts` | 内部 handler 不变,只是被调用方式变了 |
|
||||
| `src/jobs/state.ts` | job 状态管理不变 |
|
||||
| `src/jobs/templates.ts` | 模板发现不变 |
|
||||
| `src/jobs/classifier.ts` | 任务分类器不变 |
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 1. CLI 快速路径 (`cli.tsx`)
|
||||
|
||||
**改后**:
|
||||
```typescript
|
||||
// 新: claude job <subcommand>
|
||||
if (
|
||||
feature('TEMPLATES') &&
|
||||
args[0] === 'job'
|
||||
) {
|
||||
profileCheckpoint('cli_templates_path')
|
||||
const { templatesMain } = await import('../cli/handlers/templateJobs.js')
|
||||
await templatesMain(args.slice(1))
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// 向后兼容 (deprecated)
|
||||
if (
|
||||
feature('TEMPLATES') &&
|
||||
(args[0] === 'new' || args[0] === 'list' || args[0] === 'reply')
|
||||
) {
|
||||
console.error(`[deprecated] Use: claude job ${args[0]} ${args.slice(1).join(' ')}`.trim())
|
||||
profileCheckpoint('cli_templates_path')
|
||||
const { templatesMain } = await import('../cli/handlers/templateJobs.js')
|
||||
await templatesMain(args)
|
||||
process.exit(0)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. templateJobs.ts 新增 status 子命令
|
||||
|
||||
在现有 `switch` 中增加:
|
||||
```typescript
|
||||
case 'status':
|
||||
handleStatus(args.slice(1))
|
||||
break
|
||||
```
|
||||
|
||||
```typescript
|
||||
function handleStatus(args: string[]): void {
|
||||
const jobId = args[0]
|
||||
if (!jobId) {
|
||||
console.error('Usage: claude job status <job-id>')
|
||||
process.exitCode = 1
|
||||
return
|
||||
}
|
||||
const state = readJobState(jobId)
|
||||
if (!state) {
|
||||
console.error(`Job not found: ${jobId}`)
|
||||
process.exitCode = 1
|
||||
return
|
||||
}
|
||||
console.log(`Job: ${state.jobId}`)
|
||||
console.log(` Template: ${state.templateName}`)
|
||||
console.log(` Status: ${state.status}`)
|
||||
console.log(` Created: ${state.createdAt}`)
|
||||
console.log(` Updated: ${state.updatedAt}`)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. REPL 斜杠命令
|
||||
|
||||
**`src/commands/job/index.ts`**:
|
||||
```typescript
|
||||
import type { Command } from '../../commands.js'
|
||||
import { feature } from 'bun:bundle'
|
||||
|
||||
const job = {
|
||||
type: 'local-jsx',
|
||||
name: 'job',
|
||||
description: 'Manage template jobs',
|
||||
argumentHint: '[list|new|reply|status]',
|
||||
isEnabled: () => {
|
||||
if (feature('TEMPLATES')) return true
|
||||
return false
|
||||
},
|
||||
load: () => import('./job.js'),
|
||||
} satisfies Command
|
||||
|
||||
export default job
|
||||
```
|
||||
|
||||
**`src/commands/job/job.tsx`**:
|
||||
```typescript
|
||||
export async function call(
|
||||
onDone: LocalJSXCommandOnDone,
|
||||
_context: LocalJSXCommandContext,
|
||||
args: string,
|
||||
): Promise<React.ReactNode> {
|
||||
const parts = args.trim().split(/\s+/)
|
||||
const sub = parts[0] || 'list'
|
||||
|
||||
// 委托给 templatesMain
|
||||
const { templatesMain } = await import('../../cli/handlers/templateJobs.js')
|
||||
|
||||
// 捕获 console.log 输出作为结果返回给 REPL
|
||||
const lines: string[] = []
|
||||
const origLog = console.log
|
||||
const origError = console.error
|
||||
console.log = (...a: unknown[]) => lines.push(a.join(' '))
|
||||
console.error = (...a: unknown[]) => lines.push(a.join(' '))
|
||||
|
||||
try {
|
||||
await templatesMain([sub, ...parts.slice(1)])
|
||||
} finally {
|
||||
console.log = origLog
|
||||
console.error = origError
|
||||
}
|
||||
|
||||
onDone(lines.join('\n') || 'Done.', { display: 'system' })
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
### 4. commands.ts 注册
|
||||
|
||||
```typescript
|
||||
const jobCmd = feature('TEMPLATES')
|
||||
? require('./commands/job/index.js').default
|
||||
: null
|
||||
|
||||
// COMMANDS 数组:
|
||||
...(jobCmd ? [jobCmd] : []),
|
||||
```
|
||||
|
||||
## 验证清单
|
||||
|
||||
- [ ] `claude job list` 列出模板
|
||||
- [ ] `claude job new <template>` 创建任务
|
||||
- [ ] `claude job reply <id> <text>` 回复任务
|
||||
- [ ] `claude job status <id>` 显示任务状态
|
||||
- [ ] `claude job` (无参数) 等同于 `claude job list`
|
||||
- [ ] `claude new/list/reply` 输出 deprecation 警告 + 正常工作
|
||||
- [ ] REPL 中 `/job` 可用
|
||||
- [ ] REPL 中 `/job list` 显示模板列表
|
||||
- [ ] tsc --noEmit 零错误
|
||||
- [ ] bun test 通过
|
||||
123
docs/task/task-016-backward-compat-tests.md
Normal file
123
docs/task/task-016-backward-compat-tests.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Task 016: 向后兼容 + 测试
|
||||
|
||||
> 设计文档: [daemon-restructure-design.md](../features/daemon-restructure-design.md) § 五
|
||||
> 依赖: Task 014, Task 015
|
||||
> 分支: `feat/integrate-5-branches`
|
||||
|
||||
## 目标
|
||||
|
||||
确保旧命令向后兼容 (deprecation 警告 + 正常代理),并为重构后的命令结构编写测试。
|
||||
|
||||
## 文件清单
|
||||
|
||||
### 新增
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `src/daemon/__tests__/daemonMain.test.ts` | daemonMain 子命令路由测试 |
|
||||
| `src/cli/bg/__tests__/engine.test.ts` | BgEngine 选择逻辑测试 |
|
||||
| `src/cli/bg/__tests__/detached.test.ts` | DetachedEngine 启动/停止测试 |
|
||||
| `src/cli/bg/__tests__/tail.test.ts` | 日志 tail 功能测试 |
|
||||
|
||||
### 修改
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `src/entrypoints/cli.tsx` | 确认 deprecation 路径正确代理 |
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 1. 向后兼容矩阵
|
||||
|
||||
| 旧命令 | 新命令 | 处理方式 |
|
||||
|--------|--------|---------|
|
||||
| `claude ps` | `claude daemon status` | stderr 输出 `[deprecated] Use: claude daemon status`,然后执行 |
|
||||
| `claude logs <x>` | `claude daemon logs <x>` | 同上 |
|
||||
| `claude attach <x>` | `claude daemon attach <x>` | 同上 |
|
||||
| `claude kill <x>` | `claude daemon kill <x>` | 同上 |
|
||||
| `claude --bg` | `claude daemon bg` | 保留为快捷方式,**不** deprecate (太常用) |
|
||||
| `claude new <t>` | `claude job new <t>` | stderr deprecation + 执行 |
|
||||
| `claude list` | `claude job list` | stderr deprecation + 执行 |
|
||||
| `claude reply <id>` | `claude job reply <id>` | stderr deprecation + 执行 |
|
||||
|
||||
**关键**: deprecation 输出到 stderr 而非 stdout,不影响脚本管道。
|
||||
|
||||
### 2. 测试计划
|
||||
|
||||
#### 2.1 daemonMain 路由测试
|
||||
|
||||
```typescript
|
||||
describe('daemonMain', () => {
|
||||
test('无参数默认 status', async () => { ... })
|
||||
test('start 调用 runSupervisor', async () => { ... })
|
||||
test('stop 调用 handleDaemonStop', async () => { ... })
|
||||
test('bg 委托给 bg.handleBgStart', async () => { ... })
|
||||
test('attach 委托给 bg.attachHandler', async () => { ... })
|
||||
test('logs 委托给 bg.logsHandler', async () => { ... })
|
||||
test('kill 委托给 bg.killHandler', async () => { ... })
|
||||
test('未知子命令设置 exitCode=1', async () => { ... })
|
||||
})
|
||||
```
|
||||
|
||||
#### 2.2 引擎选择测试
|
||||
|
||||
```typescript
|
||||
describe('selectEngine', () => {
|
||||
test('win32 返回 DetachedEngine', async () => { ... })
|
||||
test('darwin + tmux 可用返回 TmuxEngine', async () => { ... })
|
||||
test('darwin + tmux 不可用返回 DetachedEngine', async () => { ... })
|
||||
test('linux + tmux 可用返回 TmuxEngine', async () => { ... })
|
||||
})
|
||||
```
|
||||
|
||||
#### 2.3 DetachedEngine 测试
|
||||
|
||||
```typescript
|
||||
describe('DetachedEngine', () => {
|
||||
test('available 始终返回 true', async () => { ... })
|
||||
test('start 创建 detached 子进程并写入日志', async () => { ... })
|
||||
test('start 返回的 PID 文件存在', async () => { ... })
|
||||
})
|
||||
```
|
||||
|
||||
#### 2.4 Tail 测试
|
||||
|
||||
```typescript
|
||||
describe('tailLog', () => {
|
||||
test('输出已有日志内容', async () => { ... })
|
||||
test('追加内容时实时输出', async () => { ... })
|
||||
test('SIGINT 退出 tail', async () => { ... })
|
||||
})
|
||||
```
|
||||
|
||||
### 3. 集成验证脚本
|
||||
|
||||
可选: 在 `scripts/` 下添加一个手动验证脚本:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# scripts/verify-daemon-restructure.sh
|
||||
echo "=== 1. claude daemon status ==="
|
||||
bun run dev -- daemon status
|
||||
|
||||
echo "=== 2. claude daemon bg (should start) ==="
|
||||
bun run dev -- daemon bg --help
|
||||
|
||||
echo "=== 3. claude ps (deprecated) ==="
|
||||
bun run dev -- ps 2>&1 | head -1
|
||||
|
||||
echo "=== 4. claude job list ==="
|
||||
bun run dev -- job list
|
||||
|
||||
echo "=== 5. claude list (deprecated) ==="
|
||||
bun run dev -- list 2>&1 | head -1
|
||||
```
|
||||
|
||||
## 验证清单
|
||||
|
||||
- [ ] 旧命令全部正常工作 (仅多一行 stderr 警告)
|
||||
- [ ] `--bg` 保持无警告
|
||||
- [ ] 所有新增测试通过
|
||||
- [ ] 现有 2695 个测试无回归
|
||||
- [ ] tsc --noEmit 零错误
|
||||
- [ ] 手动在 Windows + macOS/Linux 上验证关键路径
|
||||
@@ -1,147 +0,0 @@
|
||||
# Tool 系统测试计划
|
||||
|
||||
## 概述
|
||||
|
||||
Tool 系统是 Claude Code 的核心,负责工具的定义、注册、发现和过滤。本计划覆盖 `src/Tool.ts` 中的工具接口与工具函数、`src/tools.ts` 中的注册/过滤逻辑,以及各工具目录下可独立测试的纯函数。
|
||||
|
||||
## 被测文件
|
||||
|
||||
| 文件 | 关键导出 |
|
||||
|------|----------|
|
||||
| `src/Tool.ts` | `buildTool`, `toolMatchesName`, `findToolByName`, `getEmptyToolPermissionContext`, `filterToolProgressMessages` |
|
||||
| `src/tools.ts` | `parseToolPreset`, `filterToolsByDenyRules`, `getAllBaseTools`, `getTools`, `assembleToolPool` |
|
||||
| `src/tools/shared/gitOperationTracking.ts` | `parseGitCommitId`, `detectGitOperation` |
|
||||
| `src/tools/shared/spawnMultiAgent.ts` | `resolveTeammateModel`, `generateUniqueTeammateName` |
|
||||
| `src/tools/GrepTool/GrepTool.ts` | `applyHeadLimit`, `formatLimitInfo`(内部辅助函数) |
|
||||
| `src/tools/FileEditTool/utils.ts` | 字符串匹配/补丁相关纯函数 |
|
||||
|
||||
---
|
||||
|
||||
## 测试用例
|
||||
|
||||
### src/Tool.ts
|
||||
|
||||
#### describe('buildTool')
|
||||
|
||||
- test('fills in default isEnabled as true') — 不传 isEnabled 时,构建的 tool.isEnabled() 应返回 true
|
||||
- test('fills in default isConcurrencySafe as false') — 默认值应为 false(fail-closed)
|
||||
- test('fills in default isReadOnly as false') — 默认假设有写操作
|
||||
- test('fills in default isDestructive as false') — 默认非破坏性
|
||||
- test('fills in default checkPermissions as allow') — 默认 checkPermissions 应返回 `{ behavior: 'allow', updatedInput }`
|
||||
- test('fills in default userFacingName from tool name') — userFacingName 默认应返回 tool.name
|
||||
- test('preserves explicitly provided methods') — 传入自定义 isEnabled 等方法时应覆盖默认值
|
||||
- test('preserves all non-defaultable properties') — name, inputSchema, call, description 等属性原样保留
|
||||
|
||||
#### describe('toolMatchesName')
|
||||
|
||||
- test('returns true for exact name match') — `{ name: 'Bash' }` 匹配 'Bash'
|
||||
- test('returns false for non-matching name') — `{ name: 'Bash' }` 不匹配 'Read'
|
||||
- test('returns true when name matches an alias') — `{ name: 'Bash', aliases: ['BashTool'] }` 匹配 'BashTool'
|
||||
- test('returns false when aliases is undefined') — `{ name: 'Bash' }` 不匹配 'BashTool'
|
||||
- test('returns false when aliases is empty') — `{ name: 'Bash', aliases: [] }` 不匹配 'BashTool'
|
||||
|
||||
#### describe('findToolByName')
|
||||
|
||||
- test('finds tool by primary name') — 从 tools 列表中按 name 找到工具
|
||||
- test('finds tool by alias') — 从 tools 列表中按 alias 找到工具
|
||||
- test('returns undefined when no match') — 找不到时返回 undefined
|
||||
- test('returns first match when duplicates exist') — 多个同名工具时返回第一个
|
||||
|
||||
#### describe('getEmptyToolPermissionContext')
|
||||
|
||||
- test('returns default permission mode') — mode 应为 'default'
|
||||
- test('returns empty maps and arrays') — additionalWorkingDirectories 为空 Map,rules 为空对象
|
||||
- test('returns isBypassPermissionsModeAvailable as false')
|
||||
|
||||
#### describe('filterToolProgressMessages')
|
||||
|
||||
- test('filters out hook_progress messages') — 移除 type 为 hook_progress 的消息
|
||||
- test('keeps tool progress messages') — 保留非 hook_progress 的消息
|
||||
- test('returns empty array for empty input')
|
||||
- test('handles messages without type field') — data 不含 type 时应保留
|
||||
|
||||
---
|
||||
|
||||
### src/tools.ts
|
||||
|
||||
#### describe('parseToolPreset')
|
||||
|
||||
- test('returns "default" for "default" input') — 精确匹配
|
||||
- test('returns "default" for "Default" input') — 大小写不敏感
|
||||
- test('returns null for unknown preset') — 未知字符串返回 null
|
||||
- test('returns null for empty string')
|
||||
|
||||
#### describe('filterToolsByDenyRules')
|
||||
|
||||
- test('returns all tools when no deny rules') — 空 deny 规则不过滤任何工具
|
||||
- test('filters out tools matching blanket deny rule') — deny rule `{ toolName: 'Bash' }` 应移除 Bash
|
||||
- test('does not filter tools with content-specific deny rules') — deny rule `{ toolName: 'Bash', ruleContent: 'rm -rf' }` 不移除 Bash(只在运行时阻止特定命令)
|
||||
- test('filters MCP tools by server name prefix') — deny rule `mcp__server` 应移除该 server 下所有工具
|
||||
- test('preserves tools not matching any deny rule')
|
||||
|
||||
#### describe('getAllBaseTools')
|
||||
|
||||
- test('returns a non-empty array of tools') — 至少包含核心工具
|
||||
- test('each tool has required properties') — 每个工具应有 name, inputSchema, call 等属性
|
||||
- test('includes BashTool, FileReadTool, FileEditTool') — 核心工具始终存在
|
||||
- test('includes TestingPermissionTool when NODE_ENV is test') — 需设置 env
|
||||
|
||||
#### describe('getTools')
|
||||
|
||||
- test('returns filtered tools based on permission context') — 根据 deny rules 过滤
|
||||
- test('returns simple tools in CLAUDE_CODE_SIMPLE mode') — 仅返回 Bash/Read/Edit
|
||||
- test('filters disabled tools via isEnabled') — isEnabled 返回 false 的工具被排除
|
||||
|
||||
---
|
||||
|
||||
### src/tools/shared/gitOperationTracking.ts
|
||||
|
||||
#### describe('parseGitCommitId')
|
||||
|
||||
- test('extracts commit hash from git commit output') — 从 `[main abc1234] message` 中提取 `abc1234`
|
||||
- test('returns null for non-commit output') — 无法解析时返回 null
|
||||
- test('handles various branch name formats') — `[feature/foo abc1234]` 等
|
||||
|
||||
#### describe('detectGitOperation')
|
||||
|
||||
- test('detects git commit operation') — 命令含 `git commit` 时识别为 commit
|
||||
- test('detects git push operation') — 命令含 `git push` 时识别
|
||||
- test('returns null for non-git commands') — 非 git 命令返回 null
|
||||
- test('detects git merge operation')
|
||||
- test('detects git rebase operation')
|
||||
|
||||
---
|
||||
|
||||
### src/tools/shared/spawnMultiAgent.ts
|
||||
|
||||
#### describe('resolveTeammateModel')
|
||||
|
||||
- test('returns specified model when provided')
|
||||
- test('falls back to default model when not specified')
|
||||
|
||||
#### describe('generateUniqueTeammateName')
|
||||
|
||||
- test('generates a name when no existing names') — 无冲突时返回基础名
|
||||
- test('appends suffix when name conflicts') — 与已有名称冲突时添加后缀
|
||||
- test('handles multiple conflicts') — 多次冲突时递增后缀
|
||||
|
||||
---
|
||||
|
||||
## Mock 需求
|
||||
|
||||
| 依赖 | Mock 方式 | 说明 |
|
||||
|------|-----------|------|
|
||||
| `bun:bundle` (feature) | 已 polyfill 为 `() => false` | 不需额外 mock |
|
||||
| `process.env` | `bun:test` mock | 测试 `USER_TYPE`、`NODE_ENV`、`CLAUDE_CODE_SIMPLE` |
|
||||
| `getDenyRuleForTool` | mock module | `filterToolsByDenyRules` 测试中需控制返回值 |
|
||||
| `isToolSearchEnabledOptimistic` | mock module | `getAllBaseTools` 中条件加载 |
|
||||
|
||||
## 集成测试场景
|
||||
|
||||
放在 `tests/integration/tool-chain.test.ts`:
|
||||
|
||||
### describe('Tool registration and discovery')
|
||||
|
||||
- test('getAllBaseTools returns tools that can be found by findToolByName') — 注册 → 查找完整链路
|
||||
- test('filterToolsByDenyRules + getTools produces consistent results') — 过滤管线一致性
|
||||
- test('assembleToolPool deduplicates built-in and MCP tools') — 合并去重逻辑
|
||||
@@ -1,416 +0,0 @@
|
||||
# 工具函数(纯函数)测试计划
|
||||
|
||||
## 概述
|
||||
|
||||
覆盖 `src/utils/` 下所有可独立单元测试的纯函数。这些函数无外部依赖,输入输出确定性强,是测试金字塔的底层基石。
|
||||
|
||||
## 被测文件
|
||||
|
||||
| 文件 | 状态 | 关键导出 |
|
||||
|------|------|----------|
|
||||
| `src/utils/array.ts` | **已有测试** | intersperse, count, uniq |
|
||||
| `src/utils/set.ts` | **已有测试** | difference, intersects, every, union |
|
||||
| `src/utils/xml.ts` | 待测 | escapeXml, escapeXmlAttr |
|
||||
| `src/utils/hash.ts` | 待测 | djb2Hash, hashContent, hashPair |
|
||||
| `src/utils/stringUtils.ts` | 待测 | escapeRegExp, capitalize, plural, firstLineOf, countCharInString, normalizeFullWidthDigits, normalizeFullWidthSpace, safeJoinLines, truncateToLines, EndTruncatingAccumulator |
|
||||
| `src/utils/semver.ts` | 待测 | gt, gte, lt, lte, satisfies, order |
|
||||
| `src/utils/uuid.ts` | 待测 | validateUuid, createAgentId |
|
||||
| `src/utils/format.ts` | 待测 | formatFileSize, formatSecondsShort, formatDuration, formatNumber, formatTokens, formatRelativeTime, formatRelativeTimeAgo |
|
||||
| `src/utils/json.ts` | 待测 | safeParseJSON, safeParseJSONC, parseJSONL, addItemToJSONCArray |
|
||||
| `src/utils/truncate.ts` | 待测 | truncatePathMiddle, truncateToWidth, truncateStartToWidth, truncateToWidthNoEllipsis, truncate, wrapText |
|
||||
| `src/utils/diff.ts` | 待测 | adjustHunkLineNumbers, getPatchFromContents |
|
||||
| `src/utils/frontmatterParser.ts` | 待测 | parseFrontmatter, splitPathInFrontmatter, parsePositiveIntFromFrontmatter, parseBooleanFrontmatter, parseShellFrontmatter |
|
||||
| `src/utils/file.ts` | 待测(纯函数部分) | convertLeadingTabsToSpaces, addLineNumbers, stripLineNumberPrefix, pathsEqual, normalizePathForComparison |
|
||||
| `src/utils/glob.ts` | 待测(纯函数部分) | extractGlobBaseDirectory |
|
||||
| `src/utils/tokens.ts` | 待测 | getTokenCountFromUsage |
|
||||
| `src/utils/path.ts` | 待测(纯函数部分) | containsPathTraversal, normalizePathForConfigKey |
|
||||
|
||||
---
|
||||
|
||||
## 测试用例
|
||||
|
||||
### src/utils/xml.ts — 测试文件: `src/utils/__tests__/xml.test.ts`
|
||||
|
||||
#### describe('escapeXml')
|
||||
|
||||
- test('escapes ampersand') — `&` → `&`
|
||||
- test('escapes less-than') — `<` → `<`
|
||||
- test('escapes greater-than') — `>` → `>`
|
||||
- test('does not escape quotes') — `"` 和 `'` 保持原样
|
||||
- test('handles empty string') — `""` → `""`
|
||||
- test('handles string with no special chars') — `"hello"` 原样返回
|
||||
- test('escapes multiple special chars in one string') — `<a & b>` → `<a & b>`
|
||||
|
||||
#### describe('escapeXmlAttr')
|
||||
|
||||
- test('escapes all xml chars plus quotes') — `"` → `"`, `'` → `'`
|
||||
- test('escapes double quotes') — `he said "hi"` 正确转义
|
||||
- test('escapes single quotes') — `it's` 正确转义
|
||||
|
||||
---
|
||||
|
||||
### src/utils/hash.ts — 测试文件: `src/utils/__tests__/hash.test.ts`
|
||||
|
||||
#### describe('djb2Hash')
|
||||
|
||||
- test('returns consistent hash for same input') — 相同输入返回相同结果
|
||||
- test('returns different hashes for different inputs') — 不同输入大概率不同
|
||||
- test('returns a 32-bit integer') — 结果在 int32 范围内
|
||||
- test('handles empty string') — 空字符串有确定的哈希值
|
||||
- test('handles unicode strings') — 中文/emoji 等正确处理
|
||||
|
||||
#### describe('hashContent')
|
||||
|
||||
- test('returns consistent hash for same content') — 确定性
|
||||
- test('returns string result') — 返回值为字符串
|
||||
|
||||
#### describe('hashPair')
|
||||
|
||||
- test('returns consistent hash for same pair') — 确定性
|
||||
- test('order matters') — hashPair(a, b) ≠ hashPair(b, a)
|
||||
- test('handles empty strings')
|
||||
|
||||
---
|
||||
|
||||
### src/utils/stringUtils.ts — 测试文件: `src/utils/__tests__/stringUtils.test.ts`
|
||||
|
||||
#### describe('escapeRegExp')
|
||||
|
||||
- test('escapes dots') — `.` → `\\.`
|
||||
- test('escapes asterisks') — `*` → `\\*`
|
||||
- test('escapes brackets') — `[` → `\\[`
|
||||
- test('escapes all special chars') — `.*+?^${}()|[]\` 全部转义
|
||||
- test('leaves normal chars unchanged') — `hello` 原样
|
||||
- test('escaped string works in RegExp') — `new RegExp(escapeRegExp('a.b'))` 精确匹配 `a.b`
|
||||
|
||||
#### describe('capitalize')
|
||||
|
||||
- test('uppercases first char') — `"foo"` → `"Foo"`
|
||||
- test('does NOT lowercase rest') — `"fooBar"` → `"FooBar"`(区别于 lodash capitalize)
|
||||
- test('handles single char') — `"a"` → `"A"`
|
||||
- test('handles empty string') — `""` → `""`
|
||||
- test('handles already capitalized') — `"Foo"` → `"Foo"`
|
||||
|
||||
#### describe('plural')
|
||||
|
||||
- test('returns singular for n=1') — `plural(1, 'file')` → `'file'`
|
||||
- test('returns plural for n=0') — `plural(0, 'file')` → `'files'`
|
||||
- test('returns plural for n>1') — `plural(3, 'file')` → `'files'`
|
||||
- test('uses custom plural form') — `plural(2, 'entry', 'entries')` → `'entries'`
|
||||
|
||||
#### describe('firstLineOf')
|
||||
|
||||
- test('returns first line of multi-line string') — `"a\nb\nc"` → `"a"`
|
||||
- test('returns full string when no newline') — `"hello"` → `"hello"`
|
||||
- test('handles empty string') — `""` → `""`
|
||||
- test('handles string starting with newline') — `"\nhello"` → `""`
|
||||
|
||||
#### describe('countCharInString')
|
||||
|
||||
- test('counts occurrences') — `countCharInString("aabac", "a")` → `3`
|
||||
- test('returns 0 when char not found') — `countCharInString("hello", "x")` → `0`
|
||||
- test('handles empty string') — `countCharInString("", "a")` → `0`
|
||||
- test('respects start position') — `countCharInString("aaba", "a", 2)` → `1`
|
||||
|
||||
#### describe('normalizeFullWidthDigits')
|
||||
|
||||
- test('converts full-width digits to half-width') — `"0123"` → `"0123"`
|
||||
- test('leaves half-width digits unchanged') — `"0123"` → `"0123"`
|
||||
- test('mixed content') — `"port 8080"` → `"port 8080"`
|
||||
|
||||
#### describe('normalizeFullWidthSpace')
|
||||
|
||||
- test('converts ideographic space to regular space') — `"\u3000"` → `" "`
|
||||
- test('converts multiple spaces') — `"a\u3000b\u3000c"` → `"a b c"`
|
||||
|
||||
#### describe('safeJoinLines')
|
||||
|
||||
- test('joins lines with default delimiter') — `["a","b"]` → `"a,b"`
|
||||
- test('truncates when exceeding maxSize') — 超限时截断并添加 `...[truncated]`
|
||||
- test('handles empty array') — `[]` → `""`
|
||||
- test('uses custom delimiter') — delimiter 为 `"\n"` 时按行连接
|
||||
|
||||
#### describe('truncateToLines')
|
||||
|
||||
- test('returns full text when within limit') — 行数不超限时原样返回
|
||||
- test('truncates and adds ellipsis') — 超限时截断并加 `…`
|
||||
- test('handles exact limit') — 刚好等于 maxLines 时不截断
|
||||
- test('handles single line') — 单行文本不截断
|
||||
|
||||
#### describe('EndTruncatingAccumulator')
|
||||
|
||||
- test('accumulates strings normally within limit')
|
||||
- test('truncates when exceeding maxSize')
|
||||
- test('reports truncated status correctly')
|
||||
- test('reports totalBytes including truncated content')
|
||||
- test('toString includes truncation marker')
|
||||
- test('clear resets all state')
|
||||
- test('append with Buffer works') — 接受 Buffer 类型
|
||||
|
||||
---
|
||||
|
||||
### src/utils/semver.ts — 测试文件: `src/utils/__tests__/semver.test.ts`
|
||||
|
||||
#### describe('gt / gte / lt / lte')
|
||||
|
||||
- test('gt: 2.0.0 > 1.0.0') → true
|
||||
- test('gt: 1.0.0 > 1.0.0') → false
|
||||
- test('gte: 1.0.0 >= 1.0.0') → true
|
||||
- test('lt: 1.0.0 < 2.0.0') → true
|
||||
- test('lte: 1.0.0 <= 1.0.0') → true
|
||||
- test('handles pre-release versions') — `1.0.0-beta < 1.0.0`
|
||||
|
||||
#### describe('satisfies')
|
||||
|
||||
- test('version satisfies caret range') — `satisfies('1.2.3', '^1.0.0')` → true
|
||||
- test('version does not satisfy range') — `satisfies('2.0.0', '^1.0.0')` → false
|
||||
- test('exact match') — `satisfies('1.0.0', '1.0.0')` → true
|
||||
|
||||
#### describe('order')
|
||||
|
||||
- test('returns -1 for lesser') — `order('1.0.0', '2.0.0')` → -1
|
||||
- test('returns 0 for equal') — `order('1.0.0', '1.0.0')` → 0
|
||||
- test('returns 1 for greater') — `order('2.0.0', '1.0.0')` → 1
|
||||
|
||||
---
|
||||
|
||||
### src/utils/uuid.ts — 测试文件: `src/utils/__tests__/uuid.test.ts`
|
||||
|
||||
#### describe('validateUuid')
|
||||
|
||||
- test('accepts valid v4 UUID') — `'550e8400-e29b-41d4-a716-446655440000'` → 返回 UUID
|
||||
- test('returns null for invalid format') — `'not-a-uuid'` → null
|
||||
- test('returns null for empty string') — `''` → null
|
||||
- test('returns null for null/undefined input')
|
||||
- test('accepts uppercase UUIDs') — 大写字母有效
|
||||
|
||||
#### describe('createAgentId')
|
||||
|
||||
- test('returns string starting with "a"') — 前缀为 `a`
|
||||
- test('has correct length') — 前缀 + 16 hex 字符
|
||||
- test('generates unique ids') — 连续两次调用结果不同
|
||||
|
||||
---
|
||||
|
||||
### src/utils/format.ts — 测试文件: `src/utils/__tests__/format.test.ts`
|
||||
|
||||
#### describe('formatFileSize')
|
||||
|
||||
- test('formats bytes') — `500` → `"500 bytes"`
|
||||
- test('formats kilobytes') — `1536` → `"1.5KB"`
|
||||
- test('formats megabytes') — `1572864` → `"1.5MB"`
|
||||
- test('formats gigabytes') — `1610612736` → `"1.5GB"`
|
||||
- test('removes trailing .0') — `1024` → `"1KB"` (不是 `"1.0KB"`)
|
||||
|
||||
#### describe('formatSecondsShort')
|
||||
|
||||
- test('formats milliseconds to seconds') — `1234` → `"1.2s"`
|
||||
- test('formats zero') — `0` → `"0.0s"`
|
||||
|
||||
#### describe('formatDuration')
|
||||
|
||||
- test('formats seconds') — `5000` → `"5s"`
|
||||
- test('formats minutes and seconds') — `65000` → `"1m 5s"`
|
||||
- test('formats hours') — `3661000` → `"1h 1m 1s"`
|
||||
- test('formats days') — `90061000` → `"1d 1h 1m"`
|
||||
- test('returns "0s" for zero') — `0` → `"0s"`
|
||||
- test('hideTrailingZeros omits zero components') — `3600000` + `hideTrailingZeros` → `"1h"`
|
||||
- test('mostSignificantOnly returns largest unit') — `3661000` + `mostSignificantOnly` → `"1h"`
|
||||
|
||||
#### describe('formatNumber')
|
||||
|
||||
- test('formats thousands') — `1321` → `"1.3k"`
|
||||
- test('formats small numbers as-is') — `900` → `"900"`
|
||||
- test('lowercase output') — `1500` → `"1.5k"` (不是 `"1.5K"`)
|
||||
|
||||
#### describe('formatTokens')
|
||||
|
||||
- test('strips .0 suffix') — `1000` → `"1k"` (不是 `"1.0k"`)
|
||||
- test('keeps non-zero decimal') — `1500` → `"1.5k"`
|
||||
|
||||
#### describe('formatRelativeTime')
|
||||
|
||||
- test('formats past time') — now - 3600s → `"1h ago"` (narrow style)
|
||||
- test('formats future time') — now + 3600s → `"in 1h"` (narrow style)
|
||||
- test('formats less than 1 second') — now → `"0s ago"`
|
||||
- test('uses custom now parameter for deterministic output')
|
||||
|
||||
---
|
||||
|
||||
### src/utils/json.ts — 测试文件: `src/utils/__tests__/json.test.ts`
|
||||
|
||||
#### describe('safeParseJSON')
|
||||
|
||||
- test('parses valid JSON') — `'{"a":1}'` → `{ a: 1 }`
|
||||
- test('returns null for invalid JSON') — `'not json'` → null
|
||||
- test('returns null for null input') — `null` → null
|
||||
- test('returns null for undefined input') — `undefined` → null
|
||||
- test('returns null for empty string') — `""` → null
|
||||
- test('handles JSON with BOM') — BOM 前缀不影响解析
|
||||
- test('caches results for repeated calls') — 同一输入不重复解析
|
||||
|
||||
#### describe('safeParseJSONC')
|
||||
|
||||
- test('parses JSON with comments') — 含 `//` 注释的 JSON 正确解析
|
||||
- test('parses JSON with trailing commas') — 宽松模式
|
||||
- test('returns null for invalid input')
|
||||
- test('returns null for null input')
|
||||
|
||||
#### describe('parseJSONL')
|
||||
|
||||
- test('parses multiple JSON lines') — `'{"a":1}\n{"b":2}'` → `[{a:1}, {b:2}]`
|
||||
- test('skips malformed lines') — 含错误行时跳过该行
|
||||
- test('handles empty input') — `""` → `[]`
|
||||
- test('handles trailing newline') — 尾部换行不产生空元素
|
||||
- test('accepts Buffer input') — Buffer 类型同样工作
|
||||
- test('handles BOM prefix')
|
||||
|
||||
#### describe('addItemToJSONCArray')
|
||||
|
||||
- test('adds item to existing array') — `[1, 2]` + 3 → `[1, 2, 3]`
|
||||
- test('creates new array for empty content') — `""` + item → `[item]`
|
||||
- test('creates new array for non-array content') — `'"hello"'` + item → `[item]`
|
||||
- test('preserves comments in JSONC') — 注释不被丢弃
|
||||
- test('handles empty array') — `"[]"` + item → `[item]`
|
||||
|
||||
---
|
||||
|
||||
### src/utils/diff.ts — 测试文件: `src/utils/__tests__/diff.test.ts`
|
||||
|
||||
#### describe('adjustHunkLineNumbers')
|
||||
|
||||
- test('shifts line numbers by positive offset') — 所有 hunk 的 oldStart/newStart 增加 offset
|
||||
- test('shifts by negative offset') — 负 offset 减少行号
|
||||
- test('handles empty hunk array') — `[]` → `[]`
|
||||
|
||||
#### describe('getPatchFromContents')
|
||||
|
||||
- test('returns empty array for identical content') — 相同内容无差异
|
||||
- test('detects added lines') — 新内容多出行
|
||||
- test('detects removed lines') — 旧内容缺少行
|
||||
- test('detects modified lines') — 行内容变化
|
||||
- test('handles empty old content') — 从空文件到有内容
|
||||
- test('handles empty new content') — 删除所有内容
|
||||
|
||||
---
|
||||
|
||||
### src/utils/frontmatterParser.ts — 测试文件: `src/utils/__tests__/frontmatterParser.test.ts`
|
||||
|
||||
#### describe('parseFrontmatter')
|
||||
|
||||
- test('extracts YAML frontmatter between --- delimiters') — 正确提取 frontmatter 并返回 body
|
||||
- test('returns empty frontmatter for content without ---') — 无 frontmatter 时 data 为空
|
||||
- test('handles empty content') — `""` 正确处理
|
||||
- test('handles frontmatter-only content') — 只有 frontmatter 无 body
|
||||
- test('falls back to quoting on YAML parse error') — 无效 YAML 不崩溃
|
||||
|
||||
#### describe('splitPathInFrontmatter')
|
||||
|
||||
- test('splits comma-separated paths') — `"a.ts, b.ts"` → `["a.ts", "b.ts"]`
|
||||
- test('expands brace patterns') — `"*.{ts,tsx}"` → `["*.ts", "*.tsx"]`
|
||||
- test('handles string array input') — `["a.ts", "b.ts"]` → `["a.ts", "b.ts"]`
|
||||
- test('respects braces in comma splitting') — 大括号内的逗号不作为分隔符
|
||||
|
||||
#### describe('parsePositiveIntFromFrontmatter')
|
||||
|
||||
- test('returns number for valid positive int') — `5` → `5`
|
||||
- test('returns undefined for negative') — `-1` → undefined
|
||||
- test('returns undefined for non-number') — `"abc"` → undefined
|
||||
- test('returns undefined for float') — `1.5` → undefined
|
||||
|
||||
#### describe('parseBooleanFrontmatter')
|
||||
|
||||
- test('returns true for true') — `true` → true
|
||||
- test('returns true for "true"') — `"true"` → true
|
||||
- test('returns false for false') — `false` → false
|
||||
- test('returns false for other values') — `"yes"`, `1` → false
|
||||
|
||||
#### describe('parseShellFrontmatter')
|
||||
|
||||
- test('returns bash for "bash"') — 正确识别
|
||||
- test('returns powershell for "powershell"')
|
||||
- test('returns undefined for invalid value') — `"zsh"` → undefined
|
||||
|
||||
---
|
||||
|
||||
### src/utils/file.ts(纯函数部分)— 测试文件: `src/utils/__tests__/file.test.ts`
|
||||
|
||||
#### describe('convertLeadingTabsToSpaces')
|
||||
|
||||
- test('converts single tab to 2 spaces') — `"\thello"` → `" hello"`
|
||||
- test('converts multiple leading tabs') — `"\t\thello"` → `" hello"`
|
||||
- test('does not convert tabs within line') — `"a\tb"` 保持原样
|
||||
- test('handles mixed content')
|
||||
|
||||
#### describe('addLineNumbers')
|
||||
|
||||
- test('adds line numbers starting from 1') — 每行添加 `N\t` 前缀
|
||||
- test('respects startLine parameter') — 从指定行号开始
|
||||
- test('handles empty content')
|
||||
|
||||
#### describe('stripLineNumberPrefix')
|
||||
|
||||
- test('strips tab-prefixed line number') — `"1\thello"` → `"hello"`
|
||||
- test('strips padded line number') — `" 1\thello"` → `"hello"`
|
||||
- test('returns line unchanged when no prefix')
|
||||
|
||||
#### describe('pathsEqual')
|
||||
|
||||
- test('returns true for identical paths')
|
||||
- test('handles trailing slashes') — 带/不带尾部斜杠视为相同
|
||||
- test('handles case sensitivity based on platform')
|
||||
|
||||
#### describe('normalizePathForComparison')
|
||||
|
||||
- test('normalizes forward slashes')
|
||||
- test('resolves path for comparison')
|
||||
|
||||
---
|
||||
|
||||
### src/utils/glob.ts(纯函数部分)— 测试文件: `src/utils/__tests__/glob.test.ts`
|
||||
|
||||
#### describe('extractGlobBaseDirectory')
|
||||
|
||||
- test('extracts static prefix from glob') — `"src/**/*.ts"` → `{ baseDir: "src", relativePattern: "**/*.ts" }`
|
||||
- test('handles root-level glob') — `"*.ts"` → `{ baseDir: ".", relativePattern: "*.ts" }`
|
||||
- test('handles deep static path') — `"src/utils/model/*.ts"` → baseDir 为 `"src/utils/model"`
|
||||
- test('handles Windows drive root') — `"C:\\Users\\**\\*.ts"` 正确分割
|
||||
|
||||
---
|
||||
|
||||
### src/utils/tokens.ts(纯函数部分)— 测试文件: `src/utils/__tests__/tokens.test.ts`
|
||||
|
||||
#### describe('getTokenCountFromUsage')
|
||||
|
||||
- test('sums input and output tokens') — `{ input_tokens: 100, output_tokens: 50 }` → 150
|
||||
- test('includes cache tokens') — cache_creation + cache_read 加入总数
|
||||
- test('handles zero values') — 全 0 时返回 0
|
||||
|
||||
---
|
||||
|
||||
### src/utils/path.ts(纯函数部分)— 测试文件: `src/utils/__tests__/path.test.ts`
|
||||
|
||||
#### describe('containsPathTraversal')
|
||||
|
||||
- test('detects ../ traversal') — `"../etc/passwd"` → true
|
||||
- test('detects mid-path traversal') — `"foo/../../bar"` → true
|
||||
- test('returns false for safe paths') — `"src/utils/file.ts"` → false
|
||||
- test('returns false for paths containing .. in names') — `"foo..bar"` → false
|
||||
|
||||
#### describe('normalizePathForConfigKey')
|
||||
|
||||
- test('converts backslashes to forward slashes') — `"src\\utils"` → `"src/utils"`
|
||||
- test('leaves forward slashes unchanged')
|
||||
|
||||
---
|
||||
|
||||
## Mock 需求
|
||||
|
||||
本计划中的函数大部分为纯函数,**不需要 mock**。少数例外:
|
||||
|
||||
| 函数 | 依赖 | 处理 |
|
||||
|------|------|------|
|
||||
| `hashContent` / `hashPair` | `Bun.hash` | Bun 运行时下自动可用 |
|
||||
| `formatRelativeTime` | `Date` | 使用 `now` 参数注入确定性时间 |
|
||||
| `safeParseJSON` | `logError` | 可通过 `shouldLogError: false` 跳过 |
|
||||
| `safeParseJSONC` | `logError` | mock `logError` 避免测试输出噪音 |
|
||||
@@ -1,134 +0,0 @@
|
||||
# Context 构建测试计划
|
||||
|
||||
## 概述
|
||||
|
||||
Context 构建系统负责组装发送给 Claude API 的系统提示和用户上下文。包括 git 状态获取、CLAUDE.md 文件发现与加载、系统提示拼装三部分。
|
||||
|
||||
## 被测文件
|
||||
|
||||
| 文件 | 关键导出 |
|
||||
|------|----------|
|
||||
| `src/context.ts` | `getSystemContext`, `getUserContext`, `getGitStatus`, `setSystemPromptInjection` |
|
||||
| `src/utils/claudemd.ts` | `stripHtmlComments`, `getClaudeMds`, `isMemoryFilePath`, `getLargeMemoryFiles`, `filterInjectedMemoryFiles`, `getExternalClaudeMdIncludes`, `hasExternalClaudeMdIncludes`, `processMemoryFile`, `getMemoryFiles` |
|
||||
| `src/utils/systemPrompt.ts` | `buildEffectiveSystemPrompt` |
|
||||
|
||||
---
|
||||
|
||||
## 测试用例
|
||||
|
||||
### src/utils/claudemd.ts — 纯函数部分
|
||||
|
||||
#### describe('stripHtmlComments')
|
||||
|
||||
- test('strips block-level HTML comments') — `"text <!-- comment --> more"` → content 不含注释
|
||||
- test('preserves inline content') — 行内文本保留
|
||||
- test('preserves code block content') — ` ```html\n<!-- not stripped -->\n``` ` 内的注释不移除
|
||||
- test('returns stripped: false when no comments') — 无注释时 stripped 为 false
|
||||
- test('returns stripped: true when comments exist')
|
||||
- test('handles empty string') — `""` → `{ content: "", stripped: false }`
|
||||
- test('handles multiple comments') — 多个注释全部移除
|
||||
|
||||
#### describe('getClaudeMds')
|
||||
|
||||
- test('assembles memory files with type descriptions') — 不同 type 的文件有不同前缀描述
|
||||
- test('includes instruction prompt prefix') — 输出包含指令前缀
|
||||
- test('handles empty memory files array') — 空数组返回空字符串或最小前缀
|
||||
- test('respects filter parameter') — filter 函数可过滤特定类型
|
||||
- test('concatenates multiple files with separators')
|
||||
|
||||
#### describe('isMemoryFilePath')
|
||||
|
||||
- test('returns true for CLAUDE.md path') — `"/project/CLAUDE.md"` → true
|
||||
- test('returns true for .claude/rules/ path') — `"/project/.claude/rules/foo.md"` → true
|
||||
- test('returns true for memory file path') — `"~/.claude/memory/foo.md"` → true
|
||||
- test('returns false for regular file') — `"/project/src/main.ts"` → false
|
||||
- test('returns false for unrelated .md file') — `"/project/README.md"` → false
|
||||
|
||||
#### describe('getLargeMemoryFiles')
|
||||
|
||||
- test('returns files exceeding 40K chars') — 内容 > MAX_MEMORY_CHARACTER_COUNT 的文件被返回
|
||||
- test('returns empty array when all files are small')
|
||||
- test('correctly identifies threshold boundary')
|
||||
|
||||
#### describe('filterInjectedMemoryFiles')
|
||||
|
||||
- test('filters out AutoMem type files') — feature flag 开启时移除自动记忆
|
||||
- test('filters out TeamMem type files')
|
||||
- test('preserves other types') — 非 AutoMem/TeamMem 的文件保留
|
||||
|
||||
#### describe('getExternalClaudeMdIncludes')
|
||||
|
||||
- test('returns includes from outside CWD') — 外部 @include 路径被识别
|
||||
- test('returns empty array when all includes are internal')
|
||||
|
||||
#### describe('hasExternalClaudeMdIncludes')
|
||||
|
||||
- test('returns true when external includes exist')
|
||||
- test('returns false when no external includes')
|
||||
|
||||
---
|
||||
|
||||
### src/utils/systemPrompt.ts
|
||||
|
||||
#### describe('buildEffectiveSystemPrompt')
|
||||
|
||||
- test('returns default system prompt when no overrides') — 无任何覆盖时使用默认提示
|
||||
- test('overrideSystemPrompt replaces everything') — override 模式替换全部内容
|
||||
- test('customSystemPrompt replaces default') — `--system-prompt` 参数替换默认
|
||||
- test('appendSystemPrompt is appended after main prompt') — append 在主提示之后
|
||||
- test('agent definition replaces default prompt') — agent 模式使用 agent prompt
|
||||
- test('agent definition with append combines both') — agent prompt + append
|
||||
- test('override takes precedence over agent and custom') — 优先级最高
|
||||
- test('returns array of strings') — 返回值为 SystemPrompt 类型(字符串数组)
|
||||
|
||||
---
|
||||
|
||||
### src/context.ts — 需 Mock 的部分
|
||||
|
||||
#### describe('getGitStatus')
|
||||
|
||||
- test('returns formatted git status string') — 包含 branch、status、log、user
|
||||
- test('truncates status at 2000 chars') — 超长 status 被截断
|
||||
- test('returns null in test environment') — `NODE_ENV=test` 时返回 null
|
||||
- test('returns null in non-git directory') — 非 git 仓库返回 null
|
||||
- test('runs git commands in parallel') — 多个 git 命令并行执行
|
||||
|
||||
#### describe('getSystemContext')
|
||||
|
||||
- test('includes gitStatus key') — 返回对象包含 gitStatus
|
||||
- test('returns memoized result on subsequent calls') — 多次调用返回同一结果
|
||||
- test('skips git when instructions disabled')
|
||||
|
||||
#### describe('getUserContext')
|
||||
|
||||
- test('includes currentDate key') — 返回对象包含当前日期
|
||||
- test('includes claudeMd key when CLAUDE.md exists') — 加载 CLAUDE.md 内容
|
||||
- test('respects CLAUDE_CODE_DISABLE_CLAUDE_MDS env') — 设置后不加载 CLAUDE.md
|
||||
- test('returns memoized result')
|
||||
|
||||
#### describe('setSystemPromptInjection')
|
||||
|
||||
- test('clears memoized context caches') — 调用后下次 getSystemContext/getUserContext 重新计算
|
||||
- test('injection value is accessible via getSystemPromptInjection')
|
||||
|
||||
---
|
||||
|
||||
## Mock 需求
|
||||
|
||||
| 依赖 | Mock 方式 | 用途 |
|
||||
|------|-----------|------|
|
||||
| `execFileNoThrow` | `mock.module` | `getGitStatus` 中的 git 命令 |
|
||||
| `getMemoryFiles` | `mock.module` | `getUserContext` 中的 CLAUDE.md 加载 |
|
||||
| `getCwd` | `mock.module` | 路径解析上下文 |
|
||||
| `process.env.NODE_ENV` | 直接设置 | 测试环境检测 |
|
||||
| `process.env.CLAUDE_CODE_DISABLE_CLAUDE_MDS` | 直接设置 | 禁用 CLAUDE.md |
|
||||
|
||||
## 集成测试场景
|
||||
|
||||
放在 `tests/integration/context-build.test.ts`:
|
||||
|
||||
### describe('Context assembly pipeline')
|
||||
|
||||
- test('getUserContext produces claudeMd containing CLAUDE.md content') — 端到端验证 CLAUDE.md 被正确加载到 context
|
||||
- test('buildEffectiveSystemPrompt + getUserContext produces complete prompt') — 系统提示 + 用户上下文完整性
|
||||
- test('setSystemPromptInjection invalidates and rebuilds context') — 注入后重新构建上下文
|
||||
@@ -1,104 +0,0 @@
|
||||
# 权限系统测试计划
|
||||
|
||||
## 概述
|
||||
|
||||
权限系统控制工具是否可以执行,包含规则解析器、权限检查管线和权限模式判断。测试重点是纯函数解析器和规则匹配逻辑。
|
||||
|
||||
## 被测文件
|
||||
|
||||
| 文件 | 关键导出 |
|
||||
|------|----------|
|
||||
| `src/utils/permissions/permissionRuleParser.ts` | `permissionRuleValueFromString`, `permissionRuleValueToString`, `escapeRuleContent`, `unescapeRuleContent`, `normalizeLegacyToolName`, `getLegacyToolNames` |
|
||||
| `src/utils/permissions/PermissionMode.ts` | 权限模式常量和辅助函数 |
|
||||
| `src/utils/permissions/permissions.ts` | `hasPermissionsToUseTool`, `getDenyRuleForTool`, `checkRuleBasedPermissions` |
|
||||
| `src/types/permissions.ts` | `PermissionMode`, `PermissionBehavior`, `PermissionRule` 类型定义 |
|
||||
|
||||
---
|
||||
|
||||
## 测试用例
|
||||
|
||||
### src/utils/permissions/permissionRuleParser.ts
|
||||
|
||||
#### describe('escapeRuleContent')
|
||||
|
||||
- test('escapes backslashes first') — `'test\\value'` → `'test\\\\value'`
|
||||
- test('escapes opening parentheses') — `'print(1)'` → `'print\\(1\\)'`
|
||||
- test('escapes closing parentheses') — `'func()'` → `'func\\(\\)'`
|
||||
- test('handles combined escape') — `'echo "test\\nvalue"'` 中的 `\\` 先转义
|
||||
- test('handles empty string') — `''` → `''`
|
||||
- test('no-op for string without special chars') — `'npm install'` 原样返回
|
||||
|
||||
#### describe('unescapeRuleContent')
|
||||
|
||||
- test('unescapes parentheses') — `'print\\(1\\)'` → `'print(1)'`
|
||||
- test('unescapes backslashes last') — `'test\\\\nvalue'` → `'test\\nvalue'`
|
||||
- test('handles empty string')
|
||||
- test('roundtrip: escape then unescape returns original') — `unescapeRuleContent(escapeRuleContent(x)) === x`
|
||||
|
||||
#### describe('permissionRuleValueFromString')
|
||||
|
||||
- test('parses tool name only') — `'Bash'` → `{ toolName: 'Bash' }`
|
||||
- test('parses tool name with content') — `'Bash(npm install)'` → `{ toolName: 'Bash', ruleContent: 'npm install' }`
|
||||
- test('parses content with escaped parentheses') — `'Bash(python -c "print\\(1\\)")'` → ruleContent 为 `'python -c "print(1)"'`
|
||||
- test('treats empty parens as tool-wide rule') — `'Bash()'` → `{ toolName: 'Bash' }`(无 ruleContent)
|
||||
- test('treats wildcard content as tool-wide rule') — `'Bash(*)'` → `{ toolName: 'Bash' }`
|
||||
- test('normalizes legacy tool names') — `'Task'` → `{ toolName: 'Agent' }`(或对应的 AGENT_TOOL_NAME)
|
||||
- test('handles malformed input: no closing paren') — `'Bash(npm'` → 整个字符串作为 toolName
|
||||
- test('handles malformed input: content after closing paren') — `'Bash(npm)extra'` → 整个字符串作为 toolName
|
||||
- test('handles missing tool name') — `'(foo)'` → 整个字符串作为 toolName
|
||||
|
||||
#### describe('permissionRuleValueToString')
|
||||
|
||||
- test('serializes tool name only') — `{ toolName: 'Bash' }` → `'Bash'`
|
||||
- test('serializes with content') — `{ toolName: 'Bash', ruleContent: 'npm install' }` → `'Bash(npm install)'`
|
||||
- test('escapes content with parentheses') — ruleContent 含 `()` 时正确转义
|
||||
- test('roundtrip: fromString then toString preserves value') — 往返一致
|
||||
|
||||
#### describe('normalizeLegacyToolName')
|
||||
|
||||
- test('maps Task to Agent tool name') — `'Task'` → AGENT_TOOL_NAME
|
||||
- test('maps KillShell to TaskStop tool name') — `'KillShell'` → TASK_STOP_TOOL_NAME
|
||||
- test('maps AgentOutputTool to TaskOutput tool name')
|
||||
- test('returns unknown names unchanged') — `'UnknownTool'` → `'UnknownTool'`
|
||||
|
||||
#### describe('getLegacyToolNames')
|
||||
|
||||
- test('returns legacy names for canonical name') — 给定 AGENT_TOOL_NAME 返回包含 `'Task'`
|
||||
- test('returns empty array for name with no legacy aliases')
|
||||
|
||||
---
|
||||
|
||||
### src/utils/permissions/permissions.ts — 需 Mock
|
||||
|
||||
#### describe('getDenyRuleForTool')
|
||||
|
||||
- test('returns deny rule matching tool name') — 匹配到 blanket deny 规则时返回
|
||||
- test('returns null when no deny rules match') — 无匹配时返回 null
|
||||
- test('matches MCP tools by server prefix') — `mcp__server` 规则匹配该 server 下的 MCP 工具
|
||||
- test('does not match content-specific deny rules') — 有 ruleContent 的 deny 规则不作为 blanket deny
|
||||
|
||||
#### describe('checkRuleBasedPermissions')(集成级别)
|
||||
|
||||
- test('deny rule takes precedence over allow') — 同时有 allow 和 deny 时 deny 优先
|
||||
- test('ask rule prompts user') — 匹配 ask 规则返回 `{ behavior: 'ask' }`
|
||||
- test('allow rule permits execution') — 匹配 allow 规则返回 `{ behavior: 'allow' }`
|
||||
- test('passthrough when no rules match') — 无匹配规则返回 passthrough
|
||||
|
||||
---
|
||||
|
||||
## Mock 需求
|
||||
|
||||
| 依赖 | Mock 方式 | 说明 |
|
||||
|------|-----------|------|
|
||||
| `bun:bundle` (feature) | 已 polyfill | BRIEF_TOOL_NAME 条件加载 |
|
||||
| Tool 常量导入 | 实际值 | AGENT_TOOL_NAME 等从常量文件导入 |
|
||||
| `appState` | mock object | `hasPermissionsToUseTool` 中的状态依赖 |
|
||||
| Tool 对象 | mock object | 模拟 tool 的 name, checkPermissions 等 |
|
||||
|
||||
## 集成测试场景
|
||||
|
||||
### describe('Permission pipeline end-to-end')
|
||||
|
||||
- test('deny rule blocks tool before it runs') — deny 规则在 call 前拦截
|
||||
- test('bypassPermissions mode allows all') — bypass 模式下 ask → allow
|
||||
- test('dontAsk mode converts ask to deny') — dontAsk 模式下 ask → deny
|
||||
@@ -1,113 +0,0 @@
|
||||
# 模型路由测试计划
|
||||
|
||||
## 概述
|
||||
|
||||
模型路由系统负责 API provider 选择、模型别名解析、模型名规范化和运行时模型决策。测试重点是纯函数和环境变量驱动的逻辑。
|
||||
|
||||
## 被测文件
|
||||
|
||||
| 文件 | 关键导出 |
|
||||
|------|----------|
|
||||
| `src/utils/model/aliases.ts` | `MODEL_ALIASES`, `MODEL_FAMILY_ALIASES`, `isModelAlias`, `isModelFamilyAlias` |
|
||||
| `src/utils/model/providers.ts` | `APIProvider`, `getAPIProvider`, `isFirstPartyAnthropicBaseUrl` |
|
||||
| `src/utils/model/model.ts` | `firstPartyNameToCanonical`, `getCanonicalName`, `parseUserSpecifiedModel`, `normalizeModelStringForAPI`, `getRuntimeMainLoopModel`, `getDefaultMainLoopModelSetting` |
|
||||
|
||||
---
|
||||
|
||||
## 测试用例
|
||||
|
||||
### src/utils/model/aliases.ts
|
||||
|
||||
#### describe('isModelAlias')
|
||||
|
||||
- test('returns true for "sonnet"') — 有效别名
|
||||
- test('returns true for "opus"')
|
||||
- test('returns true for "haiku"')
|
||||
- test('returns true for "best"')
|
||||
- test('returns true for "sonnet[1m]"')
|
||||
- test('returns true for "opus[1m]"')
|
||||
- test('returns true for "opusplan"')
|
||||
- test('returns false for full model ID') — `'claude-sonnet-4-6-20250514'` → false
|
||||
- test('returns false for unknown string') — `'gpt-4'` → false
|
||||
- test('is case-sensitive') — `'Sonnet'` → false(别名是小写)
|
||||
|
||||
#### describe('isModelFamilyAlias')
|
||||
|
||||
- test('returns true for "sonnet"')
|
||||
- test('returns true for "opus"')
|
||||
- test('returns true for "haiku"')
|
||||
- test('returns false for "best"') — best 不是 family alias
|
||||
- test('returns false for "opusplan"')
|
||||
- test('returns false for "sonnet[1m]"')
|
||||
|
||||
---
|
||||
|
||||
### src/utils/model/providers.ts
|
||||
|
||||
#### describe('getAPIProvider')
|
||||
|
||||
- test('returns "firstParty" by default') — 无相关 env 时返回 firstParty
|
||||
- test('returns "bedrock" when CLAUDE_CODE_USE_BEDROCK is set') — env 为 truthy 值
|
||||
- test('returns "vertex" when CLAUDE_CODE_USE_VERTEX is set')
|
||||
- test('returns "foundry" when CLAUDE_CODE_USE_FOUNDRY is set')
|
||||
- test('bedrock takes precedence over vertex') — 多个 env 同时设置时 bedrock 优先
|
||||
|
||||
#### describe('isFirstPartyAnthropicBaseUrl')
|
||||
|
||||
- test('returns true when ANTHROPIC_BASE_URL is not set') — 默认 API
|
||||
- test('returns true for api.anthropic.com') — `'https://api.anthropic.com'` → true
|
||||
- test('returns false for custom URL') — `'https://my-proxy.com'` → false
|
||||
- test('returns false for invalid URL') — 非法 URL → false
|
||||
- test('returns true for staging URL when USER_TYPE is ant') — `'https://api-staging.anthropic.com'` + ant → true
|
||||
|
||||
---
|
||||
|
||||
### src/utils/model/model.ts
|
||||
|
||||
#### describe('firstPartyNameToCanonical')
|
||||
|
||||
- test('maps opus-4-6 full name to canonical') — `'claude-opus-4-6-20250514'` → `'claude-opus-4-6'`
|
||||
- test('maps sonnet-4-6 full name') — `'claude-sonnet-4-6-20250514'` → `'claude-sonnet-4-6'`
|
||||
- test('maps haiku-4-5') — `'claude-haiku-4-5-20251001'` → `'claude-haiku-4-5'`
|
||||
- test('maps 3P provider format') — `'us.anthropic.claude-opus-4-6-v1:0'` → `'claude-opus-4-6'`
|
||||
- test('maps claude-3-7-sonnet') — `'claude-3-7-sonnet-20250219'` → `'claude-3-7-sonnet'`
|
||||
- test('maps claude-3-5-sonnet') → `'claude-3-5-sonnet'`
|
||||
- test('maps claude-3-5-haiku') → `'claude-3-5-haiku'`
|
||||
- test('maps claude-3-opus') → `'claude-3-opus'`
|
||||
- test('is case insensitive') — `'Claude-Opus-4-6'` → `'claude-opus-4-6'`
|
||||
- test('falls back to input for unknown model') — `'unknown-model'` → `'unknown-model'`
|
||||
- test('differentiates opus-4 vs opus-4-5 vs opus-4-6') — 更具体的版本优先匹配
|
||||
|
||||
#### describe('parseUserSpecifiedModel')
|
||||
|
||||
- test('resolves "sonnet" to default sonnet model')
|
||||
- test('resolves "opus" to default opus model')
|
||||
- test('resolves "haiku" to default haiku model')
|
||||
- test('resolves "best" to best model')
|
||||
- test('resolves "opusplan" to default sonnet model') — opusplan 默认用 sonnet
|
||||
- test('appends [1m] suffix when alias has [1m]') — `'sonnet[1m]'` → 模型名 + `'[1m]'`
|
||||
- test('preserves original case for custom model names') — `'my-Custom-Model'` 保留大小写
|
||||
- test('handles [1m] suffix on non-alias models') — `'custom-model[1m]'` → `'custom-model[1m]'`
|
||||
- test('trims whitespace') — `' sonnet '` → 正确解析
|
||||
|
||||
#### describe('getRuntimeMainLoopModel')
|
||||
|
||||
- test('returns mainLoopModel by default') — 无特殊条件时原样返回
|
||||
- test('returns opus in plan mode when opusplan is set') — opusplan + plan mode → opus
|
||||
- test('returns sonnet in plan mode when haiku is set') — haiku + plan mode → sonnet 升级
|
||||
- test('returns mainLoopModel in non-plan mode') — 非 plan 模式不做替换
|
||||
|
||||
---
|
||||
|
||||
## Mock 需求
|
||||
|
||||
| 依赖 | Mock 方式 | 说明 |
|
||||
|------|-----------|------|
|
||||
| `process.env.CLAUDE_CODE_USE_BEDROCK/VERTEX/FOUNDRY` | 直接设置/恢复 | provider 选择 |
|
||||
| `process.env.ANTHROPIC_BASE_URL` | 直接设置/恢复 | URL 检测 |
|
||||
| `process.env.USER_TYPE` | 直接设置/恢复 | staging URL 和 ant 功能 |
|
||||
| `getModelStrings()` | mock.module | 返回固定模型 ID |
|
||||
| `getMainLoopModelOverride` | mock.module | 会话中模型覆盖 |
|
||||
| `getSettings_DEPRECATED` | mock.module | 用户设置中的模型 |
|
||||
| `getUserSpecifiedModelSetting` | mock.module | `getRuntimeMainLoopModel` 依赖 |
|
||||
| `isModelAllowed` | mock.module | allowlist 检查 |
|
||||
@@ -1,165 +0,0 @@
|
||||
# 消息处理测试计划
|
||||
|
||||
## 概述
|
||||
|
||||
消息处理系统负责消息的创建、查询、规范化和文本提取。覆盖消息类型定义、消息工厂函数、消息过滤/查询工具和 API 规范化管线。
|
||||
|
||||
## 被测文件
|
||||
|
||||
| 文件 | 关键导出 |
|
||||
|------|----------|
|
||||
| `src/types/message.ts` | `MessageType`, `Message`, `AssistantMessage`, `UserMessage`, `SystemMessage` 等类型 |
|
||||
| `src/utils/messages.ts` | 消息创建、查询、规范化、文本提取等函数(~3100 行) |
|
||||
| `src/utils/messages/mappers.ts` | 消息映射工具 |
|
||||
|
||||
---
|
||||
|
||||
## 测试用例
|
||||
|
||||
### src/utils/messages.ts — 消息创建
|
||||
|
||||
#### describe('createAssistantMessage')
|
||||
|
||||
- test('creates message with type "assistant"') — type 字段正确
|
||||
- test('creates message with role "assistant"') — role 正确
|
||||
- test('creates message with empty content array') — 默认 content 为空
|
||||
- test('generates unique uuid') — 每次调用 uuid 不同
|
||||
- test('includes costUsd as 0')
|
||||
|
||||
#### describe('createUserMessage')
|
||||
|
||||
- test('creates message with type "user"') — type 字段正确
|
||||
- test('creates message with provided content') — content 正确传入
|
||||
- test('generates unique uuid')
|
||||
|
||||
#### describe('createSystemMessage')
|
||||
|
||||
- test('creates system message with correct type')
|
||||
- test('includes message content')
|
||||
|
||||
#### describe('createProgressMessage')
|
||||
|
||||
- test('creates progress message with data')
|
||||
- test('has correct type "progress"')
|
||||
|
||||
---
|
||||
|
||||
### src/utils/messages.ts — 消息查询
|
||||
|
||||
#### describe('getLastAssistantMessage')
|
||||
|
||||
- test('returns last assistant message from array') — 多条消息中返回最后一条 assistant
|
||||
- test('returns undefined for empty array')
|
||||
- test('returns undefined when no assistant messages exist')
|
||||
|
||||
#### describe('hasToolCallsInLastAssistantTurn')
|
||||
|
||||
- test('returns true when last assistant has tool_use content') — content 含 tool_use block
|
||||
- test('returns false when last assistant has only text')
|
||||
- test('returns false for empty messages')
|
||||
|
||||
#### describe('isSyntheticMessage')
|
||||
|
||||
- test('identifies interrupt message as synthetic') — INTERRUPT_MESSAGE 标记
|
||||
- test('identifies cancel message as synthetic')
|
||||
- test('returns false for normal user messages')
|
||||
|
||||
#### describe('isNotEmptyMessage')
|
||||
|
||||
- test('returns true for message with content')
|
||||
- test('returns false for message with empty content array')
|
||||
- test('returns false for message with empty text content')
|
||||
|
||||
---
|
||||
|
||||
### src/utils/messages.ts — 文本提取
|
||||
|
||||
#### describe('getAssistantMessageText')
|
||||
|
||||
- test('extracts text from text blocks') — content 含 `{ type: 'text', text: 'hello' }` 时提取
|
||||
- test('returns empty string for non-text content') — 仅含 tool_use 时返回空
|
||||
- test('concatenates multiple text blocks')
|
||||
|
||||
#### describe('getUserMessageText')
|
||||
|
||||
- test('extracts text from string content') — content 为纯字符串
|
||||
- test('extracts text from content array') — content 为数组时提取 text 块
|
||||
- test('handles empty content')
|
||||
|
||||
#### describe('extractTextContent')
|
||||
|
||||
- test('extracts text items from mixed content') — 过滤出 type: 'text' 的项
|
||||
- test('returns empty array for all non-text content')
|
||||
|
||||
---
|
||||
|
||||
### src/utils/messages.ts — 规范化
|
||||
|
||||
#### describe('normalizeMessages')
|
||||
|
||||
- test('converts raw messages to normalized format') — 消息数组规范化
|
||||
- test('handles empty array') — `[]` → `[]`
|
||||
- test('preserves message order')
|
||||
- test('handles mixed message types')
|
||||
|
||||
#### describe('normalizeMessagesForAPI')
|
||||
|
||||
- test('filters out system messages') — 系统消息不发送给 API
|
||||
- test('filters out progress messages')
|
||||
- test('filters out attachment messages')
|
||||
- test('preserves user and assistant messages')
|
||||
- test('reorders tool results to match API expectations')
|
||||
- test('handles empty array')
|
||||
|
||||
---
|
||||
|
||||
### src/utils/messages.ts — 合并
|
||||
|
||||
#### describe('mergeUserMessages')
|
||||
|
||||
- test('merges consecutive user messages') — 相邻用户消息合并
|
||||
- test('does not merge non-consecutive user messages')
|
||||
- test('preserves assistant messages between user messages')
|
||||
|
||||
#### describe('mergeAssistantMessages')
|
||||
|
||||
- test('merges consecutive assistant messages')
|
||||
- test('combines content arrays')
|
||||
|
||||
---
|
||||
|
||||
### src/utils/messages.ts — 辅助函数
|
||||
|
||||
#### describe('buildMessageLookups')
|
||||
|
||||
- test('builds index by message uuid') — 按 uuid 建立查找表
|
||||
- test('returns empty lookups for empty messages')
|
||||
- test('handles duplicate uuids gracefully')
|
||||
|
||||
---
|
||||
|
||||
## Mock 需求
|
||||
|
||||
| 依赖 | Mock 方式 | 说明 |
|
||||
|------|-----------|------|
|
||||
| `crypto.randomUUID` | `mock` 或 spy | 消息创建中的 uuid 生成 |
|
||||
| Message 对象 | 手动构造 | 创建符合类型的 mock 消息对象 |
|
||||
|
||||
### Mock 消息工厂(放在 `tests/mocks/messages.ts`)
|
||||
|
||||
```typescript
|
||||
// 通用 mock 消息构造器
|
||||
export function mockAssistantMessage(overrides?: Partial<AssistantMessage>): AssistantMessage
|
||||
export function mockUserMessage(content: string, overrides?: Partial<UserMessage>): UserMessage
|
||||
export function mockSystemMessage(overrides?: Partial<SystemMessage>): SystemMessage
|
||||
export function mockToolUseBlock(name: string, input: unknown): ToolUseBlock
|
||||
export function mockToolResultMessage(toolUseId: string, content: string): UserMessage
|
||||
```
|
||||
|
||||
## 集成测试场景
|
||||
|
||||
### describe('Message pipeline')
|
||||
|
||||
- test('create → normalize → API format produces valid request') — 创建消息 → normalizeMessagesForAPI → 验证输出结构
|
||||
- test('tool use and tool result pairing is preserved through normalization')
|
||||
- test('merge + normalize handles conversation with interruptions')
|
||||
@@ -1,112 +0,0 @@
|
||||
# Cron 调度测试计划
|
||||
|
||||
## 概述
|
||||
|
||||
Cron 模块提供 cron 表达式解析、下次运行时间计算和人类可读描述。全部为纯函数,无外部依赖,是最适合单元测试的模块之一。
|
||||
|
||||
## 被测文件
|
||||
|
||||
| 文件 | 关键导出 |
|
||||
|------|----------|
|
||||
| `src/utils/cron.ts` | `CronFields`, `parseCronExpression`, `computeNextCronRun`, `cronToHuman` |
|
||||
|
||||
---
|
||||
|
||||
## 测试用例
|
||||
|
||||
### describe('parseCronExpression')
|
||||
|
||||
#### 有效表达式
|
||||
|
||||
- test('parses wildcard fields') — `'* * * * *'` → 每个字段为完整范围
|
||||
- test('parses specific values') — `'30 14 1 6 3'` → minute=[30], hour=[14], dom=[1], month=[6], dow=[3]
|
||||
- test('parses step syntax') — `'*/5 * * * *'` → minute=[0,5,10,...,55]
|
||||
- test('parses range syntax') — `'1-5 * * * *'` → minute=[1,2,3,4,5]
|
||||
- test('parses range with step') — `'1-10/3 * * * *'` → minute=[1,4,7,10]
|
||||
- test('parses comma-separated list') — `'1,15,30 * * * *'` → minute=[1,15,30]
|
||||
- test('parses day-of-week 7 as Sunday alias') — `'0 0 * * 7'` → dow=[0]
|
||||
- test('parses range with day-of-week 7') — `'0 0 * * 5-7'` → dow=[0,5,6]
|
||||
- test('parses complex combined expression') — `'0,30 9-17 * * 1-5'` → 工作日 9-17 每半小时
|
||||
|
||||
#### 无效表达式
|
||||
|
||||
- test('returns null for wrong field count') — `'* * *'` → null
|
||||
- test('returns null for out-of-range values') — `'60 * * * *'` → null(minute max=59)
|
||||
- test('returns null for invalid step') — `'*/0 * * * *'` → null(step=0)
|
||||
- test('returns null for reversed range') — `'10-5 * * * *'` → null(lo>hi)
|
||||
- test('returns null for empty string') — `''` → null
|
||||
- test('returns null for non-numeric tokens') — `'abc * * * *'` → null
|
||||
|
||||
#### 字段范围验证
|
||||
|
||||
- test('minute: 0-59')
|
||||
- test('hour: 0-23')
|
||||
- test('dayOfMonth: 1-31')
|
||||
- test('month: 1-12')
|
||||
- test('dayOfWeek: 0-6 (plus 7 alias)')
|
||||
|
||||
---
|
||||
|
||||
### describe('computeNextCronRun')
|
||||
|
||||
#### 基本匹配
|
||||
|
||||
- test('finds next minute') — from 14:30:45, cron `'31 14 * * *'` → 14:31:00 同天
|
||||
- test('finds next hour') — from 14:30, cron `'0 15 * * *'` → 15:00 同天
|
||||
- test('rolls to next day') — from 14:30, cron `'0 10 * * *'` → 10:00 次日
|
||||
- test('rolls to next month') — from 1月31日, cron `'0 0 1 * *'` → 2月1日
|
||||
- test('is strictly after from date') — from 恰好匹配时应返回下一次而非当前时间
|
||||
|
||||
#### DOM/DOW 语义
|
||||
|
||||
- test('OR semantics when both dom and dow constrained') — dom=15, dow=3 → 匹配 15 号 OR 周三
|
||||
- test('only dom constrained uses dom') — dom=15, dow=* → 只匹配 15 号
|
||||
- test('only dow constrained uses dow') — dom=*, dow=3 → 只匹配周三
|
||||
- test('both wildcarded matches every day') — dom=*, dow=* → 每天
|
||||
|
||||
#### 边界情况
|
||||
|
||||
- test('handles month boundary') — 从 2 月 28 日寻找 2 月 29 日或 3 月 1 日
|
||||
- test('returns null after 366-day search') — 不可能匹配的表达式返回 null(理论上不会发生)
|
||||
- test('handles step across midnight') — `'0 0 * * *'` 从 23:59 → 次日 0:00
|
||||
|
||||
#### 每 N 分钟
|
||||
|
||||
- test('every 5 minutes from arbitrary time') — `'*/5 * * * *'` from 14:32 → 14:35
|
||||
- test('every minute') — `'* * * * *'` from 14:32:45 → 14:33:00
|
||||
|
||||
---
|
||||
|
||||
### describe('cronToHuman')
|
||||
|
||||
#### 常见模式
|
||||
|
||||
- test('every N minutes') — `'*/5 * * * *'` → `'Every 5 minutes'`
|
||||
- test('every minute') — `'*/1 * * * *'` → `'Every minute'`
|
||||
- test('every hour at :00') — `'0 * * * *'` → `'Every hour'`
|
||||
- test('every hour at :30') — `'30 * * * *'` → `'Every hour at :30'`
|
||||
- test('every N hours') — `'0 */2 * * *'` → `'Every 2 hours'`
|
||||
- test('daily at specific time') — `'30 9 * * *'` → `'Every day at 9:30 AM'`
|
||||
- test('specific day of week') — `'0 9 * * 3'` → `'Every Wednesday at 9:00 AM'`
|
||||
- test('weekdays') — `'0 9 * * 1-5'` → `'Weekdays at 9:00 AM'`
|
||||
|
||||
#### Fallback
|
||||
|
||||
- test('returns raw cron for complex patterns') — 非常见模式返回原始 cron 字符串
|
||||
- test('returns raw cron for wrong field count') — `'* * *'` → 原样返回
|
||||
|
||||
#### UTC 模式
|
||||
|
||||
- test('UTC option formats time in local timezone') — `{ utc: true }` 时 UTC 时间转本地显示
|
||||
- test('UTC midnight crossing adjusts day name') — UTC 时间跨天时本地星期名正确
|
||||
|
||||
---
|
||||
|
||||
## Mock 需求
|
||||
|
||||
**无需 Mock**。所有函数为纯函数,唯一的外部依赖是 `Date` 构造器和 `toLocaleTimeString`,可通过传入确定性的 `from` 参数控制。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `cronToHuman` 的时间格式化依赖系统 locale,测试中建议使用 `'en-US'` locale 或只验证部分输出
|
||||
- `computeNextCronRun` 使用本地时区,DST 相关测试需注意运行环境
|
||||
@@ -1,106 +0,0 @@
|
||||
# Git 工具测试计划
|
||||
|
||||
## 概述
|
||||
|
||||
Git 工具模块提供 git 远程 URL 规范化、仓库根目录查找、裸仓库安全检测等功能。测试重点是纯函数的 URL 规范化和需要文件系统 mock 的仓库发现逻辑。
|
||||
|
||||
## 被测文件
|
||||
|
||||
| 文件 | 关键导出 |
|
||||
|------|----------|
|
||||
| `src/utils/git.ts` | `normalizeGitRemoteUrl`, `findGitRoot`, `findCanonicalGitRoot`, `getIsGit`, `isAtGitRoot`, `getRepoRemoteHash`, `isCurrentDirectoryBareGitRepo`, `gitExe`, `getGitState`, `stashToCleanState`, `preserveGitStateForIssue` |
|
||||
|
||||
---
|
||||
|
||||
## 测试用例
|
||||
|
||||
### describe('normalizeGitRemoteUrl')(纯函数)
|
||||
|
||||
#### SSH 格式
|
||||
|
||||
- test('normalizes SSH URL') — `'git@github.com:owner/repo.git'` → `'github.com/owner/repo'`
|
||||
- test('normalizes SSH URL without .git suffix') — `'git@github.com:owner/repo'` → `'github.com/owner/repo'`
|
||||
- test('handles GitLab SSH') — `'git@gitlab.com:group/subgroup/repo.git'` → `'gitlab.com/group/subgroup/repo'`
|
||||
|
||||
#### HTTPS 格式
|
||||
|
||||
- test('normalizes HTTPS URL') — `'https://github.com/owner/repo.git'` → `'github.com/owner/repo'`
|
||||
- test('normalizes HTTPS URL without .git suffix') — `'https://github.com/owner/repo'` → `'github.com/owner/repo'`
|
||||
- test('normalizes HTTP URL') — `'http://github.com/owner/repo.git'` → `'github.com/owner/repo'`
|
||||
|
||||
#### SSH:// 协议格式
|
||||
|
||||
- test('normalizes ssh:// URL') — `'ssh://git@github.com/owner/repo'` → `'github.com/owner/repo'`
|
||||
- test('handles user prefix in ssh://') — `'ssh://user@host/path'` → `'host/path'`
|
||||
|
||||
#### 代理 URL(CCR git proxy)
|
||||
|
||||
- test('normalizes legacy proxy URL') — `'http://local_proxy@127.0.0.1:16583/git/owner/repo'` → `'github.com/owner/repo'`
|
||||
- test('normalizes GHE proxy URL') — `'http://user@127.0.0.1:8080/git/ghe.company.com/owner/repo'` → `'ghe.company.com/owner/repo'`
|
||||
|
||||
#### 边界情况
|
||||
|
||||
- test('returns null for empty string') — `''` → null
|
||||
- test('returns null for whitespace') — `' '` → null
|
||||
- test('returns null for unrecognized format') — `'not-a-url'` → null
|
||||
- test('output is lowercase') — `'git@GitHub.com:Owner/Repo.git'` → `'github.com/owner/repo'`
|
||||
- test('SSH and HTTPS for same repo produce same result') — 相同仓库不同协议 → 相同输出
|
||||
|
||||
---
|
||||
|
||||
### describe('findGitRoot')(需文件系统 Mock)
|
||||
|
||||
- test('finds git root from nested directory') — `/project/src/utils/` → `/project/`(假设 `/project/.git` 存在)
|
||||
- test('finds git root from root directory') — `/project/` → `/project/`
|
||||
- test('returns null for non-git directory') — 无 `.git` → null
|
||||
- test('handles worktree .git file') — `.git` 为文件时也识别
|
||||
- test('memoizes results') — 同一路径不重复查找
|
||||
|
||||
### describe('findCanonicalGitRoot')
|
||||
|
||||
- test('returns same as findGitRoot for regular repo')
|
||||
- test('resolves worktree to main repo root') — worktree 路径 → 主仓库根目录
|
||||
- test('returns null for non-git directory')
|
||||
|
||||
### describe('gitExe')
|
||||
|
||||
- test('returns git path string') — 返回字符串
|
||||
- test('memoizes the result') — 多次调用返回同一值
|
||||
|
||||
---
|
||||
|
||||
### describe('getRepoRemoteHash')(需 Mock)
|
||||
|
||||
- test('returns 16-char hex hash') — 返回值为 16 位十六进制字符串
|
||||
- test('returns null when no remote') — 无 remote URL 时返回 null
|
||||
- test('same repo SSH/HTTPS produce same hash') — 不同协议同一仓库 hash 相同
|
||||
|
||||
---
|
||||
|
||||
### describe('isCurrentDirectoryBareGitRepo')(需文件系统 Mock)
|
||||
|
||||
- test('detects bare git repo attack vector') — 目录含 HEAD + objects/ + refs/ 但无有效 .git/HEAD → true
|
||||
- test('returns false for normal directory') — 普通目录 → false
|
||||
- test('returns false for regular git repo') — 有效 .git 目录 → false
|
||||
|
||||
---
|
||||
|
||||
## Mock 需求
|
||||
|
||||
| 依赖 | Mock 方式 | 说明 |
|
||||
|------|-----------|------|
|
||||
| `statSync` | mock module | `findGitRoot` 中的 .git 检测 |
|
||||
| `readFileSync` | mock module | worktree .git 文件读取 |
|
||||
| `realpathSync` | mock module | 路径解析 |
|
||||
| `execFileNoThrow` | mock module | git 命令执行 |
|
||||
| `whichSync` | mock module | `gitExe` 中的 git 路径查找 |
|
||||
| `getCwd` | mock module | 当前工作目录 |
|
||||
| `getRemoteUrl` | mock module | `getRepoRemoteHash` 依赖 |
|
||||
| 临时目录 | `mkdtemp` | 集成测试中创建临时 git 仓库 |
|
||||
|
||||
## 集成测试场景
|
||||
|
||||
### describe('Git repo discovery')(放在 tests/integration/)
|
||||
|
||||
- test('findGitRoot works in actual git repo') — 在临时 git init 的目录中验证
|
||||
- test('normalizeGitRemoteUrl + getRepoRemoteHash produces stable hash') — URL → hash 端到端验证
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user