Compare commits
@@ -9,7 +9,7 @@ assignees: ''
|
|||||||
## ⚠️ 提交前确认
|
## ⚠️ 提交前确认
|
||||||
<!-- 请确认以下事项 -->
|
<!-- 请确认以下事项 -->
|
||||||
- [ ] 我已经仔细阅读了 [README 常见问题](https://github.com/NanmiCoder/cc-haha#常见问题) 部分
|
- [ ] 我已经仔细阅读了 [README 常见问题](https://github.com/NanmiCoder/cc-haha#常见问题) 部分
|
||||||
- [ ] 我已经阅读了[第三方模型使用指南](https://github.com/NanmiCoder/cc-haha/blob/main/docs/third-party-models.md)
|
- [ ] 我已经阅读了[第三方模型使用指南](https://github.com/NanmiCoder/cc-haha/blob/main/docs/guide/third-party-models.md)
|
||||||
- [ ] 我已经搜索并查看了[已关闭的 issues](https://github.com/NanmiCoder/cc-haha/issues?q=is%3Aissue+is%3Aclosed)
|
- [ ] 我已经搜索并查看了[已关闭的 issues](https://github.com/NanmiCoder/cc-haha/issues?q=is%3Aissue+is%3Aclosed)
|
||||||
|
|
||||||
## ❓ 问题描述
|
## ❓ 问题描述
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
name: Deploy VitePress Docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- 'docs/**'
|
||||||
|
- 'package.json'
|
||||||
|
- '.github/workflows/deploy-docs.yml'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: pages
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: npm
|
||||||
|
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build with VitePress
|
||||||
|
run: npm run docs:build
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: docs/.vitepress/dist
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
@@ -6,3 +6,7 @@ node_modules
|
|||||||
# Computer Use runtime (auto-generated)
|
# Computer Use runtime (auto-generated)
|
||||||
.runtime/
|
.runtime/
|
||||||
extracted-natives/
|
extracted-natives/
|
||||||
|
|
||||||
|
# VitePress
|
||||||
|
docs/.vitepress/dist
|
||||||
|
docs/.vitepress/cache
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
DISCLAIMER AND LICENSE
|
||||||
|
|
||||||
|
Copyright (c) 2026 Anthropic, PBC. All rights reserved for the original source code.
|
||||||
|
|
||||||
|
This repository is based on the Claude Code source code leaked from the Anthropic
|
||||||
|
npm registry on 2026-03-31. All original source code copyrights belong to Anthropic.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED FOR EDUCATIONAL AND RESEARCH PURPOSES ONLY.
|
||||||
|
|
||||||
|
The purpose of this repository is to facilitate the study and research of AI Agent
|
||||||
|
engineering, including but not limited to:
|
||||||
|
- Agent architecture and design patterns
|
||||||
|
- Tool use and function calling mechanisms
|
||||||
|
- Multi-agent orchestration
|
||||||
|
- Terminal UI (TUI) implementation for AI applications
|
||||||
|
- MCP (Model Context Protocol) integration
|
||||||
|
|
||||||
|
RESTRICTIONS:
|
||||||
|
|
||||||
|
1. This software SHALL NOT be used for any commercial purposes.
|
||||||
|
2. This software SHALL NOT be used to compete with or replicate Anthropic's
|
||||||
|
commercial products or services.
|
||||||
|
3. This software SHALL NOT be redistributed, sublicensed, or sold in any form.
|
||||||
|
4. Users SHALL comply with all applicable laws and regulations in their jurisdiction.
|
||||||
|
|
||||||
|
NO WARRANTY:
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
By using this software, you acknowledge that:
|
||||||
|
- You understand this is leaked source code and all original rights belong to Anthropic.
|
||||||
|
- You will use it solely for learning and research of Agent engineering.
|
||||||
|
- You assume all legal risks and responsibilities arising from the use of this software.
|
||||||
@@ -1,28 +1,30 @@
|
|||||||
# Claude Code Haha
|
# Claude Code Haha
|
||||||
|
|
||||||
<p align="right"><a href="./README.md">中文</a> | <strong>English</strong></p>
|
<p align="center">
|
||||||
|
<img src="docs/images/banner.jpg" alt="Claude Code Haha Banner" width="800">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
[](https://github.com/NanmiCoder/cc-haha/stargazers)
|
||||||
|
[](https://github.com/NanmiCoder/cc-haha/network/members)
|
||||||
|
[](https://github.com/NanmiCoder/cc-haha/issues)
|
||||||
|
[](https://github.com/NanmiCoder/cc-haha/pulls)
|
||||||
|
[](https://github.com/NanmiCoder/cc-haha/blob/main/LICENSE)
|
||||||
|
[](README.md)
|
||||||
|
[](README.en.md)
|
||||||
|
[](https://claudecode-haha.relakkesyang.org)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
A **locally runnable version** repaired from the leaked Claude Code source, with support for any Anthropic-compatible API endpoint such as MiniMax and OpenRouter.
|
A **locally runnable version** repaired from the leaked Claude Code source, with support for any Anthropic-compatible API endpoint such as MiniMax and OpenRouter.
|
||||||
|
|
||||||
> The original leaked source does not run as-is. This repository fixes multiple blocking issues in the startup path so the full Ink TUI can work locally.
|
> The original leaked source does not run as-is. This repository fixes multiple blocking issues in the startup path so the full Ink TUI can work locally.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="docs/00runtime.png" alt="Runtime screenshot" width="800">
|
<a href="#features">Features</a> · <a href="#architecture-overview">Architecture</a> · <a href="#quick-start">Quick Start</a> · <a href="docs/en/guide/env-vars.md">Env Vars</a> · <a href="docs/en/guide/faq.md">FAQ</a> · <a href="docs/en/guide/global-usage.md">Global Usage</a> · <a href="#more-documentation">More Docs</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
- [Features](#features)
|
|
||||||
- [Architecture Overview](#architecture-overview)
|
|
||||||
- [Quick Start](#quick-start)
|
|
||||||
- [Environment Variables](#environment-variables)
|
|
||||||
- [Fallback Mode](#fallback-mode)
|
|
||||||
- [Computer Use Desktop Control](#computer-use-desktop-control)
|
|
||||||
- [FAQ](#faq)
|
|
||||||
- [Fixes Compared with the Original Leaked Source](#fixes-compared-with-the-original-leaked-source)
|
|
||||||
- [Project Structure](#project-structure)
|
|
||||||
- [Tech Stack](#tech-stack)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -30,11 +32,13 @@ A **locally runnable version** repaired from the leaked Claude Code source, with
|
|||||||
- Full Ink TUI experience (matching the official Claude Code interface)
|
- Full Ink TUI experience (matching the official Claude Code interface)
|
||||||
- `--print` headless mode for scripts and CI
|
- `--print` headless mode for scripts and CI
|
||||||
- MCP server, plugin, and Skills support
|
- MCP server, plugin, and Skills support
|
||||||
- Custom API endpoint and model support ([Third-Party Models Guide](docs/third-party-models.en.md))
|
- Custom API endpoint and model support ([Third-Party Models Guide](docs/en/guide/third-party-models.md))
|
||||||
- **Computer Use desktop control** (screenshots, mouse, keyboard, app management) — [Guide](docs/computer-use.en.md)
|
- **Memory System** (cross-session persistent memory) — [Usage Guide](docs/memory/01-usage-guide.md)
|
||||||
- Fallback Recovery CLI mode
|
- **Multi-Agent System** (agent orchestration, parallel tasks, Teams collaboration) — [Usage Guide](docs/agent/01-usage-guide.md) | [Implementation](docs/agent/02-implementation.md)
|
||||||
|
- **Skills System** (extensible capability plugins, custom workflows) — [Usage Guide](docs/skills/01-usage-guide.md) | [Implementation](docs/skills/02-implementation.md)
|
||||||
> **Computer Use Note**: This project includes a **modified version of Computer Use**. The official implementation relies on Anthropic's private native modules. We replaced the entire underlying operation layer with a Python bridge (`pyautogui` + `mss` + `pyobjc`), enabling anyone to use Computer Use on macOS. See the [Computer Use Guide](docs/computer-use.en.md) for details.
|
- **Channel System** (remote Agent control via Telegram/Feishu/Discord IM platforms) — [Architecture](docs/en/channel/01-channel-system.md)
|
||||||
|
- **Computer Use desktop control** — [Guide](docs/en/features/computer-use.md) | [Architecture](docs/en/features/computer-use-architecture.md)
|
||||||
|
- Fallback Recovery CLI mode (`CLAUDE_CODE_FORCE_RECOVERY_CLI=1 ./bin/claude-haha`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -42,16 +46,16 @@ A **locally runnable version** repaired from the leaked Claude Code source, with
|
|||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" width="25%"><img src="docs/01-overall-architecture.png" alt="Overall architecture"><br><b>Overall architecture</b></td>
|
<td align="center" width="25%"><img src="docs/images/01-overall-architecture.png" alt="Overall architecture"><br><b>Overall architecture</b></td>
|
||||||
<td align="center" width="25%"><img src="docs/02-request-lifecycle.png" alt="Request lifecycle"><br><b>Request lifecycle</b></td>
|
<td align="center" width="25%"><img src="docs/images/02-request-lifecycle.png" alt="Request lifecycle"><br><b>Request lifecycle</b></td>
|
||||||
<td align="center" width="25%"><img src="docs/03-tool-system.png" alt="Tool system"><br><b>Tool system</b></td>
|
<td align="center" width="25%"><img src="docs/images/03-tool-system.png" alt="Tool system"><br><b>Tool system</b></td>
|
||||||
<td align="center" width="25%"><img src="docs/04-multi-agent.png" alt="Multi-agent architecture"><br><b>Multi-agent architecture</b></td>
|
<td align="center" width="25%"><img src="docs/images/04-multi-agent.png" alt="Multi-agent architecture"><br><b>Multi-agent architecture</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" width="25%"><img src="docs/05-terminal-ui.png" alt="Terminal UI"><br><b>Terminal UI</b></td>
|
<td align="center" width="25%"><img src="docs/images/05-terminal-ui.png" alt="Terminal UI"><br><b>Terminal UI</b></td>
|
||||||
<td align="center" width="25%"><img src="docs/06-permission-security.png" alt="Permissions and security"><br><b>Permissions and security</b></td>
|
<td align="center" width="25%"><img src="docs/images/06-permission-security.png" alt="Permissions and security"><br><b>Permissions and security</b></td>
|
||||||
<td align="center" width="25%"><img src="docs/07-services-layer.png" alt="Services layer"><br><b>Services layer</b></td>
|
<td align="center" width="25%"><img src="docs/images/07-services-layer.png" alt="Services layer"><br><b>Services layer</b></td>
|
||||||
<td align="center" width="25%"><img src="docs/08-state-data-flow.png" alt="State and data flow"><br><b>State and data flow</b></td>
|
<td align="center" width="25%"><img src="docs/images/08-state-data-flow.png" alt="State and data flow"><br><b>State and data flow</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -61,215 +65,55 @@ A **locally runnable version** repaired from the leaked Claude Code source, with
|
|||||||
|
|
||||||
### 1. Install Bun
|
### 1. Install Bun
|
||||||
|
|
||||||
This project requires [Bun](https://bun.sh). If Bun is not installed on the target machine yet, use one of the following methods first:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# macOS / Linux (official install script)
|
# macOS / Linux
|
||||||
curl -fsSL https://bun.sh/install | bash
|
curl -fsSL https://bun.sh/install | bash
|
||||||
```
|
|
||||||
|
|
||||||
If a minimal Linux image reports `unzip is required to install bun`, install `unzip` first:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Ubuntu / Debian
|
|
||||||
apt update && apt install -y unzip
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# macOS (Homebrew)
|
# macOS (Homebrew)
|
||||||
brew install bun
|
brew install bun
|
||||||
```
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# Windows (PowerShell)
|
# Windows (PowerShell)
|
||||||
powershell -c "irm bun.sh/install.ps1 | iex"
|
powershell -c "irm bun.sh/install.ps1 | iex"
|
||||||
```
|
```
|
||||||
|
|
||||||
After installation, reopen the terminal and verify:
|
> On minimal Linux images, if you see `unzip is required`, run `apt update && apt install -y unzip` first.
|
||||||
|
|
||||||
```bash
|
### 2. Install Dependencies and Configure
|
||||||
bun --version
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Install project dependencies
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun install
|
bun install
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Configure environment variables
|
|
||||||
|
|
||||||
Copy the example file and fill in your API key:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
|
# Edit .env with your API key — see docs/en/guide/env-vars.md for details
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit `.env` (the example below uses [MiniMax](https://platform.minimaxi.com/subscribe/token-plan?code=1TG2Cseab2&source=link) as the API provider — you can replace it with any compatible service):
|
### 3. Start
|
||||||
|
|
||||||
```env
|
|
||||||
# API authentication (choose one)
|
|
||||||
ANTHROPIC_API_KEY=sk-xxx # Standard API key via x-api-key header
|
|
||||||
ANTHROPIC_AUTH_TOKEN=sk-xxx # Bearer token via Authorization header
|
|
||||||
|
|
||||||
# API endpoint (optional, defaults to Anthropic)
|
|
||||||
ANTHROPIC_BASE_URL=https://api.minimaxi.com/anthropic
|
|
||||||
|
|
||||||
# Model configuration
|
|
||||||
ANTHROPIC_MODEL=MiniMax-M2.7-highspeed
|
|
||||||
ANTHROPIC_DEFAULT_SONNET_MODEL=MiniMax-M2.7-highspeed
|
|
||||||
ANTHROPIC_DEFAULT_HAIKU_MODEL=MiniMax-M2.7-highspeed
|
|
||||||
ANTHROPIC_DEFAULT_OPUS_MODEL=MiniMax-M2.7-highspeed
|
|
||||||
|
|
||||||
# Timeout in milliseconds
|
|
||||||
API_TIMEOUT_MS=3000000
|
|
||||||
|
|
||||||
# Disable telemetry and non-essential network traffic
|
|
||||||
DISABLE_TELEMETRY=1
|
|
||||||
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Tip**: You can also configure environment variables via the `env` field in `~/.claude/settings.json`. This is consistent with the official Claude Code configuration:
|
|
||||||
>
|
|
||||||
> ```json
|
|
||||||
> {
|
|
||||||
> "env": {
|
|
||||||
> "ANTHROPIC_AUTH_TOKEN": "sk-xxx",
|
|
||||||
> "ANTHROPIC_BASE_URL": "https://api.minimaxi.com/anthropic",
|
|
||||||
> "ANTHROPIC_MODEL": "MiniMax-M2.7-highspeed"
|
|
||||||
> }
|
|
||||||
> }
|
|
||||||
> ```
|
|
||||||
>
|
|
||||||
> Priority: Environment variables > `.env` file > `~/.claude/settings.json`
|
|
||||||
|
|
||||||
### 4. Start
|
|
||||||
|
|
||||||
#### macOS / Linux
|
#### macOS / Linux
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Interactive TUI mode (full interface)
|
./bin/claude-haha # Interactive TUI mode
|
||||||
./bin/claude-haha
|
./bin/claude-haha -p "your prompt here" # Headless mode
|
||||||
|
./bin/claude-haha --help # Show all options
|
||||||
# Headless mode (single prompt)
|
|
||||||
./bin/claude-haha -p "your prompt here"
|
|
||||||
|
|
||||||
# Pipe input
|
|
||||||
echo "explain this code" | ./bin/claude-haha -p
|
|
||||||
|
|
||||||
# Show all options
|
|
||||||
./bin/claude-haha --help
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
|
|
||||||
> **Prerequisite**: [Git for Windows](https://git-scm.com/download/win) must be installed (provides Git Bash, which the project's internal shell execution depends on).
|
> **Prerequisite**: [Git for Windows](https://git-scm.com/download/win) must be installed.
|
||||||
|
|
||||||
The startup script `bin/claude-haha` is a bash script and cannot run directly in cmd or PowerShell. Use one of the following methods:
|
|
||||||
|
|
||||||
**Option 1: PowerShell / cmd — call Bun directly (recommended)**
|
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Interactive TUI mode
|
# PowerShell / cmd — call Bun directly
|
||||||
bun --env-file=.env ./src/entrypoints/cli.tsx
|
bun --env-file=.env ./src/entrypoints/cli.tsx
|
||||||
|
|
||||||
# Headless mode
|
# Or run inside Git Bash
|
||||||
bun --env-file=.env ./src/entrypoints/cli.tsx -p "your prompt here"
|
|
||||||
|
|
||||||
# Fallback Recovery CLI
|
|
||||||
bun --env-file=.env ./src/localRecoveryCli.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option 2: Run inside Git Bash**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Same usage as macOS / Linux
|
|
||||||
./bin/claude-haha
|
./bin/claude-haha
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note**: Some features (voice input, Computer Use, sandbox isolation, etc.) are not available on Windows. This does not affect the core TUI interaction.
|
### 4. Global Usage (Optional)
|
||||||
|
|
||||||
---
|
Add `bin/` to your PATH to run from any directory. See [Global Usage Guide](docs/en/guide/global-usage.md):
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
| Variable | Required | Description |
|
|
||||||
|------|------|------|
|
|
||||||
| `ANTHROPIC_API_KEY` | One of two | API key sent via the `x-api-key` header |
|
|
||||||
| `ANTHROPIC_AUTH_TOKEN` | One of two | Auth token sent via the `Authorization: Bearer` header |
|
|
||||||
| `ANTHROPIC_BASE_URL` | No | Custom API endpoint, defaults to Anthropic |
|
|
||||||
| `ANTHROPIC_MODEL` | No | Default model |
|
|
||||||
| `ANTHROPIC_DEFAULT_SONNET_MODEL` | No | Sonnet-tier model mapping |
|
|
||||||
| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | No | Haiku-tier model mapping |
|
|
||||||
| `ANTHROPIC_DEFAULT_OPUS_MODEL` | No | Opus-tier model mapping |
|
|
||||||
| `API_TIMEOUT_MS` | No | API request timeout, default `600000` (10min) |
|
|
||||||
| `DISABLE_TELEMETRY` | No | Set to `1` to disable telemetry |
|
|
||||||
| `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | No | Set to `1` to disable non-essential network traffic |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Fallback Mode
|
|
||||||
|
|
||||||
If the full TUI has issues, use the simplified readline-based interaction mode:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
CLAUDE_CODE_FORCE_RECOVERY_CLI=1 ./bin/claude-haha
|
export PATH="$HOME/path/to/claude-code-haha/bin:$PATH"
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Computer Use Desktop Control
|
|
||||||
|
|
||||||
This project enables and modifies Claude Code's Computer Use feature (internal codename "Chicago"), allowing AI models to directly control your macOS desktop — screenshots, mouse clicks, keyboard input, app management.
|
|
||||||
|
|
||||||
**Underlying modification**: The official implementation depends on Anthropic's private native modules (`@ant/computer-use-swift`, `@ant/computer-use-input`). This project replaces them entirely with a Python bridge using `pyautogui` (mouse/keyboard), `mss` (screenshots), and `pyobjc` (macOS APIs) — no closed-source binaries required.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Ensure Python 3 and macOS Accessibility/Screen Recording permissions, then:
|
|
||||||
./bin/claude-haha
|
|
||||||
> Take a screenshot
|
|
||||||
> Open Safari and search for something
|
|
||||||
```
|
|
||||||
|
|
||||||
For supported platforms, technical architecture, and approaches we tried, see: **[Computer Use Guide](docs/computer-use.en.md)**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Fixes Compared with the Original Leaked Source
|
|
||||||
|
|
||||||
The leaked source could not run directly. This repository mainly fixes the following issues:
|
|
||||||
|
|
||||||
| Issue | Root cause | Fix |
|
|
||||||
|------|------|------|
|
|
||||||
| TUI does not start | The entry script routed no-argument startup to the recovery CLI | Restored the full `cli.tsx` entry |
|
|
||||||
| Startup hangs | The `verify` skill imports a missing `.md` file, causing Bun's text loader to hang indefinitely | Added stub `.md` files |
|
|
||||||
| `--print` hangs | `filePersistence/types.ts` was missing | Added type stub files |
|
|
||||||
| `--print` hangs | `ultraplan/prompt.txt` was missing | Added resource stub files |
|
|
||||||
| **Enter key does nothing** | The `modifiers-napi` native package was missing, `isModifierPressed()` threw, `handleEnter` was interrupted, and `onSubmit` never ran | Added try/catch fault tolerance |
|
|
||||||
| Setup was skipped | `preload.ts` automatically set `LOCAL_RECOVERY=1`, skipping all initialization | Removed the default setting |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```text
|
|
||||||
bin/claude-haha # Entry script
|
|
||||||
preload.ts # Bun preload (sets MACRO globals)
|
|
||||||
.env.example # Environment variable template
|
|
||||||
src/
|
|
||||||
├── entrypoints/cli.tsx # Main CLI entry
|
|
||||||
├── main.tsx # Main TUI logic (Commander.js + React/Ink)
|
|
||||||
├── localRecoveryCli.ts # Fallback Recovery CLI
|
|
||||||
├── setup.ts # Startup initialization
|
|
||||||
├── screens/REPL.tsx # Interactive REPL screen
|
|
||||||
├── ink/ # Ink terminal rendering engine
|
|
||||||
├── components/ # UI components
|
|
||||||
├── tools/ # Agent tools (Bash, Edit, Grep, etc.)
|
|
||||||
├── commands/ # Slash commands (/commit, /review, etc.)
|
|
||||||
├── skills/ # Skill system
|
|
||||||
├── services/ # Service layer (API, MCP, OAuth, etc.)
|
|
||||||
├── hooks/ # React hooks
|
|
||||||
└── utils/ # Utility functions
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -287,39 +131,21 @@ src/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## FAQ
|
## More Documentation
|
||||||
|
|
||||||
### Q: `undefined is not an object (evaluating 'usage.input_tokens')`
|
| Document | Description |
|
||||||
|
|------|------|
|
||||||
**Cause**: `ANTHROPIC_BASE_URL` is misconfigured. The API endpoint is returning HTML or another non-JSON format instead of a valid Anthropic protocol response.
|
| [Environment Variables](docs/en/guide/env-vars.md) | Full env var reference and configuration methods |
|
||||||
|
| [Third-Party Models](docs/en/guide/third-party-models.md) | Using OpenAI / DeepSeek / Ollama and other non-Anthropic models |
|
||||||
This project uses the **Anthropic Messages API protocol**. `ANTHROPIC_BASE_URL` must point to an endpoint compatible with Anthropic's `/v1/messages` interface. The Anthropic SDK automatically appends `/v1/messages` to the base URL, so:
|
| [Memory System](docs/memory/01-usage-guide.md) | Cross-session persistent memory usage and implementation |
|
||||||
|
| [Multi-Agent System](docs/agent/01-usage-guide.md) | Agent orchestration, parallel tasks and Teams collaboration |
|
||||||
- MiniMax: `ANTHROPIC_BASE_URL=https://api.minimaxi.com/anthropic` ✅
|
| [Skills System](docs/skills/01-usage-guide.md) | Extensible capability plugins, custom workflows and conditional activation |
|
||||||
- OpenRouter: `ANTHROPIC_BASE_URL=https://openrouter.ai/api` ✅
|
| [Channel System](docs/en/channel/01-channel-system.md) | Remote Agent control via Telegram/Feishu/Discord IM platforms |
|
||||||
- OpenRouter (wrong): `ANTHROPIC_BASE_URL=https://openrouter.ai/anthropic` ❌ (returns HTML)
|
| [Computer Use](docs/en/features/computer-use.md) | Desktop control (screenshots, mouse, keyboard) — [Architecture](docs/en/features/computer-use-architecture.md) |
|
||||||
|
| [Global Usage](docs/en/guide/global-usage.md) | Run claude-haha from any directory |
|
||||||
If your model provider only supports the OpenAI protocol, you need a proxy like LiteLLM for protocol translation. See the [Third-Party Models Guide](docs/third-party-models.en.md).
|
| [FAQ](docs/en/guide/faq.md) | Common error troubleshooting |
|
||||||
|
| [Source Fixes](docs/en/reference/fixes.md) | Fixes compared with the original leaked source |
|
||||||
### Q: `Cannot find package 'bundle'`
|
| [Project Structure](docs/en/reference/project-structure.md) | Code directory structure |
|
||||||
|
|
||||||
```
|
|
||||||
error: Cannot find package 'bundle' from '.../claude-code-haha/src/entrypoints/cli.tsx'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cause**: Your Bun version is too old and doesn't support the required `bun:bundle` built-in module.
|
|
||||||
|
|
||||||
**Fix**: Upgrade Bun to the latest version:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bun upgrade
|
|
||||||
```
|
|
||||||
|
|
||||||
### Q: How to use OpenAI / DeepSeek / Ollama or other non-Anthropic models?
|
|
||||||
|
|
||||||
This project only supports the Anthropic protocol. If your model provider doesn't natively support the Anthropic protocol, you need a proxy like [LiteLLM](https://github.com/BerriAI/litellm) for protocol translation (OpenAI → Anthropic).
|
|
||||||
|
|
||||||
See the [Third-Party Models Guide](docs/third-party-models.en.md) for detailed setup instructions.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,30 @@
|
|||||||
# Claude Code Haha
|
# Claude Code Haha
|
||||||
|
|
||||||
<p align="right"><strong>中文</strong> | <a href="./README.en.md">English</a></p>
|
<p align="center">
|
||||||
|
<img src="docs/images/banner.jpg" alt="Claude Code Haha Banner" width="800">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
[](https://github.com/NanmiCoder/cc-haha/stargazers)
|
||||||
|
[](https://github.com/NanmiCoder/cc-haha/network/members)
|
||||||
|
[](https://github.com/NanmiCoder/cc-haha/issues)
|
||||||
|
[](https://github.com/NanmiCoder/cc-haha/pulls)
|
||||||
|
[](https://github.com/NanmiCoder/cc-haha/blob/main/LICENSE)
|
||||||
|
[](README.md)
|
||||||
|
[](README.en.md)
|
||||||
|
[](https://claudecode-haha.relakkesyang.org)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
基于 Claude Code 泄露源码修复的**本地可运行版本**,支持接入任意 Anthropic 兼容 API(如 MiniMax、OpenRouter 等)。
|
基于 Claude Code 泄露源码修复的**本地可运行版本**,支持接入任意 Anthropic 兼容 API(如 MiniMax、OpenRouter 等)。
|
||||||
|
|
||||||
> 原始泄露源码无法直接运行。本仓库修复了启动链路中的多个阻塞问题,使完整的 Ink TUI 交互界面可以在本地工作。
|
> 原始泄露源码无法直接运行。本仓库修复了启动链路中的多个阻塞问题,使完整的 Ink TUI 交互界面可以在本地工作。
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="docs/00runtime.png" alt="运行截图" width="800">
|
<a href="#功能">功能</a> · <a href="#架构概览">架构概览</a> · <a href="#快速开始">快速开始</a> · <a href="docs/guide/env-vars.md">环境变量</a> · <a href="docs/guide/faq.md">FAQ</a> · <a href="docs/guide/global-usage.md">全局使用</a> · <a href="#更多文档">更多文档</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## 目录
|
|
||||||
|
|
||||||
- [功能](#功能)
|
|
||||||
- [架构概览](#架构概览)
|
|
||||||
- [快速开始](#快速开始)
|
|
||||||
- [环境变量说明](#环境变量说明)
|
|
||||||
- [降级模式](#降级模式)
|
|
||||||
- [Computer Use 桌面控制](#computer-use-桌面控制)
|
|
||||||
- [常见问题](#常见问题)
|
|
||||||
- [相对于原始泄露源码的修复](#相对于原始泄露源码的修复)
|
|
||||||
- [项目结构](#项目结构)
|
|
||||||
- [技术栈](#技术栈)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
@@ -30,11 +32,13 @@
|
|||||||
- 完整的 Ink TUI 交互界面(与官方 Claude Code 一致)
|
- 完整的 Ink TUI 交互界面(与官方 Claude Code 一致)
|
||||||
- `--print` 无头模式(脚本/CI 场景)
|
- `--print` 无头模式(脚本/CI 场景)
|
||||||
- 支持 MCP 服务器、插件、Skills
|
- 支持 MCP 服务器、插件、Skills
|
||||||
- 支持自定义 API 端点和模型([第三方模型使用指南](docs/third-party-models.md))
|
- 支持自定义 API 端点和模型([第三方模型使用指南](docs/guide/third-party-models.md))
|
||||||
- **Computer Use 桌面控制**(截屏、鼠标、键盘、应用管理)— [使用指南](docs/computer-use.md)
|
- **记忆系统**(跨会话持久化记忆)— [使用指南](docs/memory/01-usage-guide.md)
|
||||||
- 降级 Recovery CLI 模式
|
- **多 Agent 系统**(多代理编排、并行任务、Teams 协作)— [使用指南](docs/agent/01-usage-guide.md) | [实现原理](docs/agent/02-implementation.md)
|
||||||
|
- **Skills 系统**(可扩展能力插件、自定义工作流)— [使用指南](docs/skills/01-usage-guide.md) | [实现原理](docs/skills/02-implementation.md)
|
||||||
> **Computer Use 说明**:本项目包含**魔改版的 Computer Use** 功能。官方实现依赖 Anthropic 私有原生模块,我们替换了整个底层操作层,使用 Python bridge(`pyautogui` + `mss` + `pyobjc`)实现,使得任何人都可以在 macOS 上使用。详见 [Computer Use 功能指南](docs/computer-use.md)。
|
- **Channel 系统**(通过 Telegram/飞书/Discord 等 IM 远程控制 Agent)— [架构解析](docs/channel/01-channel-system.md)
|
||||||
|
- **Computer Use 桌面控制** — [功能指南](docs/features/computer-use.md) | [架构解析](docs/features/computer-use-architecture.md)
|
||||||
|
- 降级 Recovery CLI 模式(`CLAUDE_CODE_FORCE_RECOVERY_CLI=1 ./bin/claude-haha`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -42,16 +46,16 @@
|
|||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" width="25%"><img src="docs/01-overall-architecture.png" alt="整体架构"><br><b>整体架构</b></td>
|
<td align="center" width="25%"><img src="docs/images/01-overall-architecture.png" alt="整体架构"><br><b>整体架构</b></td>
|
||||||
<td align="center" width="25%"><img src="docs/02-request-lifecycle.png" alt="请求生命周期"><br><b>请求生命周期</b></td>
|
<td align="center" width="25%"><img src="docs/images/02-request-lifecycle.png" alt="请求生命周期"><br><b>请求生命周期</b></td>
|
||||||
<td align="center" width="25%"><img src="docs/03-tool-system.png" alt="工具系统"><br><b>工具系统</b></td>
|
<td align="center" width="25%"><img src="docs/images/03-tool-system.png" alt="工具系统"><br><b>工具系统</b></td>
|
||||||
<td align="center" width="25%"><img src="docs/04-multi-agent.png" alt="多 Agent 架构"><br><b>多 Agent 架构</b></td>
|
<td align="center" width="25%"><img src="docs/images/04-multi-agent.png" alt="多 Agent 架构"><br><b>多 Agent 架构</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" width="25%"><img src="docs/05-terminal-ui.png" alt="终端 UI"><br><b>终端 UI</b></td>
|
<td align="center" width="25%"><img src="docs/images/05-terminal-ui.png" alt="终端 UI"><br><b>终端 UI</b></td>
|
||||||
<td align="center" width="25%"><img src="docs/06-permission-security.png" alt="权限与安全"><br><b>权限与安全</b></td>
|
<td align="center" width="25%"><img src="docs/images/06-permission-security.png" alt="权限与安全"><br><b>权限与安全</b></td>
|
||||||
<td align="center" width="25%"><img src="docs/07-services-layer.png" alt="服务层"><br><b>服务层</b></td>
|
<td align="center" width="25%"><img src="docs/images/07-services-layer.png" alt="服务层"><br><b>服务层</b></td>
|
||||||
<td align="center" width="25%"><img src="docs/08-state-data-flow.png" alt="状态与数据流"><br><b>状态与数据流</b></td>
|
<td align="center" width="25%"><img src="docs/images/08-state-data-flow.png" alt="状态与数据流"><br><b>状态与数据流</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -61,215 +65,55 @@
|
|||||||
|
|
||||||
### 1. 安装 Bun
|
### 1. 安装 Bun
|
||||||
|
|
||||||
本项目运行依赖 [Bun](https://bun.sh)。如果你的电脑还没有安装 Bun,可以先执行下面任一方式:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# macOS / Linux(官方安装脚本)
|
# macOS / Linux
|
||||||
curl -fsSL https://bun.sh/install | bash
|
curl -fsSL https://bun.sh/install | bash
|
||||||
```
|
|
||||||
|
|
||||||
如果在精简版 Linux 环境里提示 `unzip is required to install bun`,先安装 `unzip`:
|
# macOS (Homebrew)
|
||||||
|
|
||||||
```bash
|
|
||||||
# Ubuntu / Debian
|
|
||||||
apt update && apt install -y unzip
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# macOS(Homebrew)
|
|
||||||
brew install bun
|
brew install bun
|
||||||
```
|
|
||||||
|
|
||||||
```powershell
|
# Windows (PowerShell)
|
||||||
# Windows(PowerShell)
|
|
||||||
powershell -c "irm bun.sh/install.ps1 | iex"
|
powershell -c "irm bun.sh/install.ps1 | iex"
|
||||||
```
|
```
|
||||||
|
|
||||||
安装完成后,重新打开终端并确认:
|
> 精简版 Linux 如提示 `unzip is required`,先运行 `apt update && apt install -y unzip`
|
||||||
|
|
||||||
```bash
|
### 2. 安装依赖并配置
|
||||||
bun --version
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 安装项目依赖
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun install
|
bun install
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 配置环境变量
|
|
||||||
|
|
||||||
复制示例文件并填入你的 API Key:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
|
# 编辑 .env 填入你的 API Key,详见 docs/guide/env-vars.md
|
||||||
```
|
```
|
||||||
|
|
||||||
编辑 `.env`(以下示例使用 [MiniMax](https://platform.minimaxi.com/subscribe/token-plan?code=1TG2Cseab2&source=link) 作为 API 提供商,也可替换为其他兼容服务):
|
### 3. 启动
|
||||||
|
|
||||||
```env
|
|
||||||
# API 认证(二选一)
|
|
||||||
ANTHROPIC_API_KEY=sk-xxx # 标准 API Key(x-api-key 头)
|
|
||||||
ANTHROPIC_AUTH_TOKEN=sk-xxx # Bearer Token(Authorization 头)
|
|
||||||
|
|
||||||
# API 端点(可选,默认 Anthropic 官方)
|
|
||||||
ANTHROPIC_BASE_URL=https://api.minimaxi.com/anthropic
|
|
||||||
|
|
||||||
# 模型配置
|
|
||||||
ANTHROPIC_MODEL=MiniMax-M2.7-highspeed
|
|
||||||
ANTHROPIC_DEFAULT_SONNET_MODEL=MiniMax-M2.7-highspeed
|
|
||||||
ANTHROPIC_DEFAULT_HAIKU_MODEL=MiniMax-M2.7-highspeed
|
|
||||||
ANTHROPIC_DEFAULT_OPUS_MODEL=MiniMax-M2.7-highspeed
|
|
||||||
|
|
||||||
# 超时(毫秒)
|
|
||||||
API_TIMEOUT_MS=3000000
|
|
||||||
|
|
||||||
# 禁用遥测和非必要网络请求
|
|
||||||
DISABLE_TELEMETRY=1
|
|
||||||
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
|
|
||||||
```
|
|
||||||
|
|
||||||
> **提示**:除了 `.env` 文件,你也可以通过 `~/.claude/settings.json` 的 `env` 字段配置环境变量。这与官方 Claude Code 的配置方式一致:
|
|
||||||
>
|
|
||||||
> ```json
|
|
||||||
> {
|
|
||||||
> "env": {
|
|
||||||
> "ANTHROPIC_AUTH_TOKEN": "sk-xxx",
|
|
||||||
> "ANTHROPIC_BASE_URL": "https://api.minimaxi.com/anthropic",
|
|
||||||
> "ANTHROPIC_MODEL": "MiniMax-M2.7-highspeed"
|
|
||||||
> }
|
|
||||||
> }
|
|
||||||
> ```
|
|
||||||
>
|
|
||||||
> 配置优先级:环境变量 > `.env` 文件 > `~/.claude/settings.json`
|
|
||||||
|
|
||||||
### 4. 启动
|
|
||||||
|
|
||||||
#### macOS / Linux
|
#### macOS / Linux
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 交互 TUI 模式(完整界面)
|
./bin/claude-haha # 交互 TUI 模式
|
||||||
./bin/claude-haha
|
./bin/claude-haha -p "your prompt here" # 无头模式
|
||||||
|
./bin/claude-haha --help # 查看所有选项
|
||||||
# 无头模式(单次问答)
|
|
||||||
./bin/claude-haha -p "your prompt here"
|
|
||||||
|
|
||||||
# 管道输入
|
|
||||||
echo "explain this code" | ./bin/claude-haha -p
|
|
||||||
|
|
||||||
# 查看所有选项
|
|
||||||
./bin/claude-haha --help
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
|
|
||||||
> **前置要求**:必须安装 [Git for Windows](https://git-scm.com/download/win)(提供 Git Bash,项目内部 Shell 执行依赖它)。
|
> **前置要求**:必须安装 [Git for Windows](https://git-scm.com/download/win)
|
||||||
|
|
||||||
Windows 下启动脚本 `bin/claude-haha` 是 bash 脚本,无法在 cmd / PowerShell 中直接运行。请使用以下方式:
|
|
||||||
|
|
||||||
**方式一:PowerShell / cmd 直接调用 Bun(推荐)**
|
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# 交互 TUI 模式
|
# PowerShell / cmd 直接调用 Bun
|
||||||
bun --env-file=.env ./src/entrypoints/cli.tsx
|
bun --env-file=.env ./src/entrypoints/cli.tsx
|
||||||
|
|
||||||
# 无头模式
|
# 或在 Git Bash 中运行
|
||||||
bun --env-file=.env ./src/entrypoints/cli.tsx -p "your prompt here"
|
|
||||||
|
|
||||||
# 降级 Recovery CLI
|
|
||||||
bun --env-file=.env ./src/localRecoveryCli.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
**方式二:Git Bash 中运行**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 在 Git Bash 终端中,与 macOS/Linux 用法一致
|
|
||||||
./bin/claude-haha
|
./bin/claude-haha
|
||||||
```
|
```
|
||||||
|
|
||||||
> **注意**:部分功能(语音输入、Computer Use、Sandbox 隔离等)在 Windows 上不可用,不影响核心 TUI 交互。
|
### 4. 全局使用(可选)
|
||||||
|
|
||||||
---
|
将 `bin/` 加入 PATH 后可在任意目录启动,详见 [全局使用指南](docs/guide/global-usage.md):
|
||||||
|
|
||||||
## 环境变量说明
|
|
||||||
|
|
||||||
| 变量 | 必填 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| `ANTHROPIC_API_KEY` | 二选一 | API Key,通过 `x-api-key` 头发送 |
|
|
||||||
| `ANTHROPIC_AUTH_TOKEN` | 二选一 | Auth Token,通过 `Authorization: Bearer` 头发送 |
|
|
||||||
| `ANTHROPIC_BASE_URL` | 否 | 自定义 API 端点,默认 Anthropic 官方 |
|
|
||||||
| `ANTHROPIC_MODEL` | 否 | 默认模型 |
|
|
||||||
| `ANTHROPIC_DEFAULT_SONNET_MODEL` | 否 | Sonnet 级别模型映射 |
|
|
||||||
| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | 否 | Haiku 级别模型映射 |
|
|
||||||
| `ANTHROPIC_DEFAULT_OPUS_MODEL` | 否 | Opus 级别模型映射 |
|
|
||||||
| `API_TIMEOUT_MS` | 否 | API 请求超时,默认 600000 (10min) |
|
|
||||||
| `DISABLE_TELEMETRY` | 否 | 设为 `1` 禁用遥测 |
|
|
||||||
| `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | 否 | 设为 `1` 禁用非必要网络请求 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 降级模式
|
|
||||||
|
|
||||||
如果完整 TUI 出现问题,可以使用简化版 readline 交互模式:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
CLAUDE_CODE_FORCE_RECOVERY_CLI=1 ./bin/claude-haha
|
export PATH="$HOME/path/to/claude-code-haha/bin:$PATH"
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Computer Use 桌面控制
|
|
||||||
|
|
||||||
本项目启用并改造了 Claude Code 的 Computer Use 功能(内部代号 "Chicago"),让 AI 模型可以直接控制你的 macOS 桌面——截屏、鼠标点击、键盘输入、应用管理。
|
|
||||||
|
|
||||||
**底层改造**:官方实现依赖 Anthropic 私有原生模块(`@ant/computer-use-swift`、`@ant/computer-use-input`),本项目用 Python bridge 完全替代,使用 `pyautogui`(鼠标键盘)、`mss`(截图)、`pyobjc`(macOS API),无需任何闭源二进制。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 确保有 Python 3 和 macOS 辅助功能/屏幕录制权限,然后直接使用:
|
|
||||||
./bin/claude-haha
|
|
||||||
> 帮我截个屏
|
|
||||||
> 打开网易云音乐搜索一首歌
|
|
||||||
```
|
|
||||||
|
|
||||||
详细说明、支持的设备列表、技术架构和尝试过的方案请参考:**[Computer Use 功能指南](docs/computer-use.md)**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 相对于原始泄露源码的修复
|
|
||||||
|
|
||||||
泄露的源码无法直接运行,主要修复了以下问题:
|
|
||||||
|
|
||||||
| 问题 | 根因 | 修复 |
|
|
||||||
|------|------|------|
|
|
||||||
| TUI 不启动 | 入口脚本把无参数启动路由到了 recovery CLI | 恢复走 `cli.tsx` 完整入口 |
|
|
||||||
| 启动卡死 | `verify` skill 导入缺失的 `.md` 文件,Bun text loader 无限挂起 | 创建 stub `.md` 文件 |
|
|
||||||
| `--print` 卡死 | `filePersistence/types.ts` 缺失 | 创建类型桩文件 |
|
|
||||||
| `--print` 卡死 | `ultraplan/prompt.txt` 缺失 | 创建资源桩文件 |
|
|
||||||
| **Enter 键无响应** | `modifiers-napi` native 包缺失,`isModifierPressed()` 抛异常导致 `handleEnter` 中断,`onSubmit` 永远不执行 | 加 try-catch 容错 |
|
|
||||||
| setup 被跳过 | `preload.ts` 自动设置 `LOCAL_RECOVERY=1` 跳过全部初始化 | 移除默认设置 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
bin/claude-haha # 入口脚本
|
|
||||||
preload.ts # Bun preload(设置 MACRO 全局变量)
|
|
||||||
.env.example # 环境变量模板
|
|
||||||
src/
|
|
||||||
├── entrypoints/cli.tsx # CLI 主入口
|
|
||||||
├── main.tsx # TUI 主逻辑(Commander.js + React/Ink)
|
|
||||||
├── localRecoveryCli.ts # 降级 Recovery CLI
|
|
||||||
├── setup.ts # 启动初始化
|
|
||||||
├── screens/REPL.tsx # 交互 REPL 界面
|
|
||||||
├── ink/ # Ink 终端渲染引擎
|
|
||||||
├── components/ # UI 组件
|
|
||||||
├── tools/ # Agent 工具(Bash, Edit, Grep 等)
|
|
||||||
├── commands/ # 斜杠命令(/commit, /review 等)
|
|
||||||
├── skills/ # Skill 系统
|
|
||||||
├── services/ # 服务层(API, MCP, OAuth 等)
|
|
||||||
├── hooks/ # React hooks
|
|
||||||
└── utils/ # 工具函数
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -287,39 +131,21 @@ src/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 常见问题
|
## 更多文档
|
||||||
|
|
||||||
### Q: `undefined is not an object (evaluating 'usage.input_tokens')`
|
| 文档 | 说明 |
|
||||||
|
|------|------|
|
||||||
**原因**:`ANTHROPIC_BASE_URL` 配置不正确,API 端点返回的不是 Anthropic 协议格式的 JSON,而是 HTML 页面或其他格式。
|
| [环境变量](docs/guide/env-vars.md) | 完整环境变量参考和配置方式 |
|
||||||
|
| [第三方模型](docs/guide/third-party-models.md) | 接入 OpenAI / DeepSeek / Ollama 等非 Anthropic 模型 |
|
||||||
本项目使用 **Anthropic Messages API 协议**,`ANTHROPIC_BASE_URL` 必须指向一个兼容 Anthropic `/v1/messages` 接口的端点。Anthropic SDK 会自动在 base URL 后面拼接 `/v1/messages`,所以:
|
| [记忆系统](docs/memory/01-usage-guide.md) | 跨会话持久化记忆的使用与实现 |
|
||||||
|
| [多 Agent 系统](docs/agent/01-usage-guide.md) | 多代理编排、并行任务执行与 Teams 协作 |
|
||||||
- MiniMax:`ANTHROPIC_BASE_URL=https://api.minimaxi.com/anthropic` ✅
|
| [Skills 系统](docs/skills/01-usage-guide.md) | 可扩展能力插件、自定义工作流与条件激活 |
|
||||||
- OpenRouter:`ANTHROPIC_BASE_URL=https://openrouter.ai/api` ✅
|
| [Channel 系统](docs/channel/01-channel-system.md) | 通过 Telegram/飞书/Discord 等 IM 平台远程控制 Agent |
|
||||||
- OpenRouter 错误写法:`ANTHROPIC_BASE_URL=https://openrouter.ai/anthropic` ❌(返回 HTML)
|
| [Computer Use](docs/features/computer-use.md) | 桌面控制功能(截屏、鼠标、键盘)— [架构解析](docs/features/computer-use-architecture.md) |
|
||||||
|
| [全局使用](docs/guide/global-usage.md) | 在任意目录启动 claude-haha |
|
||||||
如果你的模型供应商只支持 OpenAI 协议,需要通过 LiteLLM 等代理做协议转换,详见 [第三方模型使用指南](docs/third-party-models.md)。
|
| [常见问题](docs/guide/faq.md) | 常见错误排查 |
|
||||||
|
| [源码修复记录](docs/reference/fixes.md) | 相对于原始泄露源码的修复内容 |
|
||||||
### Q: `Cannot find package 'bundle'`
|
| [项目结构](docs/reference/project-structure.md) | 代码目录结构说明 |
|
||||||
|
|
||||||
```
|
|
||||||
error: Cannot find package 'bundle' from '.../claude-code-haha/src/entrypoints/cli.tsx'
|
|
||||||
```
|
|
||||||
|
|
||||||
**原因**:Bun 版本过低,不支持项目所需的 `bun:bundle` 等内置模块。
|
|
||||||
|
|
||||||
**解决**:升级 Bun 到最新版本:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bun upgrade
|
|
||||||
```
|
|
||||||
|
|
||||||
### Q: 怎么接入 OpenAI / DeepSeek / Ollama 等非 Anthropic 模型?
|
|
||||||
|
|
||||||
本项目只支持 Anthropic 协议。如果模型供应商不直接支持 Anthropic 协议,需要用 [LiteLLM](https://github.com/BerriAI/litellm) 等代理做协议转换(OpenAI → Anthropic)。
|
|
||||||
|
|
||||||
详细配置步骤请参考:[第三方模型使用指南](docs/third-party-models.md)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
#Get your current working directory and export it as an environment variable.
|
||||||
|
export CALLER_DIR="$(pwd -W 2>/dev/null || pwd)"
|
||||||
|
|
||||||
cd "$ROOT_DIR"
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
# Force recovery CLI (simple readline REPL, no Ink TUI)
|
# Force recovery CLI (simple readline REPL, no Ink TUI)
|
||||||
|
|||||||
@@ -0,0 +1,217 @@
|
|||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
|
||||||
|
// GitHub-compatible slugify (matches github-slugger algorithm)
|
||||||
|
// Makes heading anchor IDs consistent between VitePress and GitHub rendering
|
||||||
|
function slugify(str: string): string {
|
||||||
|
return str
|
||||||
|
.replace(/<[^>]*>/g, '')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^\p{L}\p{M}\p{N}\p{Pc}\- ]/gu, '')
|
||||||
|
.replace(/ /g, '-')
|
||||||
|
}
|
||||||
|
|
||||||
|
const zhSidebar = [
|
||||||
|
{
|
||||||
|
text: '快速开始',
|
||||||
|
items: [
|
||||||
|
{ text: '安装与启动', link: '/guide/quick-start' },
|
||||||
|
{ text: '环境变量', link: '/guide/env-vars' },
|
||||||
|
{ text: '第三方模型', link: '/guide/third-party-models' },
|
||||||
|
{ text: '全局使用', link: '/guide/global-usage' },
|
||||||
|
{ text: '常见问题', link: '/guide/faq' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '记忆系统',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: '概览', link: '/memory/' },
|
||||||
|
{ text: '使用指南', link: '/memory/01-usage-guide' },
|
||||||
|
{ text: '实现原理', link: '/memory/02-implementation' },
|
||||||
|
{ text: 'AutoDream 记忆整合', link: '/memory/03-autodream' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '多 Agent 系统',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: '概览', link: '/agent/' },
|
||||||
|
{ text: '使用指南', link: '/agent/01-usage-guide' },
|
||||||
|
{ text: '实现原理', link: '/agent/02-implementation' },
|
||||||
|
{ text: 'Agent 框架解析', link: '/agent/03-agent-framework' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Skills 系统',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: '使用指南', link: '/skills/01-usage-guide' },
|
||||||
|
{ text: '实现原理', link: '/skills/02-implementation' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Channel 系统',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: '概览', link: '/channel/' },
|
||||||
|
{ text: '架构解析', link: '/channel/01-channel-system' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Computer Use',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: '功能指南', link: '/features/computer-use' },
|
||||||
|
{ text: '架构解析', link: '/features/computer-use-architecture' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '参考',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: '源码修复记录', link: '/reference/fixes' },
|
||||||
|
{ text: '项目结构', link: '/reference/project-structure' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const enSidebar = [
|
||||||
|
{
|
||||||
|
text: 'Getting Started',
|
||||||
|
items: [
|
||||||
|
{ text: 'Quick Start', link: '/en/guide/quick-start' },
|
||||||
|
{ text: 'Environment Variables', link: '/en/guide/env-vars' },
|
||||||
|
{ text: 'Third-Party Models', link: '/en/guide/third-party-models' },
|
||||||
|
{ text: 'Global Usage', link: '/en/guide/global-usage' },
|
||||||
|
{ text: 'FAQ', link: '/en/guide/faq' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Memory System',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: 'Overview', link: '/en/memory/' },
|
||||||
|
{ text: 'Usage Guide', link: '/en/memory/01-usage-guide' },
|
||||||
|
{ text: 'Implementation', link: '/en/memory/02-implementation' },
|
||||||
|
{ text: 'AutoDream', link: '/en/memory/03-autodream' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Multi-Agent System',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: 'Overview', link: '/en/agent/' },
|
||||||
|
{ text: 'Usage Guide', link: '/en/agent/01-usage-guide' },
|
||||||
|
{ text: 'Implementation', link: '/en/agent/02-implementation' },
|
||||||
|
{ text: 'Framework Deep Dive', link: '/en/agent/03-agent-framework' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Skills System',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: 'Usage Guide', link: '/en/skills/01-usage-guide' },
|
||||||
|
{ text: 'Implementation', link: '/en/skills/02-implementation' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Channel System',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: 'Overview', link: '/en/channel/' },
|
||||||
|
{ text: 'Architecture', link: '/en/channel/01-channel-system' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Computer Use',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{ text: 'Guide', link: '/en/features/computer-use' },
|
||||||
|
{ text: 'Architecture', link: '/en/features/computer-use-architecture' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Reference',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Source Fixes', link: '/en/reference/fixes' },
|
||||||
|
{ text: 'Project Structure', link: '/en/reference/project-structure' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
title: 'Claude Code Haha',
|
||||||
|
description: '基于 Claude Code 泄露源码修复的本地可运行版本,支持接入任意 Anthropic 兼容 API',
|
||||||
|
lastUpdated: true,
|
||||||
|
base: '/',
|
||||||
|
|
||||||
|
markdown: {
|
||||||
|
anchor: {
|
||||||
|
slugify,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
head: [
|
||||||
|
['script', { async: '', src: 'https://www.googletagmanager.com/gtag/js?id=G-D42DM82263' }],
|
||||||
|
['script', {}, `window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', 'G-D42DM82263');`],
|
||||||
|
['script', { async: '', src: 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-5210914487984731', crossorigin: 'anonymous' }],
|
||||||
|
],
|
||||||
|
|
||||||
|
locales: {
|
||||||
|
root: {
|
||||||
|
label: '中文',
|
||||||
|
lang: 'zh-CN',
|
||||||
|
themeConfig: {
|
||||||
|
nav: [
|
||||||
|
{ text: '首页', link: '/' },
|
||||||
|
{ text: '快速开始', link: '/guide/quick-start' },
|
||||||
|
],
|
||||||
|
sidebar: zhSidebar,
|
||||||
|
outline: { label: '页面导航' },
|
||||||
|
returnToTopLabel: '返回顶部',
|
||||||
|
sidebarMenuLabel: '菜单',
|
||||||
|
darkModeSwitchLabel: '主题',
|
||||||
|
lastUpdated: { text: '最后更新于' },
|
||||||
|
docFooter: { prev: '上一页', next: '下一页' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
label: 'English',
|
||||||
|
lang: 'en-US',
|
||||||
|
description: 'A locally runnable version repaired from the leaked Claude Code source, with support for any Anthropic-compatible API endpoint.',
|
||||||
|
themeConfig: {
|
||||||
|
editLink: {
|
||||||
|
pattern: 'https://github.com/NanmiCoder/cc-haha/edit/main/docs/:path',
|
||||||
|
text: 'Edit this page on GitHub',
|
||||||
|
},
|
||||||
|
nav: [
|
||||||
|
{ text: 'Home', link: '/en/' },
|
||||||
|
{ text: 'Quick Start', link: '/en/guide/quick-start' },
|
||||||
|
],
|
||||||
|
sidebar: enSidebar,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
themeConfig: {
|
||||||
|
editLink: {
|
||||||
|
pattern: 'https://github.com/NanmiCoder/cc-haha/edit/main/docs/:path',
|
||||||
|
text: '在 GitHub 上编辑此页',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
provider: 'local',
|
||||||
|
},
|
||||||
|
socialLinks: [
|
||||||
|
{ icon: 'github', link: 'https://github.com/NanmiCoder/cc-haha' },
|
||||||
|
],
|
||||||
|
footer: {
|
||||||
|
message: 'Released under the MIT License.',
|
||||||
|
copyright: 'Copyright 2026 Claude Code Haha Contributors',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
:root {
|
||||||
|
--vp-c-brand-1: #D97757;
|
||||||
|
--vp-c-brand-2: #c5684a;
|
||||||
|
--vp-c-brand-3: #b15a3e;
|
||||||
|
--vp-c-brand-soft: rgba(217, 119, 87, 0.14);
|
||||||
|
--vp-sidebar-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--vp-c-brand-1: #D97757;
|
||||||
|
--vp-c-brand-2: #e08868;
|
||||||
|
--vp-c-brand-3: #ea9a7a;
|
||||||
|
--vp-c-brand-soft: rgba(217, 119, 87, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hero image larger */
|
||||||
|
.VPHero .image-container {
|
||||||
|
max-width: 500px !important;
|
||||||
|
max-height: 500px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPHero .image-container .image-src {
|
||||||
|
max-width: 500px !important;
|
||||||
|
max-height: 500px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* medium-zoom overlay */
|
||||||
|
.medium-zoom-overlay {
|
||||||
|
z-index: 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.medium-zoom-image--opened {
|
||||||
|
z-index: 31;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import DefaultTheme from 'vitepress/theme'
|
||||||
|
import mediumZoom from 'medium-zoom'
|
||||||
|
import { onMounted, watch, nextTick } from 'vue'
|
||||||
|
import { useRoute } from 'vitepress'
|
||||||
|
import './custom.css'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
extends: DefaultTheme,
|
||||||
|
setup() {
|
||||||
|
const route = useRoute()
|
||||||
|
const initZoom = () => {
|
||||||
|
mediumZoom('.main img', { background: 'var(--vp-c-bg)' })
|
||||||
|
}
|
||||||
|
onMounted(() => initZoom())
|
||||||
|
watch(
|
||||||
|
() => route.path,
|
||||||
|
() => nextTick(() => initZoom())
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,443 @@
|
|||||||
|
# Claude Code 多 Agent 系统 — 使用指南
|
||||||
|
|
||||||
|
> 让 Claude Code 同时调度多个专业代理,并行处理复杂任务。
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#一什么是多-agent-系统">多 Agent 系统</a> · <a href="#二六种内置-agent">六种内置 Agent</a> · <a href="#三如何生成-agent">如何生成 Agent</a> · <a href="#四后台任务管理">后台任务管理</a> · <a href="#五agent-teams--多代理协作">Agent Teams</a> · <a href="#六自定义-agent">自定义 Agent</a> · <a href="#七权限模式">权限模式</a> · <a href="#八快速参考">快速参考</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、什么是多 Agent 系统?
|
||||||
|
|
||||||
|
Claude Code 的多 Agent 系统是一套**智能任务编排框架**,让主代理能够生成多个专业化的子代理(Subagent),各自独立执行不同的任务,最终将结果汇总给用户。
|
||||||
|
|
||||||
|
核心理念:**把大任务拆分为多个专业小任务,并行执行,提高效率。**
|
||||||
|
|
||||||
|
| 场景 | 传统方式 | 多 Agent 方式 |
|
||||||
|
|------|----------|---------------|
|
||||||
|
| 调研 5 个模块的架构 | 逐个串行探索 | 5 个 Explore agent 并行扫描 |
|
||||||
|
| 实现 + 测试 + 文档 | 顺序完成 | Team 成员各自负责一块 |
|
||||||
|
| 代码审查 | 单线程逐文件看 | 多个 reviewer 并行审查 |
|
||||||
|
| 调试复杂 bug | 一个假设一个假设试 | 多个 debugger 并行验证 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、六种内置 Agent
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Claude Code 内置了 6 种专业代理,每种都有特定的工具池和适用场景:
|
||||||
|
|
||||||
|
### 1. general-purpose(通用代理)
|
||||||
|
|
||||||
|
**适用场景**:复杂的多步骤研究、代码搜索、需要完整工具访问的任务。
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "调研认证模块",
|
||||||
|
prompt: "分析 src/auth/ 下所有文件的认证流程...",
|
||||||
|
subagent_type: "general-purpose"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **工具池**:全部工具(`*`)
|
||||||
|
- **模型**:继承父代理
|
||||||
|
- **特点**:万能型,不确定用哪个 agent 时选它
|
||||||
|
|
||||||
|
### 2. Explore(探索代理)
|
||||||
|
|
||||||
|
**适用场景**:快速搜索文件、搜索代码模式、回答代码库结构问题。
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "搜索 API 端点",
|
||||||
|
prompt: "找到所有 REST API 端点的定义...",
|
||||||
|
subagent_type: "Explore"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **工具池**:只读工具(Glob、Grep、Read、Bash)
|
||||||
|
- **模型**:Haiku(快速低成本)
|
||||||
|
- **特点**:不能修改文件,速度快,适合调研
|
||||||
|
|
||||||
|
### 3. Plan(规划代理)
|
||||||
|
|
||||||
|
**适用场景**:设计实现方案、分析架构权衡、生成分步计划。
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "规划重构方案",
|
||||||
|
prompt: "设计将 monolith 拆分为微服务的方案...",
|
||||||
|
subagent_type: "Plan"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **工具池**:只读工具(同 Explore)
|
||||||
|
- **模型**:继承父代理(需要强推理能力)
|
||||||
|
- **特点**:输出结构化计划,包含关键文件和依赖分析
|
||||||
|
|
||||||
|
### 4. verification(验证代理)
|
||||||
|
|
||||||
|
**适用场景**:独立验证实现是否正确,运行测试,边界检查。
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "验证登录功能",
|
||||||
|
prompt: "验证新实现的登录功能是否正确...",
|
||||||
|
subagent_type: "verification"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **工具池**:只读工具
|
||||||
|
- **模型**:继承父代理
|
||||||
|
- **特点**:始终在后台运行,输出 PASS/FAIL/PARTIAL 判定,红色标识
|
||||||
|
|
||||||
|
### 5. claude-code-guide(指南代理)
|
||||||
|
|
||||||
|
**适用场景**:回答关于 Claude Code、Agent SDK、Claude API 的问题。
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "查询 Claude API 用法",
|
||||||
|
prompt: "如何使用 tool_use 功能...",
|
||||||
|
subagent_type: "claude-code-guide"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **工具池**:Bash、Read、WebFetch、WebSearch
|
||||||
|
- **模型**:Haiku
|
||||||
|
- **特点**:专注文档查询,dontAsk 权限模式
|
||||||
|
|
||||||
|
### 6. statusline-setup(状态栏配置代理)
|
||||||
|
|
||||||
|
**适用场景**:配置 Claude Code 状态栏显示。
|
||||||
|
|
||||||
|
- **工具池**:仅 Read + Edit
|
||||||
|
- **模型**:Sonnet
|
||||||
|
- **特点**:高度专业化,范围极小
|
||||||
|
|
||||||
|
### Agent 类型对比表
|
||||||
|
|
||||||
|
| Agent | 读写 | 工具池 | 模型 | 用途 |
|
||||||
|
|-------|------|--------|------|------|
|
||||||
|
| general-purpose | 读写 | 全部 | 继承 | 通用任务 |
|
||||||
|
| Explore | 只读 | 搜索+读取 | Haiku | 快速探索 |
|
||||||
|
| Plan | 只读 | 搜索+读取 | 继承 | 架构规划 |
|
||||||
|
| verification | 只读 | 搜索+读取 | 继承 | 独立验证 |
|
||||||
|
| claude-code-guide | 只读 | 搜索+网络 | Haiku | 文档指南 |
|
||||||
|
| statusline-setup | 读写 | Read+Edit | Sonnet | 状态栏配置 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、如何生成 Agent
|
||||||
|
|
||||||
|
### 基本参数
|
||||||
|
|
||||||
|
Agent 工具接受以下参数:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必需 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `description` | string | 是 | 3-5 词任务简述 |
|
||||||
|
| `prompt` | string | 是 | 完整的任务描述 |
|
||||||
|
| `subagent_type` | string | 否 | Agent 类型(见上表) |
|
||||||
|
| `model` | string | 否 | 模型覆盖:sonnet/opus/haiku |
|
||||||
|
| `run_in_background` | boolean | 否 | 是否后台运行 |
|
||||||
|
| `name` | string | 否 | 命名后可通过 SendMessage 寻址 |
|
||||||
|
| `team_name` | string | 否 | 加入指定团队 |
|
||||||
|
| `mode` | string | 否 | 权限模式 |
|
||||||
|
| `isolation` | string | 否 | 隔离模式:worktree |
|
||||||
|
|
||||||
|
### 前台同步执行(默认)
|
||||||
|
|
||||||
|
最简单的用法,Agent 执行完毕后返回结果:
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "分析错误日志",
|
||||||
|
prompt: "读取 logs/ 下最近的错误日志,总结常见错误模式"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
主代理会等待子代理完成,然后收到结果继续工作。
|
||||||
|
|
||||||
|
### 后台异步执行
|
||||||
|
|
||||||
|
适合耗时任务,主代理可以继续做其他事情:
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "全面代码审查",
|
||||||
|
prompt: "审查 src/ 下所有 TypeScript 文件的代码质量...",
|
||||||
|
run_in_background: true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- Agent 立即返回 `async_launched` 状态和 taskId
|
||||||
|
- 主代理继续工作,不需要等待
|
||||||
|
- Agent 完成后自动收到 `<task-notification>` 通知
|
||||||
|
- 通知包含任务状态、输出文件路径和结果摘要
|
||||||
|
|
||||||
|
### 并行生成多个 Agent
|
||||||
|
|
||||||
|
在一条消息中生成多个独立的 Agent,实现真正的并行:
|
||||||
|
|
||||||
|
```
|
||||||
|
// 同时启动 3 个探索 agent
|
||||||
|
Agent({ description: "探索前端", prompt: "...", subagent_type: "Explore", run_in_background: true })
|
||||||
|
Agent({ description: "探索后端", prompt: "...", subagent_type: "Explore", run_in_background: true })
|
||||||
|
Agent({ description: "探索数据库", prompt: "...", subagent_type: "Explore", run_in_background: true })
|
||||||
|
```
|
||||||
|
|
||||||
|
### Worktree 隔离
|
||||||
|
|
||||||
|
让 Agent 在独立的 git worktree 中工作,不影响主工作区:
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "实验性重构",
|
||||||
|
prompt: "尝试将模块 X 重构为...",
|
||||||
|
isolation: "worktree"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- 自动创建 git worktree(独立分支)
|
||||||
|
- Agent 在隔离环境中自由修改文件
|
||||||
|
- 完成后如有改动,返回 worktree 路径和分支名
|
||||||
|
- 无改动则自动清理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、后台任务管理
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 任务状态
|
||||||
|
|
||||||
|
后台 Agent 有四种状态:
|
||||||
|
|
||||||
|
| 状态 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `running` | 正在执行中 |
|
||||||
|
| `completed` | 执行成功 |
|
||||||
|
| `failed` | 执行失败 |
|
||||||
|
| `killed` | 被手动终止 |
|
||||||
|
|
||||||
|
### 进度追踪
|
||||||
|
|
||||||
|
后台 Agent 的进度实时更新:
|
||||||
|
|
||||||
|
- **Token 消耗**:输入/输出 token 计数
|
||||||
|
- **工具使用**:已使用的工具次数
|
||||||
|
- **最近活动**:最近 5 个工具调用描述(循环缓冲区)
|
||||||
|
- **最后活动时间**:用于检测卡住的任务
|
||||||
|
|
||||||
|
### 完成通知
|
||||||
|
|
||||||
|
当后台 Agent 完成时,主代理收到 XML 格式的通知:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<task-notification>
|
||||||
|
<task-id>abc123</task-id>
|
||||||
|
<status>completed</status>
|
||||||
|
<summary>Agent "探索前端" completed</summary>
|
||||||
|
<output-file>~/.claude/temp/.../tasks/abc123.output</output-file>
|
||||||
|
</task-notification>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自动后台化
|
||||||
|
|
||||||
|
当 `tengu_auto_background_agents` 特性开启时,前台 Agent 运行超过 **120 秒**会自动转为后台执行,释放主代理继续工作。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、Agent Teams — 多代理协作
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Agent Teams 是更高级的多代理协作模式,多个代理以团队形式工作,通过消息通信协调任务。
|
||||||
|
|
||||||
|
### 创建团队
|
||||||
|
|
||||||
|
```
|
||||||
|
TeamCreate({
|
||||||
|
team_name: "feature-team",
|
||||||
|
description: "开发用户认证功能"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
团队创建后:
|
||||||
|
- 生成团队配置文件:`~/.claude/teams/{team_name}/config.json`
|
||||||
|
- 创建共享任务目录:`~/.claude/tasks/{team_name}/`
|
||||||
|
- 当前代理自动成为 **Team Lead**(团队负责人)
|
||||||
|
|
||||||
|
### 添加团队成员
|
||||||
|
|
||||||
|
通过 Agent 工具指定 `name` 和 `team_name` 生成队友:
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "前端开发",
|
||||||
|
prompt: "负责实现登录页面的 React 组件...",
|
||||||
|
name: "frontend-dev",
|
||||||
|
team_name: "feature-team"
|
||||||
|
})
|
||||||
|
|
||||||
|
Agent({
|
||||||
|
description: "后端开发",
|
||||||
|
prompt: "负责实现认证 API 端点...",
|
||||||
|
name: "backend-dev",
|
||||||
|
team_name: "feature-team"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 队友通信
|
||||||
|
|
||||||
|
通过 SendMessage 工具发送消息:
|
||||||
|
|
||||||
|
```
|
||||||
|
// 发送给特定队友
|
||||||
|
SendMessage({
|
||||||
|
to: "frontend-dev",
|
||||||
|
message: "API 接口已就绪,格式是...",
|
||||||
|
summary: "通知 API 接口格式"
|
||||||
|
})
|
||||||
|
|
||||||
|
// 广播给所有队友
|
||||||
|
SendMessage({
|
||||||
|
to: "*",
|
||||||
|
message: "大家暂停,需求变更了...",
|
||||||
|
summary: "广播需求变更"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 关停协调
|
||||||
|
|
||||||
|
当任务完成后,Team Lead 请求队友关停:
|
||||||
|
|
||||||
|
```
|
||||||
|
// 1. 发送关停请求
|
||||||
|
SendMessage({
|
||||||
|
to: "frontend-dev",
|
||||||
|
message: { type: "shutdown_request", reason: "任务已完成" }
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. 队友回复批准
|
||||||
|
SendMessage({
|
||||||
|
to: "team-lead",
|
||||||
|
message: { type: "shutdown_response", request_id: "...", approve: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. 所有队友关停后,清理团队
|
||||||
|
TeamDelete()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 执行后端
|
||||||
|
|
||||||
|
Agent Teams 支持两种执行后端:
|
||||||
|
|
||||||
|
| 后端 | 说明 | 适用场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| **in-process** | 同进程运行,AsyncLocalStorage 隔离 | 默认模式,轻量高效 |
|
||||||
|
| **tmux** | 独立 tmux pane 运行 | 需要独立终端视图 |
|
||||||
|
| **iTerm2** | 独立 iTerm2 窗口运行 | macOS iTerm2 用户 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、自定义 Agent
|
||||||
|
|
||||||
|
除了内置 Agent,你还可以创建自己的专业代理。
|
||||||
|
|
||||||
|
### 定义格式
|
||||||
|
|
||||||
|
在 `.claude/agents/` 目录下创建 `.md` 文件:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: code-reviewer
|
||||||
|
description: 专业代码审查代理
|
||||||
|
tools:
|
||||||
|
- Read
|
||||||
|
- Grep
|
||||||
|
- Glob
|
||||||
|
- Bash
|
||||||
|
model: sonnet
|
||||||
|
permissionMode: dontAsk
|
||||||
|
maxTurns: 10
|
||||||
|
---
|
||||||
|
|
||||||
|
你是一个专业的代码审查员。请检查以下方面:
|
||||||
|
|
||||||
|
1. 代码质量和可读性
|
||||||
|
2. 潜在的安全漏洞
|
||||||
|
3. 性能问题
|
||||||
|
4. 最佳实践遵循
|
||||||
|
```
|
||||||
|
|
||||||
|
### 可配置字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `name` | string | Agent 类型名称 |
|
||||||
|
| `description` | string | 何时使用的说明 |
|
||||||
|
| `tools` | string[] | 允许的工具列表(`['*']` 表示全部) |
|
||||||
|
| `disallowedTools` | string[] | 禁止的工具列表 |
|
||||||
|
| `model` | string | 使用的模型(sonnet/opus/haiku/inherit) |
|
||||||
|
| `permissionMode` | string | 权限模式 |
|
||||||
|
| `maxTurns` | number | 最大对话轮数 |
|
||||||
|
| `mcpServers` | object[] | 需要的 MCP 服务器 |
|
||||||
|
| `hooks` | object | Agent 特定的钩子 |
|
||||||
|
| `skills` | string[] | 可使用的技能 |
|
||||||
|
| `memory` | string | 记忆作用域(user/project/local) |
|
||||||
|
| `isolation` | string | 隔离模式(worktree/remote) |
|
||||||
|
| `background` | boolean | 是否默认后台运行 |
|
||||||
|
|
||||||
|
### 加载优先级
|
||||||
|
|
||||||
|
自定义 Agent 的加载遵循优先级:
|
||||||
|
|
||||||
|
1. **内置 Agent**(built-in)— 系统预定义
|
||||||
|
2. **插件 Agent**(plugin)— 通过插件注册
|
||||||
|
3. **用户 Agent**(user)— `~/.claude/agents/`
|
||||||
|
4. **项目 Agent**(project)— `.claude/agents/`(项目级)
|
||||||
|
5. **标记 Agent**(flag)— 通过 API 注册
|
||||||
|
6. **策略 Agent**(policy)— 组织策略
|
||||||
|
|
||||||
|
同名 Agent 按优先级覆盖。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、权限模式
|
||||||
|
|
||||||
|
每个 Agent 可以设置不同的权限模式:
|
||||||
|
|
||||||
|
| 模式 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `default` | 正常权限请求,需要用户确认 |
|
||||||
|
| `plan` | 所有操作需要显式审批 |
|
||||||
|
| `acceptEdits` | 自动接受文件编辑,其他操作需确认 |
|
||||||
|
| `bypassPermissions` | 跳过所有权限检查 |
|
||||||
|
| `dontAsk` | 拒绝所有未预批准的操作 |
|
||||||
|
| `auto` | AI 驱动的权限分类(仅 Ant 内部) |
|
||||||
|
| `bubble` | 权限提示冒泡到父代理终端 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、快速参考
|
||||||
|
|
||||||
|
| 操作 | 方法 |
|
||||||
|
|------|------|
|
||||||
|
| 生成子代理 | `Agent({ prompt: "...", subagent_type: "Explore" })` |
|
||||||
|
| 后台运行 | `Agent({ ..., run_in_background: true })` |
|
||||||
|
| 并行生成 | 单条消息中发送多个 Agent 调用 |
|
||||||
|
| Worktree 隔离 | `Agent({ ..., isolation: "worktree" })` |
|
||||||
|
| 创建团队 | `TeamCreate({ team_name: "..." })` |
|
||||||
|
| 发送消息 | `SendMessage({ to: "name", message: "..." })` |
|
||||||
|
| 广播消息 | `SendMessage({ to: "*", message: "..." })` |
|
||||||
|
| 请求关停 | `SendMessage({ to: "name", message: { type: "shutdown_request" } })` |
|
||||||
|
| 删除团队 | `TeamDelete()` |
|
||||||
|
| 自定义 Agent | 在 `.claude/agents/*.md` 创建定义文件 |
|
||||||
|
| 指定模型 | `Agent({ ..., model: "haiku" })` |
|
||||||
|
| 命名 Agent | `Agent({ ..., name: "researcher" })` |
|
||||||
@@ -0,0 +1,852 @@
|
|||||||
|
# Claude Code 多 Agent 系统 — 实现原理
|
||||||
|
|
||||||
|
> 深入剖析多 Agent 编排的架构设计、生成流程、上下文传递和协作机制。
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#一架构总览">架构总览</a> · <a href="#二agent-生成流程--四条路径">生成流程</a> · <a href="#三工具池系统--三层过滤">工具池系统</a> · <a href="#四上下文传递机制">上下文传递</a> · <a href="#五agent-teams-内部机制">Teams 内部机制</a> · <a href="#六后台任务引擎">后台任务引擎</a> · <a href="#七dreamtask--自动记忆整合">DreamTask</a> · <a href="#八worktree-隔离实现">Worktree 隔离</a> · <a href="#九权限同步机制">权限同步</a> · <a href="#十agent-生命周期完整数据流">生命周期数据流</a> · <a href="#十一关键源文件索引">源文件索引</a> · <a href="#十二feature-flags">Feature Flags</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、架构总览
|
||||||
|
|
||||||
|
Claude Code 的多 Agent 系统由以下核心模块组成:
|
||||||
|
|
||||||
|
### 5 大核心模块
|
||||||
|
|
||||||
|
| 模块 | 职责 | 关键文件 |
|
||||||
|
|------|------|----------|
|
||||||
|
| **Agent Tool** | 主入口,路由分发,参数解析 | `src/tools/AgentTool/AgentTool.tsx` |
|
||||||
|
| **执行引擎** | Agent 生命周期管理,查询循环 | `src/tools/AgentTool/runAgent.ts` |
|
||||||
|
| **上下文管理** | 系统提示词构建,缓存安全参数 | `src/utils/forkedAgent.ts` |
|
||||||
|
| **任务系统** | 状态追踪,进度更新,通知队列 | `src/tasks/LocalAgentTask/` |
|
||||||
|
| **Swarm 基础设施** | 团队管理,邮箱通信,权限同步 | `src/utils/swarm/` |
|
||||||
|
|
||||||
|
### 5 大 Agent 类别
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Agent Tool │
|
||||||
|
│ (入口 & 路由分发) │
|
||||||
|
├───────────┬───────────┬───────────┬─────────────┤
|
||||||
|
│ Subagent │ Fork │ Teammate │ Remote │
|
||||||
|
│ (子代理) │ (分叉) │ (队友) │ (远程) │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ 独立上下文 │ 继承上下文 │ 团队协作 │ CCR 环境 │
|
||||||
|
│ 按类型过滤 │ 缓存共享 │ 邮箱通信 │ 远程执行 │
|
||||||
|
│ 工具池 │ 字节一致 │ 权限同步 │ 轮询结果 │
|
||||||
|
└───────────┴───────────┴───────────┴─────────────┘
|
||||||
|
│
|
||||||
|
┌─────┴─────┐
|
||||||
|
│ DreamTask │
|
||||||
|
│ (记忆整合) │
|
||||||
|
│ 定时后台 │
|
||||||
|
└───────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、Agent 生成流程 — 四条路径
|
||||||
|
|
||||||
|
### 入口:`AgentTool.call()`
|
||||||
|
|
||||||
|
`src/tools/AgentTool/AgentTool.tsx` 中的 `call()` 函数是所有 Agent 生成的入口。根据输入参数,路由到四条不同的生成路径:
|
||||||
|
|
||||||
|
```
|
||||||
|
AgentTool.call(input)
|
||||||
|
│
|
||||||
|
├─ team_name + name? ──────→ 路径1: spawnTeammate()
|
||||||
|
│
|
||||||
|
├─ run_in_background? ────→ 路径2: registerAsyncAgent()
|
||||||
|
│ └─ agent.background?
|
||||||
|
│
|
||||||
|
├─ 省略 subagent_type? ───→ 路径3: Fork (buildForkedMessages())
|
||||||
|
│ └─ fork 实验开启?
|
||||||
|
│
|
||||||
|
└─ 默认 ───────────────────→ 路径4: runAgent() 同步执行
|
||||||
|
```
|
||||||
|
|
||||||
|
### 路径 1:Teammate 生成
|
||||||
|
|
||||||
|
**触发条件**:`team_name` 和 `name` 同时存在
|
||||||
|
|
||||||
|
**入口函数**:`spawnTeammate()` — `src/tools/shared/spawnMultiAgent.ts`
|
||||||
|
|
||||||
|
**流程**:
|
||||||
|
|
||||||
|
1. 检测执行后端(tmux / iTerm2 / in-process)
|
||||||
|
2. 为队友生成唯一 `agentId`:`formatAgentId(name, teamName)`
|
||||||
|
3. 分配颜色(从预定义调色板)
|
||||||
|
4. 创建执行环境:
|
||||||
|
- **in-process**:通过 `spawnInProcessTeammate()` 在同进程中启动
|
||||||
|
- **tmux**:通过 `TmuxBackend` 创建新 pane
|
||||||
|
- **iTerm2**:通过 `ITerm2Backend` 创建新窗口
|
||||||
|
5. 写入 TeamFile 成员列表
|
||||||
|
6. 返回 `TeammateSpawnedOutput`(包含 pane ID、agent ID 等)
|
||||||
|
|
||||||
|
**In-Process 队友的隔离**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/swarm/spawnInProcess.ts
|
||||||
|
export async function spawnInProcessTeammate(config, context) {
|
||||||
|
// 1. 独立的 AbortController(不随 leader 中断)
|
||||||
|
const abortController = new AbortController()
|
||||||
|
|
||||||
|
// 2. AsyncLocalStorage 上下文隔离
|
||||||
|
runWithTeammateContext(teammateContext, async () => {
|
||||||
|
// 3. 独立的任务状态
|
||||||
|
// 4. 独立的消息循环
|
||||||
|
// 5. 共享的权限管道(通过 mailbox)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 路径 2:异步 Subagent
|
||||||
|
|
||||||
|
**触发条件**:`run_in_background=true` 或 Agent 定义中 `background: true`
|
||||||
|
|
||||||
|
**流程**:
|
||||||
|
|
||||||
|
```
|
||||||
|
registerAsyncAgent()
|
||||||
|
│
|
||||||
|
├─ 创建 LocalAgentTask(status: 'running')
|
||||||
|
├─ 注册到 agentNameRegistry(如有 name)
|
||||||
|
├─ 创建输出文件符号链接
|
||||||
|
├─ 创建 AbortController(链接到父代理)
|
||||||
|
├─ 发射 SDK event: task_started
|
||||||
|
│
|
||||||
|
└─ void runAsyncAgentLifecycle() ← 异步分离执行
|
||||||
|
│
|
||||||
|
├─ 创建 ProgressTracker
|
||||||
|
├─ 遍历 makeStream() 生成器
|
||||||
|
│ ├─ 追加消息到 agentMessages[]
|
||||||
|
│ ├─ 更新进度(tokens、tools、activities)
|
||||||
|
│ └─ 发射 SDK progress events
|
||||||
|
│
|
||||||
|
└─ 完成时:
|
||||||
|
├─ finalizeAgentTool()(提取结果)
|
||||||
|
├─ completeAgentTask()(标记完成)
|
||||||
|
├─ 清理 worktree(如有隔离)
|
||||||
|
└─ enqueuePendingNotification()(通知主代理)
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键实现**:`src/tools/AgentTool/agentToolUtils.ts` — `runAsyncAgentLifecycle()`
|
||||||
|
|
||||||
|
### 路径 3:Fork Subagent
|
||||||
|
|
||||||
|
**触发条件**:省略 `subagent_type` 且 Fork 实验开启
|
||||||
|
|
||||||
|
**核心优化**:通过字节级一致的 API 请求前缀,实现 **prompt cache 命中**。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**流程**:
|
||||||
|
|
||||||
|
```
|
||||||
|
buildForkedMessages(directive, assistantMessage)
|
||||||
|
│
|
||||||
|
├─ 保留父代理完整的 assistant message(所有 tool_use 块)
|
||||||
|
├─ 构建 user message:
|
||||||
|
│ ├─ 对每个 tool_use 创建占位 tool_result(字节一致)
|
||||||
|
│ └─ 追加 per-child directive(唯一差异部分)
|
||||||
|
│
|
||||||
|
└─ 结果:字节级一致的 API 前缀 → prompt cache 命中!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fork 子代理的行为约束**(通过 `FORK_BOILERPLATE_TAG` 注入):
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 你是分叉的工作进程,不是主代理
|
||||||
|
2. 不要对话、提问或建议后续步骤
|
||||||
|
3. 直接使用工具(Bash、Read、Write 等)
|
||||||
|
4. 如修改文件,在报告前提交更改
|
||||||
|
5. 工具调用之间不要输出文本
|
||||||
|
6. 严格限制在指令范围内
|
||||||
|
7. 报告控制在 500 词以内
|
||||||
|
8. 响应必须以 "Scope:" 开头
|
||||||
|
```
|
||||||
|
|
||||||
|
**防递归保护**:`isInForkChild()` 检测是否在 fork 子进程中,防止嵌套 fork。
|
||||||
|
|
||||||
|
### 路径 4:同步 Subagent
|
||||||
|
|
||||||
|
**触发条件**:默认路径(无 team_name、无 background、非 fork)
|
||||||
|
|
||||||
|
**流程**:
|
||||||
|
|
||||||
|
```
|
||||||
|
runAgent(promptMessages, toolUseContext, options)
|
||||||
|
│
|
||||||
|
├─ 解析 Agent 定义(getSystemPrompt、tools、permissions)
|
||||||
|
├─ 构建系统提示词(buildEffectiveSystemPrompt)
|
||||||
|
├─ 创建隔离的 ToolUseContext(createSubagentContext)
|
||||||
|
├─ 启动查询循环(query() async generator)
|
||||||
|
│ ├─ 发送 API 请求
|
||||||
|
│ ├─ 处理流式事件
|
||||||
|
│ ├─ 执行工具调用
|
||||||
|
│ └─ 累积消息和 usage
|
||||||
|
│
|
||||||
|
└─ 返回 AgentToolResult
|
||||||
|
├─ content: 最后 assistant 消息的文本
|
||||||
|
├─ totalToolUseCount
|
||||||
|
├─ totalDurationMs
|
||||||
|
└─ totalTokens
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、工具池系统 — 三层过滤
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 第一层:全局禁止
|
||||||
|
|
||||||
|
`ALL_AGENT_DISALLOWED_TOOLS` — 对所有 Agent 禁止的工具:
|
||||||
|
|
||||||
|
| 工具 | 禁止原因 |
|
||||||
|
|------|----------|
|
||||||
|
| TaskOutput | 仅主代理可读取任务输出 |
|
||||||
|
| ExitPlanMode | 仅主代理可退出计划模式 |
|
||||||
|
| EnterPlanMode | 仅主代理可进入计划模式 |
|
||||||
|
| AskUserQuestion | 子代理不应直接问用户 |
|
||||||
|
| TaskStop | 仅主代理可终止任务 |
|
||||||
|
| Agent | 防止递归生成(Ant 内部例外) |
|
||||||
|
|
||||||
|
### 第二层:Agent 类型过滤
|
||||||
|
|
||||||
|
`filterToolsForAgent()` — 基于 Agent 类型的工具过滤:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/tools/AgentTool/agentToolUtils.ts
|
||||||
|
function filterToolsForAgent(tools, agentDef) {
|
||||||
|
// 1. 移除 ALL_AGENT_DISALLOWED_TOOLS
|
||||||
|
// 2. 如果非内置 Agent,额外移除 CUSTOM_AGENT_DISALLOWED_TOOLS
|
||||||
|
// 3. 如果是异步 Agent,限制为 ASYNC_AGENT_ALLOWED_TOOLS
|
||||||
|
// 4. MCP 工具始终允许
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**ASYNC_AGENT_ALLOWED_TOOLS**(15 个):
|
||||||
|
|
||||||
|
```
|
||||||
|
Read, WebSearch, TodoWrite, Grep, WebFetch, Glob,
|
||||||
|
Bash/PowerShell, FileEdit, FileWrite, NotebookEdit,
|
||||||
|
Skill, SyntheticOutput, ToolSearch, EnterWorktree, ExitWorktree
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第三层:Agent 定义过滤
|
||||||
|
|
||||||
|
`resolveAgentTools()` — 基于 Agent 定义的工具解析:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function resolveAgentTools(agentDef, availableTools) {
|
||||||
|
if (tools === ['*'] || undefined) → 通配符,全部允许
|
||||||
|
if (tools === ['Read', 'Grep']) → 仅允许列表中的工具
|
||||||
|
if (disallowedTools === ['Agent']) → 从可用工具中减去
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**过滤流程图**:
|
||||||
|
|
||||||
|
```
|
||||||
|
所有可用工具
|
||||||
|
│
|
||||||
|
├─ 减去 ALL_AGENT_DISALLOWED_TOOLS ──→ 通用禁止
|
||||||
|
│
|
||||||
|
├─ 非内置?减去 CUSTOM_AGENT_DISALLOWED_TOOLS ──→ 自定义限制
|
||||||
|
│
|
||||||
|
├─ 异步?限制为 ASYNC_AGENT_ALLOWED_TOOLS ──→ 异步白名单
|
||||||
|
│
|
||||||
|
├─ 有 tools 列表?取交集 ──→ Agent 白名单
|
||||||
|
│
|
||||||
|
├─ 有 disallowedTools?取差集 ──→ Agent 黑名单
|
||||||
|
│
|
||||||
|
└─ 最终工具池
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、上下文传递机制
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### CacheSafeParams — 缓存安全参数
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/forkedAgent.ts
|
||||||
|
export type CacheSafeParams = {
|
||||||
|
systemPrompt: SystemPrompt // 系统提示词
|
||||||
|
userContext: { [k: string]: string } // 目录结构、CLAUDE.md 等
|
||||||
|
systemContext: { [k: string]: string } // git status、环境信息
|
||||||
|
toolUseContext: ToolUseContext // 工具配置、模型、选项
|
||||||
|
forkContextMessages: Message[] // Fork 上下文消息(用于缓存共享)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**缓存共享原理**:
|
||||||
|
|
||||||
|
Fork Agent 通过保持 API 请求前缀字节级一致来复用 prompt cache:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 共享前缀(字节一致) │
|
||||||
|
│ ┌──────────────────────────────────┐ │
|
||||||
|
│ │ System Prompt │ │
|
||||||
|
│ │ User Context │ │
|
||||||
|
│ │ System Context │ │
|
||||||
|
│ │ Tool Use Context │ │
|
||||||
|
│ │ 对话历史 Messages │ │
|
||||||
|
│ │ Assistant Message (all tool_use) │ │
|
||||||
|
│ │ User Message (placeholder results│) │
|
||||||
|
│ └──────────────────────────────────┘ │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ 唯一差异:per-child directive text │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 系统提示词构建
|
||||||
|
|
||||||
|
`buildEffectiveSystemPrompt()` — `src/utils/systemPrompt.ts`
|
||||||
|
|
||||||
|
**优先级链**(从高到低):
|
||||||
|
|
||||||
|
```
|
||||||
|
Override System Prompt ← 最高优先级,完全替换
|
||||||
|
↓
|
||||||
|
Coordinator System Prompt ← 协调器模式专用
|
||||||
|
↓
|
||||||
|
Agent System Prompt ← agentDefinition.getSystemPrompt()
|
||||||
|
↓ - proactive 模式:追加到默认
|
||||||
|
↓ - 其他:替换默认
|
||||||
|
Custom System Prompt ← --system-prompt 参数
|
||||||
|
↓
|
||||||
|
Default System Prompt ← Claude Code 标准提示词
|
||||||
|
↓
|
||||||
|
Append System Prompt ← 追加到末尾
|
||||||
|
```
|
||||||
|
|
||||||
|
**Agent 特有的系统提示词增强**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/tools/AgentTool/runAgent.ts
|
||||||
|
function getAgentSystemPrompt(agentDef, toolUseContext) {
|
||||||
|
let prompt = agentDef.getSystemPrompt({ toolUseContext })
|
||||||
|
prompt = enhanceSystemPromptWithEnvDetails(prompt)
|
||||||
|
// 添加:工作目录、启用工具列表、模型信息、环境变量
|
||||||
|
return prompt
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SubagentContext — 子代理上下文隔离
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/forkedAgent.ts
|
||||||
|
export type SubagentContextOverrides = {
|
||||||
|
options?: ToolUseContext['options'] // 自定义工具、模型
|
||||||
|
agentId?: AgentId // 子代理 ID
|
||||||
|
agentType?: string // Agent 类型
|
||||||
|
messages?: Message[] // 自定义消息历史
|
||||||
|
readFileState?: ToolUseContext['readFileState'] // 文件读取缓存
|
||||||
|
abortController?: AbortController // 中止控制器
|
||||||
|
|
||||||
|
// 显式 opt-in 共享(默认隔离)
|
||||||
|
shareSetAppState?: boolean // 共享 AppState 写入
|
||||||
|
shareSetResponseLength?: boolean // 共享响应长度度量
|
||||||
|
shareAbortController?: boolean // 共享中止控制器
|
||||||
|
|
||||||
|
// 实验性注入
|
||||||
|
criticalSystemReminder_EXPERIMENTAL?: string // 每轮重新注入的提醒
|
||||||
|
contentReplacementState?: ContentReplacementState
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**隔离 vs 共享**:
|
||||||
|
|
||||||
|
| 资源 | 默认 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| readFileState | 克隆 | 文件读取缓存独立 |
|
||||||
|
| messages | 新建 | 消息历史独立 |
|
||||||
|
| abortController | 新建(链接父) | 父取消时子也取消 |
|
||||||
|
| setAppState | No-op | 默认不影响父状态 |
|
||||||
|
| contentReplacementState | 克隆 | 内容替换状态独立 |
|
||||||
|
|
||||||
|
### 模型解析
|
||||||
|
|
||||||
|
`getAgentModel()` — `src/utils/model/agent.ts`
|
||||||
|
|
||||||
|
**优先级链**:
|
||||||
|
|
||||||
|
```
|
||||||
|
CLAUDE_CODE_SUBAGENT_MODEL 环境变量 ← 最高
|
||||||
|
↓
|
||||||
|
Agent({ model: 'opus' }) 参数 ← 工具指定
|
||||||
|
↓
|
||||||
|
agentDefinition.model ← Agent 定义
|
||||||
|
↓
|
||||||
|
'inherit' ← 继承父代理模型
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、Agent Teams 内部机制
|
||||||
|
|
||||||
|
### TeamFile 结构
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 存储路径:~/.claude/teams/{team_name}/config.json
|
||||||
|
{
|
||||||
|
name: string // 团队名称
|
||||||
|
description?: string // 团队描述
|
||||||
|
createdAt: number // 创建时间戳
|
||||||
|
leadAgentId: string // Team Lead 的 Agent ID
|
||||||
|
leadSessionId?: string // Lead 的会话 UUID
|
||||||
|
hiddenPaneIds?: string[] // UI 中隐藏的 pane
|
||||||
|
teamAllowedPaths?: TeamAllowedPath[] // 团队级共享权限
|
||||||
|
members: Array<{
|
||||||
|
agentId: string // 成员 Agent ID
|
||||||
|
name: string // 显示名称
|
||||||
|
agentType?: string // 角色类型
|
||||||
|
model?: string // 使用的模型
|
||||||
|
prompt?: string // 初始任务
|
||||||
|
color?: string // UI 颜色
|
||||||
|
planModeRequired?: boolean // 是否需要 plan 审批
|
||||||
|
joinedAt: number // 加入时间
|
||||||
|
tmuxPaneId: string // 终端 pane ID
|
||||||
|
cwd: string // 工作目录
|
||||||
|
worktreePath?: string // Worktree 路径
|
||||||
|
sessionId?: string // 会话 ID
|
||||||
|
subscriptions: string[] // 消息订阅
|
||||||
|
backendType?: 'tmux'|'iterm2'|'in-process'
|
||||||
|
isActive?: boolean // false=空闲, true/undefined=活跃
|
||||||
|
mode?: PermissionMode // 当前权限模式
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 邮箱系统
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**存储路径**:`~/.claude/teams/{team_name}/inboxes/{agent_name}.json`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/teammateMailbox.ts
|
||||||
|
type TeammateMessage = {
|
||||||
|
from: string // 发送者名称
|
||||||
|
text: string // 消息内容(纯文本或 JSON)
|
||||||
|
timestamp: string // ISO 时间戳
|
||||||
|
read: boolean // 是否已读
|
||||||
|
color?: string // 发送者颜色
|
||||||
|
summary?: string // 5-10 词摘要
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**并发安全**:使用 `proper-lockfile` 文件锁,10 次重试,5-100ms 指数退避。
|
||||||
|
|
||||||
|
**消息类型**:
|
||||||
|
|
||||||
|
| 消息 | 格式 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| 纯文本 | `string` | 普通对话消息 |
|
||||||
|
| shutdown_request | `{ type, reason }` | 请求队友关停 |
|
||||||
|
| shutdown_response | `{ type, request_id, approve }` | 批准/拒绝关停 |
|
||||||
|
| plan_approval_response | `{ type, request_id, approve }` | 审批 plan |
|
||||||
|
| permission_request | `{ type, toolName, path }` | 权限请求 |
|
||||||
|
| idle_notification | 特殊格式 | 空闲通知 |
|
||||||
|
|
||||||
|
### 收件箱轮询
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/hooks/useInboxPoller.ts
|
||||||
|
// 轮询间隔:1000ms
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
const messages = await readUnreadMessages(agentName, teamName)
|
||||||
|
|
||||||
|
for (const msg of messages) {
|
||||||
|
if (isShutdownRequest(msg.text)) {
|
||||||
|
// 处理关停请求
|
||||||
|
} else if (isPlanApprovalResponse(msg.text)) {
|
||||||
|
// 处理 plan 审批
|
||||||
|
} else if (isPermissionRequest(msg.text)) {
|
||||||
|
// 路由到权限系统
|
||||||
|
} else {
|
||||||
|
// 纯文本消息 → 提交为新对话轮
|
||||||
|
onSubmitMessage(formatted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, INBOX_POLL_INTERVAL_MS)
|
||||||
|
}, [])
|
||||||
|
```
|
||||||
|
|
||||||
|
**消息处理状态**:
|
||||||
|
|
||||||
|
| 状态 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `pending` | 新收到,等待处理 |
|
||||||
|
| `processing` | 正在处理(权限请求等) |
|
||||||
|
| `processed` | 已处理完毕 |
|
||||||
|
|
||||||
|
### 消息路由
|
||||||
|
|
||||||
|
```
|
||||||
|
SendMessage({ to, message })
|
||||||
|
│
|
||||||
|
├─ to === "*" → 广播
|
||||||
|
│ └─ 遍历所有队友,逐个写入 mailbox
|
||||||
|
│
|
||||||
|
├─ agentNameRegistry.has(to) → in-process 子代理
|
||||||
|
│ └─ 通过 AppState pending messages 队列路由
|
||||||
|
│
|
||||||
|
├─ teamFile.members.find(to) → 进程级队友
|
||||||
|
│ └─ writeToMailbox(to, message, teamName)
|
||||||
|
│
|
||||||
|
├─ to.startsWith("bridge:") → 远程会话
|
||||||
|
│ └─ postInterClaudeMessage(sessionId, message)
|
||||||
|
│
|
||||||
|
└─ to.startsWith("uds:") → Unix Domain Socket
|
||||||
|
└─ sendToUdsSocket(socketPath, message)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、后台任务引擎
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### LocalAgentTask 状态机
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/tasks/LocalAgentTask/LocalAgentTask.tsx
|
||||||
|
type LocalAgentTaskState = {
|
||||||
|
type: 'local_agent'
|
||||||
|
agentId: AgentId // 唯一标识
|
||||||
|
status: 'running' | 'completed' | 'failed' | 'killed'
|
||||||
|
isBackgrounded: boolean // 前台 vs 后台
|
||||||
|
|
||||||
|
progress: {
|
||||||
|
latestInputTokens: number // 最新输入 tokens
|
||||||
|
cumulativeOutputTokens: number // 累计输出 tokens
|
||||||
|
toolUseCount: number // 工具使用次数
|
||||||
|
recentActivities: ToolActivity[] // 最近 5 个活动
|
||||||
|
lastActivity: number // 最后活动时间戳
|
||||||
|
}
|
||||||
|
|
||||||
|
result?: AgentToolResult // 最终结果
|
||||||
|
abortController: AbortController // 中止控制器
|
||||||
|
retain: boolean // UI 保持标志
|
||||||
|
evictAfter?: number // 延迟清除时间戳
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**状态转换**:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────┐
|
||||||
|
│ │
|
||||||
|
register │ ┌──── killed ←── abort()
|
||||||
|
│ │ │
|
||||||
|
▼ │ │
|
||||||
|
running ─────┼────┼──── completed ← finalizeAgentTool()
|
||||||
|
│ │
|
||||||
|
│ └──── failed ← error / timeout
|
||||||
|
│
|
||||||
|
└──── evict ← notified && endTime > grace
|
||||||
|
```
|
||||||
|
|
||||||
|
### 进度追踪
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ProgressTracker
|
||||||
|
function updateProgressFromMessage(tracker, message) {
|
||||||
|
// 1. 累积输入 tokens(取最新值)
|
||||||
|
tracker.latestInputTokens = message.usage?.input_tokens
|
||||||
|
|
||||||
|
// 2. 累加输出 tokens
|
||||||
|
tracker.cumulativeOutputTokens += message.usage?.output_tokens
|
||||||
|
|
||||||
|
// 3. 统计工具使用
|
||||||
|
tracker.toolUseCount += countToolUses(message)
|
||||||
|
|
||||||
|
// 4. 维护最近活动(循环缓冲区,max 5)
|
||||||
|
tracker.recentActivities = [...activities].slice(-5)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 通知系统
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/messageQueueManager.ts
|
||||||
|
function enqueuePendingNotification(taskId, result) {
|
||||||
|
// 1. 原子设置 notified 标志(防重复)
|
||||||
|
if (task.notified) return
|
||||||
|
task.notified = true
|
||||||
|
|
||||||
|
// 2. 格式化 XML 通知
|
||||||
|
const notification = `
|
||||||
|
<task-notification>
|
||||||
|
<task-id>${taskId}</task-id>
|
||||||
|
<status>${status}</status>
|
||||||
|
<summary>${summary}</summary>
|
||||||
|
<output-file>${outputPath}</output-file>
|
||||||
|
</task-notification>
|
||||||
|
`
|
||||||
|
|
||||||
|
// 3. 入队等待主代理消费
|
||||||
|
pendingNotifications.push(notification)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 输出管理
|
||||||
|
|
||||||
|
**存储路径**:`~/.claude/temp/{sessionId}/tasks/{taskId}.output`
|
||||||
|
|
||||||
|
| 参数 | 值 |
|
||||||
|
|------|----|
|
||||||
|
| 最大容量 | 5GB / 文件 |
|
||||||
|
| 循环缓冲区 | 1000 行 |
|
||||||
|
| 轮询间隔 | 1 秒 |
|
||||||
|
| 终态保持时间 | 3 秒(任务面板 30 秒) |
|
||||||
|
| 写入方式 | 队列异步写入,防内存堆积 |
|
||||||
|
| 安全措施 | O_NOFOLLOW 防符号链接攻击 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、DreamTask — 自动记忆整合
|
||||||
|
|
||||||
|
DreamTask 是特殊的后台 Agent,用于跨会话记忆整合。
|
||||||
|
|
||||||
|
### 触发条件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/services/autoDream/autoDream.ts
|
||||||
|
function executeAutoDream() {
|
||||||
|
// 四重门控:
|
||||||
|
if (hoursSinceLastConsolidation < minHours) return // 时间门:默认 24h
|
||||||
|
if (sessionsSinceLastConsolidation < minSessions) return // 会话门:默认 5 次
|
||||||
|
if (otherProcessConsolidating) return // 锁门:互斥
|
||||||
|
if (timeSinceLastScan < 10min) return // 扫描节流:10 分钟
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DreamTask 状态
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type DreamTaskState = {
|
||||||
|
type: 'dream'
|
||||||
|
phase: 'starting' | 'updating' // updating = 已开始编辑文件
|
||||||
|
sessionsReviewing: number // 正在审查的会话数
|
||||||
|
filesTouched: string[] // 编辑过的文件路径
|
||||||
|
turns: DreamTurn[] // 对话轮次记录
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、Worktree 隔离实现
|
||||||
|
|
||||||
|
### 创建流程
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/worktree.ts
|
||||||
|
async function createAgentWorktree(slug) {
|
||||||
|
// 1. 校验 slug(防目录逃逸攻击)
|
||||||
|
validateWorktreeSlug(slug)
|
||||||
|
|
||||||
|
// 2. 创建 git worktree
|
||||||
|
git worktree add {path} -b {branch}
|
||||||
|
|
||||||
|
// 3. 符号链接大目录(节省磁盘)
|
||||||
|
symlink(node_modules, worktree/node_modules)
|
||||||
|
|
||||||
|
// 4. 应用 sparse-checkout(如配置)
|
||||||
|
if (sparseCheckoutPaths) {
|
||||||
|
git sparse-checkout set {paths}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 返回 WorktreeSession
|
||||||
|
return { worktreePath, worktreeBranch, headCommit }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 清理机制
|
||||||
|
|
||||||
|
- Agent 完成后自动检测是否有改动(`hasWorktreeChanges()`)
|
||||||
|
- 有改动:返回 worktree 路径和分支名给用户
|
||||||
|
- 无改动:自动删除 worktree(`removeAgentWorktree()`)
|
||||||
|
- 异常退出:通过 `registerTeamForSessionCleanup()` 确保清理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、权限同步机制
|
||||||
|
|
||||||
|
### 团队级权限
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type TeamAllowedPath = {
|
||||||
|
path: string // 绝对目录路径
|
||||||
|
toolName: string // 适用的工具(如 "Edit", "Write")
|
||||||
|
addedBy: string // 添加者名称
|
||||||
|
addedAt: number // 添加时间
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
队友启动时,自动继承团队级权限规则。
|
||||||
|
|
||||||
|
### Bubble 模式
|
||||||
|
|
||||||
|
Fork Agent 使用 `bubble` 权限模式 — 权限提示冒泡到父代理终端:
|
||||||
|
|
||||||
|
```
|
||||||
|
Fork Agent 需要权限
|
||||||
|
│
|
||||||
|
└─ bubble 模式 → 权限请求发送到父代理
|
||||||
|
│
|
||||||
|
└─ 父代理的 ToolUseConfirm 对话框显示
|
||||||
|
│
|
||||||
|
├─ 用户批准 → 结果回传给 Fork Agent
|
||||||
|
└─ 用户拒绝 → Fork Agent 收到拒绝
|
||||||
|
```
|
||||||
|
|
||||||
|
### In-Process 队友权限
|
||||||
|
|
||||||
|
```
|
||||||
|
队友需要权限
|
||||||
|
│
|
||||||
|
├─ 有 UI bridge → 直接显示在 Leader 的确认对话框
|
||||||
|
│ └─ 带 worker badge 标识来源
|
||||||
|
│
|
||||||
|
└─ 无 UI bridge → 通过 mailbox 排队
|
||||||
|
└─ Leader 的 useSwarmPermissionPoller 处理
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、Agent 生命周期完整数据流
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 用户触发 Agent Tool
|
||||||
|
│
|
||||||
|
2. AgentTool.call() 路由分发
|
||||||
|
│
|
||||||
|
3. 解析 Agent 定义
|
||||||
|
├─ 查找 Agent 类型(内置 > 插件 > 用户 > 项目)
|
||||||
|
├─ 加载系统提示词
|
||||||
|
├─ 解析工具池(三层过滤)
|
||||||
|
└─ 确定权限模式和模型
|
||||||
|
│
|
||||||
|
4. 创建隔离上下文
|
||||||
|
├─ createSubagentContext()(克隆 readFileState)
|
||||||
|
├─ 生成 agentId
|
||||||
|
├─ 创建 AbortController
|
||||||
|
└─ 可选:创建 worktree
|
||||||
|
│
|
||||||
|
5. 注册任务状态
|
||||||
|
├─ registerAsyncAgent() 或 registerAgentForeground()
|
||||||
|
├─ 发射 SDK event: task_started
|
||||||
|
└─ Perfetto trace 注册
|
||||||
|
│
|
||||||
|
6. 执行查询循环
|
||||||
|
├─ query() async generator
|
||||||
|
│ ├─ 构建 API 请求(含 CacheSafeParams)
|
||||||
|
│ ├─ 流式处理响应
|
||||||
|
│ ├─ 执行工具调用
|
||||||
|
│ └─ 累积 usage 指标
|
||||||
|
├─ 更新进度(ProgressTracker)
|
||||||
|
└─ 记录 transcript
|
||||||
|
│
|
||||||
|
7. 完成处理
|
||||||
|
├─ finalizeAgentTool()(提取结果文本)
|
||||||
|
├─ completeAgentTask()(标记完成)
|
||||||
|
├─ 清理资源
|
||||||
|
│ ├─ 释放文件状态缓存
|
||||||
|
│ ├─ 关闭 MCP 连接
|
||||||
|
│ └─ 删除 worktree(如有)
|
||||||
|
├─ enqueuePendingNotification()(通知主代理)
|
||||||
|
└─ 发射 SDK event: task_completed
|
||||||
|
│
|
||||||
|
8. 主代理消费结果
|
||||||
|
├─ 同步:直接获取 AgentToolResult
|
||||||
|
└─ 异步:收到 <task-notification> 后处理
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十一、关键源文件索引
|
||||||
|
|
||||||
|
### Agent Tool 核心
|
||||||
|
|
||||||
|
| 文件 | 职责 |
|
||||||
|
|------|------|
|
||||||
|
| `src/tools/AgentTool/AgentTool.tsx` | 主工具实现,路由分发 |
|
||||||
|
| `src/tools/AgentTool/runAgent.ts` | 执行引擎,查询循环 |
|
||||||
|
| `src/tools/AgentTool/agentToolUtils.ts` | 工具池解析,结果终结 |
|
||||||
|
| `src/tools/AgentTool/forkSubagent.ts` | Fork 语义,消息继承 |
|
||||||
|
| `src/tools/AgentTool/loadAgentsDir.ts` | Agent 定义类型,解析加载 |
|
||||||
|
| `src/tools/AgentTool/builtInAgents.ts` | 内置 Agent 注册表 |
|
||||||
|
| `src/tools/AgentTool/prompt.ts` | Agent 工具 schema 和文档 |
|
||||||
|
| `src/tools/AgentTool/agentMemory.ts` | Agent 持久记忆 |
|
||||||
|
|
||||||
|
### Swarm 基础设施
|
||||||
|
|
||||||
|
| 文件 | 职责 |
|
||||||
|
|------|------|
|
||||||
|
| `src/tools/TeamCreateTool/TeamCreateTool.ts` | 团队创建 |
|
||||||
|
| `src/tools/TeamDeleteTool/TeamDeleteTool.ts` | 团队清理 |
|
||||||
|
| `src/tools/SendMessageTool/SendMessageTool.ts` | 代理间通信 |
|
||||||
|
| `src/tools/shared/spawnMultiAgent.ts` | 队友生成入口 |
|
||||||
|
| `src/utils/swarm/spawnInProcess.ts` | 进程内队友生成 |
|
||||||
|
| `src/utils/swarm/teamHelpers.ts` | 团队文件读写 |
|
||||||
|
| `src/utils/swarm/constants.ts` | 常量定义 |
|
||||||
|
| `src/utils/swarm/teammateInit.ts` | 队友初始化 |
|
||||||
|
| `src/utils/swarm/permissionSync.ts` | 权限同步 |
|
||||||
|
| `src/utils/teammate.ts` | 队友身份解析 |
|
||||||
|
| `src/utils/teammateMailbox.ts` | 邮箱消息队列 |
|
||||||
|
| `src/utils/teamDiscovery.ts` | 团队发现 |
|
||||||
|
| `src/hooks/useInboxPoller.ts` | 收件箱轮询 |
|
||||||
|
|
||||||
|
### 上下文管理
|
||||||
|
|
||||||
|
| 文件 | 职责 |
|
||||||
|
|------|------|
|
||||||
|
| `src/utils/forkedAgent.ts` | 缓存安全参数,子代理上下文 |
|
||||||
|
| `src/utils/systemPrompt.ts` | 系统提示词优先级构建 |
|
||||||
|
| `src/utils/model/agent.ts` | Agent 模型解析 |
|
||||||
|
| `src/utils/worktree.ts` | Git worktree 隔离 |
|
||||||
|
|
||||||
|
### 任务系统
|
||||||
|
|
||||||
|
| 文件 | 职责 |
|
||||||
|
|------|------|
|
||||||
|
| `src/tasks/LocalAgentTask/LocalAgentTask.tsx` | 本地 Agent 任务 |
|
||||||
|
| `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx` | 远程 Agent 任务 |
|
||||||
|
| `src/tasks/InProcessTeammateTask/` | 进程内队友任务 |
|
||||||
|
| `src/tasks/DreamTask/DreamTask.ts` | 记忆整合任务 |
|
||||||
|
| `src/utils/task/framework.ts` | 任务注册、状态更新 |
|
||||||
|
| `src/utils/task/diskOutput.ts` | 任务输出文件管理 |
|
||||||
|
| `src/utils/messageQueueManager.ts` | 通知队列 |
|
||||||
|
|
||||||
|
### 协调器
|
||||||
|
|
||||||
|
| 文件 | 职责 |
|
||||||
|
|------|------|
|
||||||
|
| `src/coordinator/coordinatorMode.ts` | 协调器模式配置 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十二、Feature Flags
|
||||||
|
|
||||||
|
| Flag | 控制内容 |
|
||||||
|
|------|----------|
|
||||||
|
| `FORK_SUBAGENT` | 启用 Fork 路径(省略 subagent_type) |
|
||||||
|
| `BUILTIN_EXPLORE_PLAN_AGENTS` | 启用 Explore/Plan Agent |
|
||||||
|
| `VERIFICATION_AGENT` | 启用 verification Agent |
|
||||||
|
| `COORDINATOR_MODE` | 启用协调器模式 |
|
||||||
|
| `KAIROS` | 启用 cwd 参数 |
|
||||||
|
| `tengu_auto_background_agents` | 120 秒后自动后台化 |
|
||||||
|
| `tengu_slim_subagent_claudemd` | 只读 Agent 省略 CLAUDE.md |
|
||||||
|
| `tengu_agent_list_attach` | Agent 列表通过 attachment 注入 |
|
||||||
@@ -0,0 +1,790 @@
|
|||||||
|
# Claude Code Agent 框架深度解析
|
||||||
|
|
||||||
|
> 从源码视角剖析全球最流行 AI Code Editor 背后的 Agent 架构设计哲学。
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#一核心-agent-循环">核心循环</a> · <a href="#二系统提示词工程">提示词工程</a> · <a href="#三工具系统设计">工具系统</a> · <a href="#四上下文管理与压缩">上下文管理</a> · <a href="#五技能与插件生态">技能与插件</a> · <a href="#六权限与安全体系">权限与安全</a> · <a href="#七故障恢复机制">故障恢复</a> · <a href="#八与-langchain-react-的本质区别">对比分析</a> · <a href="#九为什么-claude-code-能做到这么好">成功之道</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 导读:一个根本性的问题
|
||||||
|
|
||||||
|
如果你仔细观察 Claude Code 的行为,会发现一些非常有趣的现象:
|
||||||
|
|
||||||
|
- 它能在一次对话中修改几十个文件,且极少出错
|
||||||
|
- 它能自动恢复各种边界情况(token 溢出、API 超时、工具失败)
|
||||||
|
- 它能同时管理多个子代理协作完成复杂任务
|
||||||
|
- 长对话不会退化,反而能越来越精准
|
||||||
|
|
||||||
|
这些能力的背后,是一套精心设计的 Agent 框架。本文从源码层面,完整解构这套框架的设计哲学。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、核心 Agent 循环
|
||||||
|
|
||||||
|
### 1.1 不是 ReAct,而是 Async Generator 状态机
|
||||||
|
|
||||||
|
大多数 Agent 框架(包括 LangChain)采用经典的 **ReAct** 模式:
|
||||||
|
|
||||||
|
```
|
||||||
|
思考(Thought) → 行动(Action) → 观察(Observation) → 思考 → ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Claude Code **没有**采用这个模式。它的核心是一个 **异步生成器(Async Generator)驱动的状态机**,定义在 `src/query.ts`(约 1730 行):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/query.ts:219
|
||||||
|
export async function* query(params: QueryParams): AsyncGenerator<...>
|
||||||
|
```
|
||||||
|
|
||||||
|
这个函数是整个 Agent 的心脏。它不是简单的"想-做-看"循环,而是一个**流式状态机**,通过 `yield` 实时产出消息,通过状态赋值(而非递归调用)驱动循环。
|
||||||
|
|
||||||
|
### 1.2 状态结构
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/query.ts:204-217
|
||||||
|
type State = {
|
||||||
|
messages: Message[] // 完整对话历史
|
||||||
|
toolUseContext: ToolUseContext // 工具执行上下文
|
||||||
|
autoCompactTracking: AutoCompactTracking // 自动压缩追踪
|
||||||
|
maxOutputTokensRecoveryCount: number // 输出恢复计数
|
||||||
|
hasAttemptedReactiveCompact: boolean // 是否已尝试反应式压缩
|
||||||
|
maxOutputTokensOverride: number // 输出 token 覆盖值
|
||||||
|
pendingToolUseSummary: Promise<...> // 待处理的工具摘要
|
||||||
|
stopHookActive: boolean // 停止钩子状态
|
||||||
|
turnCount: number // 对话轮数
|
||||||
|
transition: Continue | undefined // 状态转换原因
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 核心循环的五个阶段
|
||||||
|
|
||||||
|
整个 `while (true)` 循环(`src/query.ts:307-1728`)分为五个阶段:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 阶段 1:消息准备与智能压缩(第 365-543 行)
|
||||||
|
|
||||||
|
在调用 API 之前,对话历史会经过四层压缩处理:
|
||||||
|
|
||||||
|
| 压缩策略 | 原理 | 触发时机 |
|
||||||
|
|----------|------|----------|
|
||||||
|
| **Snip 压缩** | 智能删除旧消息中的冗余 token | 每轮自动 |
|
||||||
|
| **Micro 压缩** | 修改已缓存消息的内容 | 每轮自动 |
|
||||||
|
| **上下文折叠** | 分阶段摘要历史消息 | 上下文接近限制时 |
|
||||||
|
| **Auto Compact** | 通过 Claude 生成完整摘要 | 上下文严重不足时 |
|
||||||
|
|
||||||
|
这是 Claude Code 能处理**极长对话**而不退化的关键——它不会简单地截断历史,而是**智能地压缩和保留关键信息**。
|
||||||
|
|
||||||
|
#### 阶段 2:流式 API 调用(第 652-954 行)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/query.ts:659-708
|
||||||
|
for await (const message of deps.callModel({
|
||||||
|
messages: prependUserContext(messagesForQuery, userContext),
|
||||||
|
systemPrompt: fullSystemPrompt,
|
||||||
|
thinkingConfig,
|
||||||
|
tools: toolUseContext.options.tools,
|
||||||
|
signal: abortController.signal,
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
关键设计:**工具在流式传输过程中就开始执行**,而不是等模型生成完整响应。这通过 `StreamingToolExecutor` 实现——当模型生成 `tool_use` 块时,工具立即开始运行。
|
||||||
|
|
||||||
|
#### 阶段 3:决策点(第 1062-1358 行)
|
||||||
|
|
||||||
|
```
|
||||||
|
模型响应完成
|
||||||
|
│
|
||||||
|
├─ 有工具调用? ──→ 继续循环(阶段 4)
|
||||||
|
│
|
||||||
|
└─ 无工具调用? ──→ 运行 Stop 钩子 → 检查 token 预算 → 返回结果
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 阶段 4:工具编排执行(第 1363-1409 行)
|
||||||
|
|
||||||
|
工具执行不是简单的逐个运行,而是有精心设计的**编排策略**(`src/services/tools/toolOrchestration.ts`):
|
||||||
|
|
||||||
|
```
|
||||||
|
工具调用列表
|
||||||
|
│
|
||||||
|
├─ 分区:只读 vs 写入
|
||||||
|
│
|
||||||
|
├─ 只读工具 ──→ 并行执行(最多 10 个并发)
|
||||||
|
│
|
||||||
|
└─ 写入工具 ──→ 串行执行(防止竞态条件)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 阶段 5:状态更新与循环(第 1704-1728 行)
|
||||||
|
|
||||||
|
这是整个设计最优雅的部分——**通过状态赋值而非递归调用驱动循环**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/query.ts:1715-1728
|
||||||
|
const next: State = {
|
||||||
|
messages: [...messagesForQuery, ...assistantMessages, ...toolResults],
|
||||||
|
toolUseContext: toolUseContextWithQueryTracking,
|
||||||
|
autoCompactTracking: tracking,
|
||||||
|
turnCount: nextTurnCount,
|
||||||
|
transition: { reason: 'next_turn' },
|
||||||
|
}
|
||||||
|
state = next
|
||||||
|
// 回到 while(true) 循环顶部
|
||||||
|
```
|
||||||
|
|
||||||
|
没有递归,没有回调地狱,只是简单的 `state = next` 然后 `continue`。这保证了:
|
||||||
|
- **内存稳定**:不会因为深度递归导致栈溢出
|
||||||
|
- **状态可追溯**:每一轮的状态转换原因都被记录
|
||||||
|
- **恢复可控**:任何阶段的错误都可以通过修改 state 来恢复
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、系统提示词工程
|
||||||
|
|
||||||
|
### 2.1 分层构建架构
|
||||||
|
|
||||||
|
系统提示词不是一个静态字符串,而是通过**分层管道**动态组装的(`src/constants/prompts.ts:444-577`):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 静态可缓存区域 │
|
||||||
|
│ ┌───────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ 角色定义 │ 系统规则 │ 任务指导 │ 工具说明 │ 风格 │ │
|
||||||
|
│ └───────────────────────────────────────────────────────┘ │
|
||||||
|
├─────────────────────── 缓存边界 ────────────────────────────┤
|
||||||
|
│ 动态可变区域 │
|
||||||
|
│ ┌───────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ 会话指引 │ 记忆系统 │ 环境信息 │ MCP 指令 │ Token 预算 │ │
|
||||||
|
│ └───────────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
这里的**缓存边界(`SYSTEM_PROMPT_DYNAMIC_BOUNDARY`)**是一个关键设计:
|
||||||
|
|
||||||
|
- **边界之上**:跨用户、跨组织通用的内容,使用 `scope: 'global'` 缓存
|
||||||
|
- **边界之下**:用户/会话特定的内容,使用 `scope: 'ephemeral'` 缓存
|
||||||
|
|
||||||
|
这意味着 Claude Code 的系统提示词**不需要每次都重新处理**——静态部分在全球范围内共享缓存,大幅降低延迟和成本。
|
||||||
|
|
||||||
|
### 2.2 两种 Section 类型
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/constants/systemPromptSections.ts
|
||||||
|
|
||||||
|
// 类型 1:缓存 Section(计算一次,整个会话复用)
|
||||||
|
systemPromptSection('memory', async () => {
|
||||||
|
return buildMemoryLines() // 读取 CLAUDE.md、记忆文件等
|
||||||
|
})
|
||||||
|
|
||||||
|
// 类型 2:缓存破坏 Section(每轮重新计算)
|
||||||
|
DANGEROUS_uncachedSystemPromptSection('mcp_instructions', async () => {
|
||||||
|
return getMcpInstructions() // MCP 服务器可能中途连接/断开
|
||||||
|
}, 'MCP servers can connect/disconnect mid-session')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 CLAUDE.md 的加载机制
|
||||||
|
|
||||||
|
CLAUDE.md 是用户自定义指令系统,按**优先级从低到高**加载(`src/utils/claudemd.ts`):
|
||||||
|
|
||||||
|
```
|
||||||
|
/etc/claude-code/CLAUDE.md ← 全局管理配置(最低优先级)
|
||||||
|
↓
|
||||||
|
~/.claude/CLAUDE.md ← 用户全局指令
|
||||||
|
↓
|
||||||
|
项目根目录/CLAUDE.md ← 项目级指令
|
||||||
|
项目根目录/.claude/CLAUDE.md
|
||||||
|
项目根目录/.claude/rules/*.md
|
||||||
|
↓
|
||||||
|
项目根目录/CLAUDE.local.md ← 本地私有指令(最高优先级)
|
||||||
|
```
|
||||||
|
|
||||||
|
支持 `@path` 语法递归引用其他文件,并自动防止循环引用。
|
||||||
|
|
||||||
|
### 2.4 系统提示词的优先级解析
|
||||||
|
|
||||||
|
最终的系统提示词通过 `buildEffectiveSystemPrompt()`(`src/utils/systemPrompt.ts:41-123`)按优先级决定:
|
||||||
|
|
||||||
|
1. **Override 提示词** — 完全替换(Loop 模式使用)
|
||||||
|
2. **Coordinator 提示词** — 协调者模式
|
||||||
|
3. **Agent 提示词** — 自定义 Agent 定义
|
||||||
|
4. **Custom 提示词** — `--system-prompt` 命令行参数
|
||||||
|
5. **默认提示词** — 标准系统提示词
|
||||||
|
6. **Append 提示词** — 始终追加到末尾
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、工具系统设计
|
||||||
|
|
||||||
|
### 3.1 工具接口:不只是函数调用
|
||||||
|
|
||||||
|
Claude Code 的工具不是简单的"名称 + 参数 + 执行"。每个工具是一个**完整的生命周期管理单元**(`src/Tool.ts:362-695`):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type Tool<Input, Output> = {
|
||||||
|
// 身份
|
||||||
|
name: string
|
||||||
|
aliases?: string[] // 向后兼容的旧名称
|
||||||
|
searchHint?: string // ToolSearch 关键词匹配
|
||||||
|
|
||||||
|
// 能力声明
|
||||||
|
isEnabled(): boolean
|
||||||
|
isConcurrencySafe(input): boolean // 是否可并行
|
||||||
|
isReadOnly(input): boolean // 是否只读
|
||||||
|
isDestructive(input): boolean // 是否破坏性
|
||||||
|
|
||||||
|
// 生命周期
|
||||||
|
validateInput(input, context) // 输入验证
|
||||||
|
checkPermissions(input, context) // 权限检查
|
||||||
|
call(input, context, ...) // 实际执行
|
||||||
|
|
||||||
|
// 输出与渲染
|
||||||
|
renderToolUseMessage(input) // 渲染调用信息
|
||||||
|
renderToolResultMessage(content) // 渲染结果信息
|
||||||
|
renderToolUseProgressMessage(...) // 渲染进度
|
||||||
|
mapToolResultToToolResultBlockParam() // 映射为 API 格式
|
||||||
|
|
||||||
|
// 智能特性
|
||||||
|
inputSchema: Zod schema // Zod 类型验证
|
||||||
|
maxResultSizeChars: number // 结果大小阈值
|
||||||
|
toAutoClassifierInput(input) // 安全分类器输入
|
||||||
|
getToolUseSummary?(input): string // 工具使用摘要
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
这种设计使得每个工具都是**自描述、自验证、自渲染**的——框架不需要了解工具的内部逻辑,只需调用标准接口。
|
||||||
|
|
||||||
|
### 3.2 工具注册:三阶段流水线
|
||||||
|
|
||||||
|
工具的发现和注册分三个阶段(`src/tools.ts`):
|
||||||
|
|
||||||
|
```
|
||||||
|
阶段 1:基础工具池(getAllBaseTools)
|
||||||
|
│ ~48 个内置工具
|
||||||
|
│ + Feature Flag 控制的条件工具
|
||||||
|
│
|
||||||
|
阶段 2:过滤(getTools)
|
||||||
|
│ 按权限模式过滤
|
||||||
|
│ 按 REPL 模式过滤
|
||||||
|
│ 按 isEnabled() 过滤
|
||||||
|
│
|
||||||
|
阶段 3:MCP 合并(assembleToolPool)
|
||||||
|
+ MCP 服务器提供的动态工具
|
||||||
|
去重(内置优先)
|
||||||
|
排序(缓存稳定性)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 工具执行管道
|
||||||
|
|
||||||
|
一次工具调用要经过**7 步管道**(`src/services/tools/toolExecution.ts`):
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 工具查找 ─→ 2. 输入解析(Zod) ─→ 3. 自定义验证
|
||||||
|
│
|
||||||
|
4. Pre-Tool 钩子 ─→ 5. 权限检查 ─→ 6. 实际执行 ─→ 7. Post-Tool 钩子
|
||||||
|
```
|
||||||
|
|
||||||
|
每一步都可以**中断、修改或增强**执行流程。这不是简单的 `try { tool.call(input) } catch`,而是一个完整的中间件管道。
|
||||||
|
|
||||||
|
### 3.4 工具延迟加载(Tool Deferred Loading)
|
||||||
|
|
||||||
|
Claude Code 有 48+ 个内置工具。如果每次 API 调用都把所有工具定义发给模型,会浪费大量 token。解决方案:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 工具可以标记为"延迟加载"
|
||||||
|
{
|
||||||
|
shouldDefer: true, // 只在 ToolSearch 中列出名称
|
||||||
|
alwaysLoad: false, // 不在初始提示词中包含完整 schema
|
||||||
|
searchHint: "notebook" // 搜索关键词
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
模型需要时通过 `ToolSearch` 工具动态获取完整定义。这大幅减少了系统提示词的大小。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、上下文管理与压缩
|
||||||
|
|
||||||
|
### 4.1 无限对话的秘密
|
||||||
|
|
||||||
|
Claude Code 宣称"对话没有上下文限制",这背后是一套**四级压缩系统**:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 第 1 级:Snip 压缩
|
||||||
|
|
||||||
|
对已处理的消息进行智能裁剪——移除重复的文件内容、过长的工具输出等。
|
||||||
|
|
||||||
|
#### 第 2 级:Micro 压缩
|
||||||
|
|
||||||
|
修改已缓存消息的内容,而不改变缓存键。这是一种"原地优化"策略。
|
||||||
|
|
||||||
|
#### 第 3 级:上下文折叠(Context Collapse)
|
||||||
|
|
||||||
|
将历史消息分阶段摘要。不是一次性摘要全部,而是**渐进式折叠**——先摘要最旧的消息,保留最近的细节。
|
||||||
|
|
||||||
|
#### 第 4 级:Auto Compact
|
||||||
|
|
||||||
|
当所有局部优化都不够时,通过 Claude 自身生成一个完整的对话摘要,替换所有历史消息。
|
||||||
|
|
||||||
|
### 4.2 系统上下文注入
|
||||||
|
|
||||||
|
每次 API 调用前,自动注入两种上下文(`src/context.ts`):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 系统上下文(memoized,整个会话缓存)
|
||||||
|
getSystemContext() → {
|
||||||
|
gitStatus, // 当前分支、最近提交、文件状态
|
||||||
|
cacheBreakerInjection // 系统级注入
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户上下文(memoized,CLAUDE.md 变化时清除)
|
||||||
|
getUserContext() → {
|
||||||
|
claudeMdContent, // 所有 CLAUDE.md 合并内容
|
||||||
|
currentDate, // 当前日期
|
||||||
|
mcpInstructions // MCP 服务器指令
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 系统提醒(System Reminders)
|
||||||
|
|
||||||
|
系统提醒是一种特殊的**附件消息**,注入到工具结果或用户消息中(`src/utils/attachments.ts`):
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<system-reminder>
|
||||||
|
这里是系统级的上下文信息,与具体的工具结果无关。
|
||||||
|
</system-reminder>
|
||||||
|
```
|
||||||
|
|
||||||
|
用途包括:
|
||||||
|
- 文件读取时的安全警告
|
||||||
|
- 记忆系统的时效提醒
|
||||||
|
- 用户侧问的附带信息
|
||||||
|
- Deferred 工具的可用通知
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、技能与插件生态
|
||||||
|
|
||||||
|
### 5.1 技能系统(Skills)
|
||||||
|
|
||||||
|
技能是 Claude Code 最强大的扩展机制之一。它不是简单的"命令别名",而是**完整的 AI 行为定义**。
|
||||||
|
|
||||||
|
#### 技能定义结构
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type BundledSkillDefinition = {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
whenToUse?: string // 模型自动判断何时使用
|
||||||
|
allowedTools?: string[] // 限制工具池
|
||||||
|
model?: string // 指定模型
|
||||||
|
hooks?: HooksSettings // 生命周期钩子
|
||||||
|
context?: 'inline' | 'fork' // 内联 or 独立上下文
|
||||||
|
agent?: string // 关联的 Agent 类型
|
||||||
|
getPromptForCommand: (args, context) => Promise<ContentBlockParam[]>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 两种执行上下文
|
||||||
|
|
||||||
|
| 上下文 | 行为 | 适用场景 |
|
||||||
|
|--------|------|----------|
|
||||||
|
| `inline` | 技能内容直接展开到当前对话 | 简单指令、格式模板 |
|
||||||
|
| `fork` | 技能作为子代理在独立上下文中运行 | 复杂工作流、需要独立 token 预算 |
|
||||||
|
|
||||||
|
#### 技能发现来源
|
||||||
|
|
||||||
|
```
|
||||||
|
内置技能(bundled) ← 编译到 CLI 中,15+ 个
|
||||||
|
↓
|
||||||
|
插件技能(plugin) ← 插件注册
|
||||||
|
↓
|
||||||
|
用户技能(~/.claude/skills/) ← 用户全局
|
||||||
|
↓
|
||||||
|
项目技能(.claude/skills/) ← 项目级
|
||||||
|
↓
|
||||||
|
策略技能(policy) ← 组织管理
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 插件系统(Plugins)
|
||||||
|
|
||||||
|
插件是更高层级的扩展单元,可以包含**技能、钩子、MCP 服务器、LSP 服务器**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type BuiltinPluginDefinition = {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
skills?: BundledSkillDefinition[] // 技能集合
|
||||||
|
hooks?: HooksSettings // 生命周期钩子
|
||||||
|
mcpServers?: Record<string, McpServerConfig> // MCP 服务器
|
||||||
|
lspServers?: Record<string, LspServerConfig> // LSP 服务器
|
||||||
|
isAvailable?: () => boolean // 可用性检查
|
||||||
|
defaultEnabled?: boolean // 默认启用
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
插件的关键设计:**用户可切换启用/禁用**,这与直接注册的技能不同。
|
||||||
|
|
||||||
|
### 5.3 钩子系统(Hooks)
|
||||||
|
|
||||||
|
钩子是整个生命周期的**可编程拦截点**:
|
||||||
|
|
||||||
|
```
|
||||||
|
SessionStart ─→ UserPromptSubmit ─→ PreToolUse ─→ [工具执行]
|
||||||
|
│ │
|
||||||
|
│ PostToolUse
|
||||||
|
│ │
|
||||||
|
└─ SubagentStart ←─── Stop ←─── TaskCompleted ←┘
|
||||||
|
│
|
||||||
|
SubagentStop ─→ SessionEnd
|
||||||
|
```
|
||||||
|
|
||||||
|
钩子通过 shell 命令执行,退出码控制行为:
|
||||||
|
- **0**:成功,stdout 内容按事件类型处理
|
||||||
|
- **2**:stderr 内容展示给模型或用户
|
||||||
|
- **其他**:仅展示给用户
|
||||||
|
|
||||||
|
### 5.4 MCP:模型上下文协议
|
||||||
|
|
||||||
|
MCP 是 Claude Code 与外部世界交互的标准协议。工具命名规范:
|
||||||
|
|
||||||
|
```
|
||||||
|
mcp__{标准化服务器名}__{工具名}
|
||||||
|
例如:mcp__chrome_devtools__take_screenshot
|
||||||
|
```
|
||||||
|
|
||||||
|
支持的传输方式:`stdio`、`sse`、`http`、`websocket`、`sdk`
|
||||||
|
|
||||||
|
MCP 工具在运行时动态发现,与内置工具**无缝合并**到统一的工具池中。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、权限与安全体系
|
||||||
|
|
||||||
|
### 6.1 分层权限模型
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ 权限规则(Rules) │
|
||||||
|
│ 来源:userSettings, projectSettings │
|
||||||
|
│ flagSettings, policySettings │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ 权限模式(Modes) │
|
||||||
|
│ default | plan | acceptEdits │
|
||||||
|
│ bypassPermissions | auto | bubble │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ 钩子(Hooks) │
|
||||||
|
│ PreToolUse 可拦截或修改 │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ 安全分类器(Classifier) │
|
||||||
|
│ ML 模型评估工具调用安全性 │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 权限决策流
|
||||||
|
|
||||||
|
每次工具调用的权限检查:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type PermissionResult =
|
||||||
|
| { behavior: 'allow', updatedInput?, decisionReason }
|
||||||
|
| { behavior: 'ask', message, suggestions }
|
||||||
|
| { behavior: 'deny', message, decisionReason }
|
||||||
|
| { behavior: 'passthrough', message }
|
||||||
|
```
|
||||||
|
|
||||||
|
决策原因追溯:
|
||||||
|
- `type: 'rule'` — 匹配了权限规则
|
||||||
|
- `type: 'mode'` — 权限模式决定
|
||||||
|
- `type: 'hook'` — 钩子拦截
|
||||||
|
- `type: 'classifier'` — ML 分类器判定
|
||||||
|
|
||||||
|
### 6.3 权限规则模式匹配
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 精确匹配
|
||||||
|
{ tool: 'Bash', behavior: 'deny' }
|
||||||
|
|
||||||
|
// 参数模式匹配
|
||||||
|
{ tool: 'Bash(git *)', behavior: 'allow' } // 允许所有 git 命令
|
||||||
|
{ tool: 'Bash(rm -rf *)', behavior: 'deny' } // 禁止 rm -rf
|
||||||
|
|
||||||
|
// 通配符
|
||||||
|
{ tool: 'File*', behavior: 'allow' } // 允许所有 File 开头的工具
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、故障恢复机制
|
||||||
|
|
||||||
|
这是 Claude Code 最精妙的设计之一。`src/query.ts` 的核心循环内置了**6 种恢复策略**:
|
||||||
|
|
||||||
|
| 恢复策略 | 触发条件 | 恢复方式 |
|
||||||
|
|----------|----------|----------|
|
||||||
|
| `collapse_drain_retry` | prompt 过长 | 排空已暂存的上下文折叠,重试 |
|
||||||
|
| `reactive_compact_retry` | 仍然过长 | 通过 Claude 生成摘要,重试 |
|
||||||
|
| `max_output_tokens_escalate` | 触及 8k 默认限制 | 升级到 64k 限制重试 |
|
||||||
|
| `max_output_tokens_recovery` | 触及任何限制 | 注入"继续"提示,重试(最多 3 次) |
|
||||||
|
| `stop_hook_blocking` | Stop 钩子阻塞 | 将阻塞错误注入上下文,重试 |
|
||||||
|
| `token_budget_continuation` | 预算尚余 | 注入预算提示,继续执行 |
|
||||||
|
|
||||||
|
每种恢复都通过修改 `state` 实现:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 例:prompt 过长恢复
|
||||||
|
if (error.type === 'prompt_too_long') {
|
||||||
|
// 排空所有暂存的折叠
|
||||||
|
const compacted = drainStagedCollapses(state.messages)
|
||||||
|
state = { ...state, messages: compacted, transition: { reason: 'collapse_drain_retry' } }
|
||||||
|
continue // 回到循环顶部重试
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.1 模型降级
|
||||||
|
|
||||||
|
当主模型流式传输失败时,系统会:
|
||||||
|
1. 清理孤立的未完成消息
|
||||||
|
2. 切换到备用模型
|
||||||
|
3. 用新模型重试
|
||||||
|
|
||||||
|
### 7.2 媒体大小恢复
|
||||||
|
|
||||||
|
当图片等媒体内容导致 token 超限时:
|
||||||
|
- 触发反应式压缩
|
||||||
|
- 自动剥离图片内容
|
||||||
|
- 保留文本信息重试
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、与 LangChain/ReAct 的本质区别
|
||||||
|
|
||||||
|
### 8.1 架构范式对比
|
||||||
|
|
||||||
|
| 维度 | LangChain | Claude Code |
|
||||||
|
|------|-----------|-------------|
|
||||||
|
| **核心模式** | ReAct(Think→Act→Observe) | Async Generator 状态机 |
|
||||||
|
| **执行模型** | 同步阻塞 | 流式非阻塞 |
|
||||||
|
| **工具执行** | 等待模型完整响应后执行 | 流式传输中即时执行 |
|
||||||
|
| **状态管理** | 外部 Memory 对象 | 内置状态赋值 + 循环 |
|
||||||
|
| **错误恢复** | 需要手动编排 | 6 种内置恢复策略 |
|
||||||
|
| **上下文压缩** | 简单截断或摘要 | 四级渐进式压缩 |
|
||||||
|
| **多 Agent** | Chain/Graph 显式编排 | 统一工具接口 + 状态机 |
|
||||||
|
| **扩展机制** | Python 类继承 | 技能 + 插件 + 钩子 + MCP |
|
||||||
|
| **缓存策略** | 无 | 全局/会话/按轮三级缓存 |
|
||||||
|
|
||||||
|
### 8.2 为什么不用 ReAct?
|
||||||
|
|
||||||
|
ReAct 模式有几个固有限制:
|
||||||
|
|
||||||
|
1. **串行瓶颈**:每一步必须等待完整的"思考→行动→观察"循环
|
||||||
|
2. **无流式能力**:模型生成完整响应后才能开始执行工具
|
||||||
|
3. **恢复困难**:没有统一的状态表示,难以实现自动恢复
|
||||||
|
4. **缓存不友好**:每次循环的 prompt 结构变化大,难以利用缓存
|
||||||
|
|
||||||
|
Claude Code 的 Async Generator 模式解决了所有这些问题:
|
||||||
|
|
||||||
|
- **流式执行**:工具在模型生成过程中就开始运行
|
||||||
|
- **状态可控**:`State` 对象包含所有需要的信息,恢复只需修改状态
|
||||||
|
- **缓存优化**:静态提示词全局缓存,动态部分最小化
|
||||||
|
- **并行能力**:只读工具自动并行,写入工具串行保序
|
||||||
|
|
||||||
|
### 8.3 与 LangChain Agent 的具体差异
|
||||||
|
|
||||||
|
```
|
||||||
|
LangChain Agent:
|
||||||
|
agent = initialize_agent(tools, llm, agent="zero-shot-react-description")
|
||||||
|
result = agent.run("do something")
|
||||||
|
# 内部:LLM → parse → tool → LLM → parse → tool → ... → final answer
|
||||||
|
# 每一步都是独立的 LLM 调用
|
||||||
|
|
||||||
|
Claude Code Agent:
|
||||||
|
for await (const msg of query({ messages, tools, systemPrompt })) {
|
||||||
|
yield msg // 实时产出消息
|
||||||
|
// 内部:流式 LLM → 流式工具执行 → 状态更新 → 继续
|
||||||
|
// 单次 API 调用可以触发多个工具,工具在流式中执行
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
关键差异:
|
||||||
|
- LangChain 的每一"步"是一次完整的 LLM 调用
|
||||||
|
- Claude Code 的每一"轮"可以包含多个工具调用,且工具在流式传输中执行
|
||||||
|
- LangChain 需要 OutputParser 解析模型输出中的工具调用
|
||||||
|
- Claude Code 直接使用 Anthropic API 的原生 `tool_use` 能力,无需解析
|
||||||
|
|
||||||
|
### 8.4 与 LangGraph 的对比
|
||||||
|
|
||||||
|
LangGraph 是 LangChain 的升级版,引入了图结构:
|
||||||
|
|
||||||
|
| 维度 | LangGraph | Claude Code |
|
||||||
|
|------|-----------|-------------|
|
||||||
|
| **状态流转** | 显式图节点 + 边 | 隐式状态机(while + continue) |
|
||||||
|
| **可视化** | 可导出为图 | 状态转换原因可追溯 |
|
||||||
|
| **持久化** | Checkpoint + State | 文件系统 + 消息历史 |
|
||||||
|
| **人机交互** | interrupt_before/after | 权限系统 + 钩子 |
|
||||||
|
| **多 Agent** | 需要显式编排 | AgentTool 统一接口 |
|
||||||
|
|
||||||
|
Claude Code 的优势在于**简单性**——不需要定义图结构,一个 while 循环就能处理所有情况。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、为什么 Claude Code 能做到这么好?
|
||||||
|
|
||||||
|
从源码分析中,我们可以总结出以下核心设计原则:
|
||||||
|
|
||||||
|
### 9.1 流式优先(Streaming First)
|
||||||
|
|
||||||
|
整个架构围绕 `AsyncGenerator` 设计,一切都是流式的:
|
||||||
|
- 模型响应是流式的
|
||||||
|
- 工具在流式中执行
|
||||||
|
- 进度实时更新
|
||||||
|
- 压缩策略是渐进式的
|
||||||
|
|
||||||
|
这意味着用户**永远不需要等待**——看到模型在思考、工具在执行、结果在产出。
|
||||||
|
|
||||||
|
### 9.2 智能缓存(Intelligent Caching)
|
||||||
|
|
||||||
|
三级提示词缓存系统(`src/services/api/claude.ts:3213-3237`):
|
||||||
|
|
||||||
|
```
|
||||||
|
Global Cache(跨组织) ← 静态系统提示词
|
||||||
|
↓
|
||||||
|
Ephemeral Cache(会话级) ← 动态系统提示词
|
||||||
|
↓
|
||||||
|
Section Cache(轮级) ← systemPromptSection 记忆化
|
||||||
|
```
|
||||||
|
|
||||||
|
这大幅降低了每次 API 调用的延迟和成本。
|
||||||
|
|
||||||
|
### 9.3 优雅降级(Graceful Degradation)
|
||||||
|
|
||||||
|
6 种恢复策略确保 Claude Code **几乎不会因为技术问题中断用户的工作流**:
|
||||||
|
- Token 超限?自动压缩
|
||||||
|
- API 超时?自动重试
|
||||||
|
- 模型失败?降级到备用模型
|
||||||
|
- 工具失败?记录错误,继续对话
|
||||||
|
|
||||||
|
### 9.4 最小抽象原则(Minimal Abstraction)
|
||||||
|
|
||||||
|
与 LangChain 的"万物皆抽象"不同,Claude Code 的核心只有:
|
||||||
|
- **一个循环**(`while (true)` in `query()`)
|
||||||
|
- **一个状态**(`State` 对象)
|
||||||
|
- **一个接口**(`Tool` 类型)
|
||||||
|
|
||||||
|
没有 Agent → AgentExecutor → Chain → Memory → Callback 的嵌套抽象层。这使得代码**易于理解、调试和扩展**。
|
||||||
|
|
||||||
|
### 9.5 原生 API 集成(Native API Integration)
|
||||||
|
|
||||||
|
Claude Code 直接使用 Anthropic API 的原生能力:
|
||||||
|
- **原生工具调用**:无需 OutputParser,直接使用 `tool_use` 块
|
||||||
|
- **原生流式传输**:无需包装层,直接消费 SSE 流
|
||||||
|
- **原生缓存**:利用 API 的 prompt caching 特性
|
||||||
|
- **原生思维链**:直接使用 extended thinking
|
||||||
|
|
||||||
|
这避免了"框架税"——LangChain 等框架在 LLM 和开发者之间增加的抽象层。
|
||||||
|
|
||||||
|
### 9.6 工具驱动的 Agent(Tool-Driven Agent)
|
||||||
|
|
||||||
|
Claude Code 的哲学是:**Agent 的能力等于其工具的能力**。
|
||||||
|
|
||||||
|
- 子代理生成?是一个工具(`AgentTool`)
|
||||||
|
- 团队管理?是一个工具(`TeamCreate`/`SendMessage`)
|
||||||
|
- 文件编辑?是一个工具(`FileEdit`)
|
||||||
|
- 技能执行?是一个工具(`SkillTool`)
|
||||||
|
|
||||||
|
这意味着**所有能力都通过统一的工具接口暴露**,模型通过自然语言推理来决定使用哪个工具。不需要显式的编排逻辑——模型本身就是编排器。
|
||||||
|
|
||||||
|
### 9.7 深度集成的开发体验
|
||||||
|
|
||||||
|
Claude Code 不是"通用 Agent + 代码插件",而是**从底层为编码场景深度优化**:
|
||||||
|
|
||||||
|
- **Git 感知**:自动注入 git 状态,理解分支、提交、diff
|
||||||
|
- **文件系统感知**:理解项目结构,智能搜索文件
|
||||||
|
- **Worktree 隔离**:安全的实验性修改环境
|
||||||
|
- **LSP 集成**:语言服务器协议提供类型信息和诊断
|
||||||
|
- **MCP 生态**:通过标准协议连接各种外部工具
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、架构总结
|
||||||
|
|
||||||
|
### 核心组件关系
|
||||||
|
|
||||||
|
```
|
||||||
|
用户输入
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
QueryEngine(src/QueryEngine.ts)
|
||||||
|
│
|
||||||
|
├─ 构建系统提示词(prompts.ts + context.ts + claudemd.ts)
|
||||||
|
├─ 组装工具池(tools.ts + MCP)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
query() 异步生成器循环(src/query.ts)
|
||||||
|
│
|
||||||
|
├─ 阶段1: 消息压缩(snip → micro → collapse → compact)
|
||||||
|
├─ 阶段2: 流式 API 调用(callModel + StreamingToolExecutor)
|
||||||
|
├─ 阶段3: 决策点(继续 or 完成)
|
||||||
|
├─ 阶段4: 工具编排(并行只读 + 串行写入)
|
||||||
|
└─ 阶段5: 状态更新(state = next → continue)
|
||||||
|
│
|
||||||
|
├─ 恢复策略(6种)
|
||||||
|
├─ 钩子系统(PreToolUse / PostToolUse / Stop / ...)
|
||||||
|
└─ 子代理生成(AgentTool → runAgent → 新的 query() 实例)
|
||||||
|
│
|
||||||
|
├─ 同步前台
|
||||||
|
├─ 异步后台(LocalAgentTask)
|
||||||
|
├─ Fork(继承上下文)
|
||||||
|
└─ Teammate(邮箱通信)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 一句话总结
|
||||||
|
|
||||||
|
> **Claude Code 的 Agent 框架是一个以 AsyncGenerator 为核心的流式状态机,通过统一的工具接口暴露所有能力,配合四级上下文压缩、三级提示词缓存、六种故障恢复策略,实现了一个无需显式编排即可自主完成复杂编程任务的 AI 系统。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十一、关键源文件索引
|
||||||
|
|
||||||
|
| 组件 | 文件路径 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 核心循环 | `src/query.ts` | Agent 主循环(~1730 行) |
|
||||||
|
| 查询引擎 | `src/QueryEngine.ts` | 高层封装(~687 行) |
|
||||||
|
| 工具定义 | `src/Tool.ts` | Tool 类型系统(~792 行) |
|
||||||
|
| 工具注册 | `src/tools.ts` | 工具发现和注册(~389 行) |
|
||||||
|
| 工具执行 | `src/services/tools/toolExecution.ts` | 执行管道(~1500 行) |
|
||||||
|
| 工具编排 | `src/services/tools/toolOrchestration.ts` | 并行/串行策略 |
|
||||||
|
| 系统提示词 | `src/constants/prompts.ts` | 提示词组装(~577 行) |
|
||||||
|
| 提示词 Sections | `src/constants/systemPromptSections.ts` | 分段缓存 |
|
||||||
|
| 上下文管理 | `src/context.ts` | 系统/用户上下文 |
|
||||||
|
| CLAUDE.md | `src/utils/claudemd.ts` | 用户指令加载 |
|
||||||
|
| 记忆系统 | `src/memdir/memdir.ts` | 持久化记忆 |
|
||||||
|
| Agent 生成 | `src/tools/AgentTool/AgentTool.tsx` | Agent 工具入口 |
|
||||||
|
| Agent 运行 | `src/tools/AgentTool/runAgent.ts` | Agent 执行逻辑 |
|
||||||
|
| Fork 代理 | `src/tools/AgentTool/forkSubagent.ts` | Fork 缓存优化 |
|
||||||
|
| 团队管理 | `src/utils/swarm/teamHelpers.ts` | Teams 基础设施 |
|
||||||
|
| 邮箱通信 | `src/utils/teammateMailbox.ts` | 异步消息队列 |
|
||||||
|
| 技能系统 | `src/skills/bundledSkills.ts` | 技能注册与管理 |
|
||||||
|
| 插件系统 | `src/plugins/builtinPlugins.ts` | 插件框架 |
|
||||||
|
| 钩子系统 | `src/utils/hooks/hooksConfigManager.ts` | 钩子管理 |
|
||||||
|
| 权限系统 | `src/utils/permissions/permissions.ts` | 权限检查 |
|
||||||
|
| 状态管理 | `src/state/AppStateStore.ts` | 全局状态 |
|
||||||
|
| 成本追踪 | `src/cost-tracker.ts` | API 成本计算 |
|
||||||
|
| API 客户端 | `src/services/api/claude.ts` | Anthropic API 封装 |
|
||||||
|
| MCP 客户端 | `src/services/mcp/client.ts` | MCP 协议实现 |
|
||||||
|
| 协调者模式 | `src/coordinator/coordinatorMode.ts` | 多 Agent 编排 |
|
||||||
|
| 远程会话 | `src/remote/RemoteSessionManager.ts` | CCR 连接管理 |
|
||||||
|
| Bridge | `src/bridge/bridgeMain.ts` | 远程桥接 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十二、进一步阅读
|
||||||
|
|
||||||
|
- [使用指南](./01-usage-guide.md) — 面向用户的多 Agent 使用手册
|
||||||
|
- [实现原理](./02-implementation.md) — 多 Agent 编排的技术细节
|
||||||
|
- [Anthropic API 文档](https://docs.anthropic.com/) — 原生 API 能力
|
||||||
|
- [MCP 协议规范](https://modelcontextprotocol.io/) — 模型上下文协议
|
||||||
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 734 KiB |
|
After Width: | Height: | Size: 698 KiB |
|
After Width: | Height: | Size: 595 KiB |
|
After Width: | Height: | Size: 679 KiB |
@@ -0,0 +1,129 @@
|
|||||||
|
# Claude Code 多 Agent 系统文档
|
||||||
|
|
||||||
|
> 完整的多 Agent 编排使用指南和实现原理文档
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 文档目录
|
||||||
|
|
||||||
|
### [01-usage-guide.md](./01-usage-guide.md) — 使用指南
|
||||||
|
|
||||||
|
面向用户的完整使用手册,涵盖:
|
||||||
|
|
||||||
|
- **Agent 工具**:参数详解、生成方式、后台运行
|
||||||
|
- **六种内置 Agent**:general-purpose、Explore、Plan、verification、claude-code-guide、statusline-setup
|
||||||
|
- **后台任务**:异步执行、进度追踪、完成通知
|
||||||
|
- **Agent Teams**:团队创建、成员协作、消息通信
|
||||||
|
- **Worktree 隔离**:独立环境、分支管理、安全上下文
|
||||||
|
- **自定义 Agent**:定义格式、工具池配置、系统提示词
|
||||||
|
|
||||||
|
**适合人群**:所有 Claude Code 用户
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [02-implementation.md](./02-implementation.md) — 实现原理
|
||||||
|
|
||||||
|
面向开发者的技术深度解析,涵盖:
|
||||||
|
|
||||||
|
- **架构总览**:5 大 Agent 类别、4 条生成路径
|
||||||
|
- **Agent 生成流程**:同步/异步/Fork/Teammate 四种路径详解
|
||||||
|
- **工具池系统**:三层过滤、常量定义、权限映射
|
||||||
|
- **上下文传递**:CacheSafeParams、系统提示词构建、Fork 缓存优化
|
||||||
|
- **Teams 内部机制**:TeamFile 结构、邮箱系统、收件箱轮询、消息路由
|
||||||
|
- **后台任务引擎**:LocalAgentTask 生命周期、进度追踪、通知队列
|
||||||
|
- **权限同步**:团队级权限、模式传播、bubble 模式
|
||||||
|
- **完整数据流**:从 Agent Tool 调用到结果回传
|
||||||
|
|
||||||
|
**适合人群**:贡献者、架构师、想深入了解实现的开发者
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [03-agent-framework.md](./03-agent-framework.md) — Agent 框架深度解析
|
||||||
|
|
||||||
|
从源码视角剖析 Claude Code 底层 Agent 架构的设计哲学,涵盖:
|
||||||
|
|
||||||
|
- **核心 Agent 循环**:AsyncGenerator 状态机、五阶段 while(true) 循环
|
||||||
|
- **系统提示词工程**:分层构建、缓存边界、CLAUDE.md 加载机制
|
||||||
|
- **工具系统设计**:完整生命周期管理、三阶段注册、七步执行管道
|
||||||
|
- **上下文管理与压缩**:四级渐进式压缩、系统上下文注入、系统提醒
|
||||||
|
- **技能与插件生态**:技能定义与发现、插件系统、钩子系统、MCP 集成
|
||||||
|
- **权限与安全体系**:分层权限模型、规则模式匹配
|
||||||
|
- **故障恢复机制**:6 种内置恢复策略、模型降级
|
||||||
|
- **与 LangChain/ReAct 对比**:架构范式差异、为什么不用 ReAct
|
||||||
|
- **为什么 Claude Code 能做到这么好**:7 大核心设计原则
|
||||||
|
|
||||||
|
**适合人群**:想理解 AI Agent 框架设计的架构师、AI 应用开发者、技术研究者
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🖼️ 配图说明
|
||||||
|
|
||||||
|
所有配图采用深色背景(#1a1a2e)+ Anthropic 品牌橙铜色(#D97757)风格,与 Claude Code 官方文档一致。
|
||||||
|
|
||||||
|
| 图片 | 说明 | 所属文档 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `01-agent-overview.png` | 多 Agent 系统概览 — 架构全景 | 使用指南 |
|
||||||
|
| `02-agent-types.png` | 六种内置 Agent — 类型对比矩阵 | 使用指南 |
|
||||||
|
| `03-spawn-flow.png` | Agent 生成流程 — 四条路径决策树 | 使用指南 |
|
||||||
|
| `04-agent-teams.png` | Agent Teams 协作 — 团队通信拓扑 | 使用指南 |
|
||||||
|
| `05-architecture.png` | 实现架构总览 — 核心模块关系 | 实现原理 |
|
||||||
|
| `06-context-passing.png` | 上下文传递 — CacheSafeParams 数据流 | 实现原理 |
|
||||||
|
| `07-tool-pool.png` | 工具池系统 — 三层过滤流程 | 实现原理 |
|
||||||
|
| `08-background-task.png` | 后台任务引擎 — 生命周期状态机 | 实现原理 |
|
||||||
|
| `09-teams-mailbox.png` | Teams 邮箱系统 — 消息路由拓扑 | 实现原理 |
|
||||||
|
| `10-fork-cache.png` | Fork 缓存优化 — 字节级一致共享 | 实现原理 |
|
||||||
|
| `11-agent-framework-overview.png` | Agent 框架架构总览 — 核心组件关系 | 框架解析 |
|
||||||
|
| `12-agent-core-loop.png` | 核心 Agent 循环 — 五阶段状态机 | 框架解析 |
|
||||||
|
| `13-system-prompt-pipeline.png` | 系统提示词构建 — 分层缓存流水线 | 框架解析 |
|
||||||
|
| `14-context-compression.png` | 上下文压缩 — 四级渐进式策略 | 框架解析 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 用户
|
||||||
|
|
||||||
|
1. 阅读 [使用指南](./01-usage-guide.md)
|
||||||
|
2. 了解六种内置 Agent 及其适用场景
|
||||||
|
3. 尝试在对话中使用 Agent 工具生成子代理
|
||||||
|
4. 探索 Agent Teams 多代理协作
|
||||||
|
|
||||||
|
### 开发者
|
||||||
|
|
||||||
|
1. 阅读 [实现原理](./02-implementation.md)
|
||||||
|
2. 查看源码位置:
|
||||||
|
- `src/tools/AgentTool/` — Agent 工具实现
|
||||||
|
- `src/tools/TeamCreateTool/` — 团队创建
|
||||||
|
- `src/tools/SendMessageTool/` — 代理间通信
|
||||||
|
- `src/utils/swarm/` — Swarm 协作基础设施
|
||||||
|
- `src/utils/forkedAgent.ts` — Fork 代理上下文
|
||||||
|
- `src/tasks/` — 任务管理系统
|
||||||
|
3. 理解四条生成路径和上下文传递机制
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 核心概念速查
|
||||||
|
|
||||||
|
| 概念 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **Agent Tool** | 主入口工具,接受 prompt + subagent_type 生成子代理 |
|
||||||
|
| **Subagent** | 独立执行任务的子代理,有自己的工具池和权限 |
|
||||||
|
| **Fork Agent** | 继承父代理完整上下文的分叉代理,共享 prompt cache |
|
||||||
|
| **Teammate** | Agent Teams 中的协作成员,通过邮箱通信 |
|
||||||
|
| **Worktree** | Git worktree 隔离模式,独立文件环境 |
|
||||||
|
| **LocalAgentTask** | 本地代理任务状态,追踪 running/completed/failed |
|
||||||
|
| **DreamTask** | 自动记忆整合任务,定期后台运行 |
|
||||||
|
| **CacheSafeParams** | 缓存安全参数,确保 API 请求前缀字节级一致 |
|
||||||
|
| **TeamFile** | 团队配置文件,存储成员列表和权限 |
|
||||||
|
| **Mailbox** | 基于文件的消息队列,支持队友间异步通信 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 相关资源
|
||||||
|
|
||||||
|
- [Claude Code Haha 主页](/)
|
||||||
|
- [记忆系统文档](/memory/01-usage-guide)
|
||||||
|
- [Agent Tool 源码](https://github.com/NanmiCoder/cc-haha/tree/main/src/tools/AgentTool/)
|
||||||
|
- [Swarm 基础设施](https://github.com/NanmiCoder/cc-haha/tree/main/src/utils/swarm/)
|
||||||
|
- [任务管理系统](https://github.com/NanmiCoder/cc-haha/tree/main/src/tasks/)
|
||||||
|
- [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues)
|
||||||
@@ -0,0 +1,785 @@
|
|||||||
|
# Channel 系统架构解析
|
||||||
|
|
||||||
|
> 从源码视角深度剖析 Claude Code 如何通过 IM 平台远程控制 Agent
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#一什么是-channel">概念</a> ·
|
||||||
|
<a href="#二整体架构">架构</a> ·
|
||||||
|
<a href="#三消息协议">协议</a> ·
|
||||||
|
<a href="#四六层访问控制">访问控制</a> ·
|
||||||
|
<a href="#五权限中继系统">权限中继</a> ·
|
||||||
|
<a href="#六ui-组件">UI</a> ·
|
||||||
|
<a href="#七插件-channel-架构">插件</a> ·
|
||||||
|
<a href="#八安全设计">安全</a> ·
|
||||||
|
<a href="#九命令行接口">CLI</a> ·
|
||||||
|
<a href="#十特性开关与分析">特性开关</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、什么是 Channel
|
||||||
|
|
||||||
|
Channel 是 Claude Code 的 **IM 集成系统**,它允许用户通过 Telegram、Feishu(飞书)、Discord、Slack 等即时通讯平台远程控制正在运行的 Claude Code Agent。
|
||||||
|
|
||||||
|
### 核心理念
|
||||||
|
|
||||||
|
传统的 AI 编程助手只能在终端中交互。Channel 系统打破了这一限制——你可以在手机上通过 Telegram 给 Claude Code 发消息,它会像在终端中一样理解并执行你的请求,并将结果回复到你的聊天窗口。
|
||||||
|
|
||||||
|
### Channel 的本质
|
||||||
|
|
||||||
|
从技术角度看,一个 Channel 就是一个特殊的 **MCP(Model Context Protocol)Server**,它需要:
|
||||||
|
|
||||||
|
1. **声明能力**:在 MCP 握手时声明 `experimental['claude/channel']` 能力
|
||||||
|
2. **推送消息**:通过 `notifications/claude/channel` 通知将 IM 消息推入 Agent 对话
|
||||||
|
3. **暴露工具**:提供 `reply`、`react`、`edit_message` 等 MCP 工具,让 Agent 回复到 IM 平台
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Channel 的两种形态
|
||||||
|
type ChannelEntry =
|
||||||
|
| { kind: 'plugin'; name: string; marketplace: string; dev?: boolean }
|
||||||
|
| { kind: 'server'; name: string; dev?: boolean }
|
||||||
|
```
|
||||||
|
|
||||||
|
**plugin 类型**:来自 marketplace 的验证插件(如 `plugin:telegram@anthropic`)
|
||||||
|
**server 类型**:直接指定的 MCP 服务器名称(始终需要 dev 旁路)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、整体架构
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 消息流转全链路
|
||||||
|
|
||||||
|
Channel 系统的消息流转遵循一个清晰的双向路径:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 入站(IM → Agent) │
|
||||||
|
│ │
|
||||||
|
│ Telegram/Feishu/Discord │
|
||||||
|
│ ↓ │
|
||||||
|
│ Channel Plugin(MCP Server) │
|
||||||
|
│ ↓ │
|
||||||
|
│ notifications/claude/channel { content, meta } │
|
||||||
|
│ ↓ │
|
||||||
|
│ useManageMCPConnections → registerNotificationHandler │
|
||||||
|
│ ↓ │
|
||||||
|
│ wrapChannelMessage() → <channel source="..." user="..."> │
|
||||||
|
│ ↓ │
|
||||||
|
│ enqueue({ priority: 'next', isMeta: true }) │
|
||||||
|
│ ↓ │
|
||||||
|
│ SleepTool 每 ~1s 轮询 hasCommandsInQueue() │
|
||||||
|
│ ↓ │
|
||||||
|
│ Model 看到 <channel> 标签,理解消息来源 │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 出站(Agent → IM) │
|
||||||
|
│ │
|
||||||
|
│ Model 决定使用哪个工具回复 │
|
||||||
|
│ ↓ │
|
||||||
|
│ callTool() → Channel 的 MCP 工具 │
|
||||||
|
│ (reply / react / edit_message / download_attachment) │
|
||||||
|
│ ↓ │
|
||||||
|
│ MCP 协议调用 Channel Server │
|
||||||
|
│ ↓ │
|
||||||
|
│ Channel Server 发送消息到 IM 平台 │
|
||||||
|
│ ↓ │
|
||||||
|
│ Telegram/Feishu/Discord 用户收到回复 │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 核心组件关系
|
||||||
|
|
||||||
|
| 组件 | 文件 | 职责 |
|
||||||
|
|------|------|------|
|
||||||
|
| **Channel Gate** | `channelNotification.ts` | 六层访问控制门 |
|
||||||
|
| **Message Wrapper** | `channelNotification.ts` | XML 消息封装 |
|
||||||
|
| **Permission Relay** | `channelPermissions.ts` | 远程权限审批 |
|
||||||
|
| **Allowlist** | `channelAllowlist.ts` | GrowthBook 白名单管理 |
|
||||||
|
| **MCP Connection** | `useManageMCPConnections.ts` | 连接管理与通知注册 |
|
||||||
|
| **Channel Message UI** | `UserChannelMessage.tsx` | 终端渲染 Channel 消息 |
|
||||||
|
| **Dev Dialog** | `DevChannelsDialog.tsx` | 开发模式确认对话框 |
|
||||||
|
| **Channels Notice** | `ChannelsNotice.tsx` | 启动时 Channel 状态通知 |
|
||||||
|
| **Plugin Integration** | `mcpPluginIntegration.ts` | 插件作用域命名 |
|
||||||
|
| **State** | `bootstrap/state.ts` | 全局 Channel 白名单状态 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、消息协议
|
||||||
|
|
||||||
|
### 3.1 入站通知 Schema
|
||||||
|
|
||||||
|
Channel Server 推送到 Claude Code 的通知格式:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// channelNotification.ts
|
||||||
|
const ChannelMessageNotificationSchema = z.object({
|
||||||
|
method: z.literal('notifications/claude/channel'),
|
||||||
|
params: z.object({
|
||||||
|
content: z.string(),
|
||||||
|
// 透传元数据 — thread_id、user 等
|
||||||
|
// 渲染为 <channel> 标签的属性
|
||||||
|
meta: z.record(z.string(), z.string()).optional(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 XML 封装
|
||||||
|
|
||||||
|
收到通知后,系统将其封装为 `<channel>` XML 标签:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// channelNotification.ts:106-116
|
||||||
|
function wrapChannelMessage(
|
||||||
|
serverName: string,
|
||||||
|
content: string,
|
||||||
|
meta?: Record<string, string>,
|
||||||
|
): string {
|
||||||
|
const attrs = Object.entries(meta ?? {})
|
||||||
|
.filter(([k]) => SAFE_META_KEY.test(k)) // 防 XML 注入
|
||||||
|
.map(([k, v]) => ` ${k}="${escapeXmlAttr(v)}"`)
|
||||||
|
.join('')
|
||||||
|
return `<channel source="${escapeXmlAttr(serverName)}"${attrs}>
|
||||||
|
${content}
|
||||||
|
</channel>`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**封装结果示例**:
|
||||||
|
```xml
|
||||||
|
<channel source="plugin:telegram:tg" user="alice" chat_id="123456">
|
||||||
|
帮我看看 main.ts 有什么问题
|
||||||
|
</channel>
|
||||||
|
```
|
||||||
|
|
||||||
|
Model 看到这个标签后,就知道消息来自 Telegram 的用户 alice,并会使用 Telegram 的 `reply` 工具回复。
|
||||||
|
|
||||||
|
### 3.3 安全的元数据过滤
|
||||||
|
|
||||||
|
Meta 键名会成为 XML 属性名,恶意构造的键(如 `x="" injected="y`)可能导致 XML 注入。系统使用严格的正则过滤:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 只允许纯标识符格式的键名
|
||||||
|
const SAFE_META_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/
|
||||||
|
```
|
||||||
|
|
||||||
|
实际场景中,Channel 服务器只发送 `chat_id`、`user`、`thread_ts`、`message_id` 这类安全键名。
|
||||||
|
|
||||||
|
### 3.4 消息入队
|
||||||
|
|
||||||
|
封装后的消息通过 enqueue 推入消息队列:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
enqueue({
|
||||||
|
mode: 'prompt',
|
||||||
|
value: wrapChannelMessage(serverName, content, meta),
|
||||||
|
priority: 'next', // 高优先级
|
||||||
|
isMeta: true, // 元数据消息
|
||||||
|
origin: { kind: 'channel', server: serverName },
|
||||||
|
skipSlashCommands: true // 不解释为斜杠命令
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
SleepTool 每约 1 秒轮询一次 `hasCommandsInQueue()`,发现新消息后唤醒 Agent。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、六层访问控制
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Channel 系统采用**六层递进式访问控制**,每一层都可以独立阻断 Channel 注册。这是整个系统安全性的基石。
|
||||||
|
|
||||||
|
### Gate 函数签名
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// channelNotification.ts:191-316
|
||||||
|
function gateChannelServer(
|
||||||
|
serverName: string,
|
||||||
|
capabilities: ServerCapabilities | undefined,
|
||||||
|
pluginSource: string | undefined,
|
||||||
|
): ChannelGateResult // { action: 'register' } | { action: 'skip', kind, reason }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.1 第一层:能力声明(Capability)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (!capabilities?.experimental?.['claude/channel']) {
|
||||||
|
return { action: 'skip', kind: 'capability',
|
||||||
|
reason: 'server did not declare claude/channel capability' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
MCP Server 必须在握手时声明 `experimental['claude/channel']: {}` 能力。这是 MCP 的"存在信号"惯例(类似 `tools: {}`),将普通 MCP Server 与 Channel Server 区分开来。
|
||||||
|
|
||||||
|
### 4.2 第二层:运行时开关(Runtime Gate)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (!isChannelsEnabled()) {
|
||||||
|
return { action: 'skip', kind: 'disabled',
|
||||||
|
reason: 'channels feature is not currently available' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`isChannelsEnabled()` 检查 GrowthBook 特性开关 `tengu_harbor`(默认 false,5 分钟刷新)。这是全局的"紧急制动"——关闭此开关立即禁用所有 Channel,不需要发版。
|
||||||
|
|
||||||
|
### 4.3 第三层:OAuth 认证(Auth)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (!getClaudeAIOAuthTokens()?.accessToken) {
|
||||||
|
return { action: 'skip', kind: 'auth',
|
||||||
|
reason: 'channels requires claude.ai authentication (run /login)' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Channel 仅限 OAuth 认证用户。API Key 用户被阻止,因为 Console 端目前还没有 `channelsEnabled` 的管理界面。
|
||||||
|
|
||||||
|
### 4.4 第四层:组织策略(Policy)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const sub = getSubscriptionType()
|
||||||
|
const managed = sub === 'team' || sub === 'enterprise'
|
||||||
|
const policy = managed ? getSettingsForSource('policySettings') : undefined
|
||||||
|
if (managed && policy?.channelsEnabled !== true) {
|
||||||
|
return { action: 'skip', kind: 'policy',
|
||||||
|
reason: 'channels not enabled by org policy' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Teams/Enterprise 组织必须在托管设置中显式启用 `channelsEnabled: true`。默认关闭——即使没有配置任何策略的团队组织也不会回退到非托管路径。
|
||||||
|
|
||||||
|
### 4.5 第五层:会话白名单(Session)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const entry = findChannelEntry(serverName, getAllowedChannels())
|
||||||
|
if (!entry) {
|
||||||
|
return { action: 'skip', kind: 'session',
|
||||||
|
reason: `server ${serverName} not in --channels list for this session` }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
MCP Server 必须在本次会话的 `--channels` 参数列表中。即使一个受信任的 Server 动态添加了 `claude/channel` 能力,也无法绕过——必须用户在启动时显式列出。
|
||||||
|
|
||||||
|
### 4.6 第六层:Marketplace 验证 + 白名单(Allowlist)
|
||||||
|
|
||||||
|
对于 plugin 类型的 Channel:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Marketplace 验证:确保安装的插件来自声称的市场
|
||||||
|
const actual = pluginSource
|
||||||
|
? parsePluginIdentifier(pluginSource).marketplace
|
||||||
|
: undefined
|
||||||
|
if (actual !== entry.marketplace) {
|
||||||
|
return { action: 'skip', kind: 'marketplace',
|
||||||
|
reason: `tag mismatch: asked for @${entry.marketplace}, installed from ${actual}` }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 白名单检查:插件必须在 GrowthBook 审批列表中
|
||||||
|
if (!entry.dev) {
|
||||||
|
const { entries } = getEffectiveChannelAllowlist(sub, policy?.allowedChannelPlugins)
|
||||||
|
if (!entries.some(e => e.plugin === entry.name && e.marketplace === entry.marketplace)) {
|
||||||
|
return { action: 'skip', kind: 'allowlist', reason: 'not on approved list' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**双重验证**:先验证 `--channels plugin:slack@anthropic` 中的 `@anthropic` 标签与实际安装的插件来源是否匹配(防止 `slack@evil` 冒充 `slack@anthropic`),再检查是否在审批白名单中。
|
||||||
|
|
||||||
|
**白名单来源优先级**:Team/Enterprise 组织可以设置 `allowedChannelPlugins`,此时它替代 GrowthBook ledger 白名单(管理员拥有信任决策权)。
|
||||||
|
|
||||||
|
对于 server 类型,白名单始终失败(schema 是 `{marketplace, plugin}` 格式,server 类型无法匹配),除非使用 `--dangerously-load-development-channels`(设置 `entry.dev = true` 旁路白名单)。
|
||||||
|
|
||||||
|
### Gate 结果类型
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type ChannelGateResult =
|
||||||
|
| { action: 'register' } // 通过所有检查,注册通知处理器
|
||||||
|
| { action: 'skip'; kind: string; reason: string } // 某层拦截
|
||||||
|
|
||||||
|
// kind 枚举:capability | disabled | auth | policy | session | marketplace | allowlist
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、权限中继系统
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 5.1 为什么需要权限中继
|
||||||
|
|
||||||
|
当 Claude Code 需要执行敏感操作(如运行 Bash 命令),会弹出权限确认对话框。但如果用户通过 Telegram 远程控制 Agent,他看不到本地终端的对话框。
|
||||||
|
|
||||||
|
权限中继系统解决了这个问题:**将权限提示转发到 IM 平台,让用户在手机上也能审批或拒绝操作**。
|
||||||
|
|
||||||
|
### 5.2 出站:CC → Channel(权限请求)
|
||||||
|
|
||||||
|
当 Agent 触发权限对话框且 Channel 声明了 `experimental['claude/channel/permission']` 能力时:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 通知 Schema
|
||||||
|
const CHANNEL_PERMISSION_REQUEST_METHOD =
|
||||||
|
'notifications/claude/channel/permission_request'
|
||||||
|
|
||||||
|
type ChannelPermissionRequestParams = {
|
||||||
|
request_id: string // 5 字母标识符(如 "tbxkq")
|
||||||
|
tool_name: string // 工具名(如 "Bash")
|
||||||
|
description: string // 人类可读描述
|
||||||
|
input_preview: string // JSON 输入预览,截断到 200 字符
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Channel Server 收到后,按各平台格式化(Telegram markdown、Discord embed 等)发送给用户。
|
||||||
|
|
||||||
|
### 5.3 Short Request ID 生成
|
||||||
|
|
||||||
|
5 字母标识符的设计充满巧思:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// channelPermissions.ts:140-152
|
||||||
|
function shortRequestId(toolUseID: string): string {
|
||||||
|
let candidate = hashToId(toolUseID)
|
||||||
|
for (let salt = 0; salt < 10; salt++) {
|
||||||
|
if (!ID_AVOID_SUBSTRINGS.some(bad => candidate.includes(bad))) {
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
candidate = hashToId(`${toolUseID}:${salt}`)
|
||||||
|
}
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**设计决策**:
|
||||||
|
- **25 字母表**:a-z 去掉 `l`(与 1/I 混淆),25^5 约 980 万种组合
|
||||||
|
- **FNV-1a 哈希**:不是加密级别,但足够稳定且快速
|
||||||
|
- **脏话过滤**:5 个随机字母可能拼出不雅词汇(如发短信给老板的场景),内置屏蔽词表
|
||||||
|
- **纯字母**:手机用户不需要切换键盘模式(十六进制需要在字母和数字间切换)
|
||||||
|
- **大小写不敏感**:适配手机自动更正
|
||||||
|
|
||||||
|
### 5.4 入站:Channel → CC(权限响应)
|
||||||
|
|
||||||
|
用户在 IM 中回复格式:`yes tbxkq` 或 `no tbxkq`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 服务端解析正则
|
||||||
|
const PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i
|
||||||
|
|
||||||
|
// 结构化通知(服务端解析后发出,而非 CC 端正则匹配文本)
|
||||||
|
const ChannelPermissionNotificationSchema = z.object({
|
||||||
|
method: z.literal('notifications/claude/channel/permission'),
|
||||||
|
params: z.object({
|
||||||
|
request_id: z.string(),
|
||||||
|
behavior: z.enum(['allow', 'deny']),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键设计**:Channel Server 负责解析用户回复并发出结构化事件,CC 端不做文本正则匹配。这意味着普通聊天中的文字永远不会意外触发权限审批。
|
||||||
|
|
||||||
|
### 5.5 多源竞争
|
||||||
|
|
||||||
|
权限响应来自四个来源,先到先得:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||||
|
│ 本地终端 │ │ Bridge │ │ Channels │ │ Hooks │
|
||||||
|
│ Local UI │ │ 远程控制 │ │ Telegram etc │ │ Permission │
|
||||||
|
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
|
||||||
|
│ │ │ │
|
||||||
|
└───────────────────┴───────────────────┴──────────────────┘
|
||||||
|
│
|
||||||
|
claim() — 先到先得
|
||||||
|
│
|
||||||
|
┌─────┴─────┐
|
||||||
|
│ resolve │
|
||||||
|
│ allow/deny│
|
||||||
|
└───────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
`createChannelPermissionCallbacks()` 使用闭包维护 pending Map(不在模块级别,也不在 AppState 中——函数引用在状态中会导致序列化问题),构造一次后存入 AppState。
|
||||||
|
|
||||||
|
### 5.6 过滤权限中继客户端
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// channelPermissions.ts:177-194
|
||||||
|
function filterPermissionRelayClients(clients, isInAllowlist) {
|
||||||
|
return clients.filter(c =>
|
||||||
|
c.type === 'connected' &&
|
||||||
|
isInAllowlist(c.name) &&
|
||||||
|
c.capabilities?.experimental?.['claude/channel'] !== undefined &&
|
||||||
|
c.capabilities?.experimental?.['claude/channel/permission'] !== undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**三个条件全部必须**:已连接 + 在白名单中 + 同时声明了两个能力(`claude/channel` 和 `claude/channel/permission`)。第二个能力是显式 opt-in——只推消息的 Channel 永远不会意外成为权限审批入口。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、UI 组件
|
||||||
|
|
||||||
|
### 6.1 终端消息渲染(UserChannelMessage)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// UserChannelMessage.tsx
|
||||||
|
// 解析 <channel> XML 标签并在终端中渲染
|
||||||
|
|
||||||
|
const CHANNEL_RE = new RegExp(
|
||||||
|
`<${CHANNEL_TAG}\\s+source="([^"]+)"([^>]*)>\\n?([\\s\\S]*?)\\n?</${CHANNEL_TAG}>`
|
||||||
|
)
|
||||||
|
|
||||||
|
// 插件服务器名称显示:plugin:slack-channel:slack → slack
|
||||||
|
function displayServerName(name: string): string {
|
||||||
|
const i = name.lastIndexOf(':')
|
||||||
|
return i === -1 ? name : name.slice(i + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const TRUNCATE_AT = 60 // 消息体截断长度
|
||||||
|
```
|
||||||
|
|
||||||
|
**渲染效果**:
|
||||||
|
```
|
||||||
|
◁ tg · alice: 帮我看看 main.ts 有什么问题
|
||||||
|
```
|
||||||
|
|
||||||
|
其中 `◁` 是 Channel 箭头符号(`CHANNEL_ARROW`),显示服务器叶子名称和可选的用户名。
|
||||||
|
|
||||||
|
### 6.2 状态通知(ChannelsNotice)
|
||||||
|
|
||||||
|
启动时显示 `--channels` 条目的状态,报告阻断原因:
|
||||||
|
|
||||||
|
| 阻断类型 | 含义 |
|
||||||
|
|----------|------|
|
||||||
|
| `disabled` | Channel 功能未启用(tengu_harbor 关闭) |
|
||||||
|
| `noAuth` | 未通过 OAuth 认证 |
|
||||||
|
| `policyBlocked` | 组织策略未启用 channels |
|
||||||
|
| `unmatched` | 未在 `--channels` 列表中匹配到 |
|
||||||
|
|
||||||
|
### 6.3 开发者确认对话框(DevChannelsDialog)
|
||||||
|
|
||||||
|
使用 `--dangerously-load-development-channels` 时显示的警告对话框:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─ WARNING: Loading development channels ──────────────────┐
|
||||||
|
│ │
|
||||||
|
│ --dangerously-load-development-channels is for local │
|
||||||
|
│ channel development only. Do not use this option to │
|
||||||
|
│ run channels you have downloaded off the internet. │
|
||||||
|
│ │
|
||||||
|
│ Please use --channels to run a list of approved channels│
|
||||||
|
│ │
|
||||||
|
│ Channels: plugin:my-channel@local │
|
||||||
|
│ │
|
||||||
|
│ > I am using this for local development │
|
||||||
|
│ Exit │
|
||||||
|
└──────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
选择 "Exit" 会调用 `gracefulShutdownSync(1)` 立即退出。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、插件 Channel 架构
|
||||||
|
|
||||||
|
### 7.1 Plugin Manifest 中的 Channel 声明
|
||||||
|
|
||||||
|
插件通过 `plugin.json` 中的 `channels` 数组声明 Channel:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// schemas.ts:670-703
|
||||||
|
const PluginManifestChannelsSchema = z.object({
|
||||||
|
channels: z.array(z.object({
|
||||||
|
server: z.string().min(1), // MCP 服务器名,必须匹配 mcpServers 中的 key
|
||||||
|
displayName: z.string().optional(), // 配置对话框标题(如 "Telegram")
|
||||||
|
userConfig: z.record( // 安装时提示用户配置的字段
|
||||||
|
z.string(),
|
||||||
|
PluginUserConfigOptionSchema()
|
||||||
|
).optional(),
|
||||||
|
}).strict()),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例 plugin.json**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "telegram",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"mcpServers": {
|
||||||
|
"tg": {
|
||||||
|
"command": "node",
|
||||||
|
"args": ["./server.js"],
|
||||||
|
"env": {
|
||||||
|
"BOT_TOKEN": "${user_config.bot_token}",
|
||||||
|
"OWNER_ID": "${user_config.owner_id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels": [
|
||||||
|
{
|
||||||
|
"server": "tg",
|
||||||
|
"displayName": "Telegram",
|
||||||
|
"userConfig": {
|
||||||
|
"bot_token": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Telegram Bot API Token",
|
||||||
|
"required": true,
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
"owner_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Your Telegram User ID",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 配置流(PluginOptionsFlow)
|
||||||
|
|
||||||
|
启用插件后,如果 Channel 有未配置的 `userConfig` 字段:
|
||||||
|
|
||||||
|
1. `getUnconfiguredChannels()` 检测未满足验证的字段
|
||||||
|
2. `PluginOptionsFlow` 组件逐个提示用户输入
|
||||||
|
3. 敏感值(如 bot_token)存入 Keychain
|
||||||
|
4. 普通值存入 `~/.claude/plugins/options/{pluginId}.json`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// mcpPluginIntegration.ts:290-318
|
||||||
|
function getUnconfiguredChannels(plugin: LoadedPlugin): UnconfiguredChannel[] {
|
||||||
|
const channels = plugin.manifest.channels
|
||||||
|
if (!channels || channels.length === 0) return []
|
||||||
|
|
||||||
|
const unconfigured: UnconfiguredChannel[] = []
|
||||||
|
for (const channel of channels) {
|
||||||
|
if (!channel.userConfig) continue
|
||||||
|
const saved = loadMcpServerUserConfig(pluginId, channel.server) ?? {}
|
||||||
|
const validation = validateUserConfig(saved, channel.userConfig)
|
||||||
|
if (!validation.valid) {
|
||||||
|
unconfigured.push({
|
||||||
|
server: channel.server,
|
||||||
|
displayName: channel.displayName ?? channel.server,
|
||||||
|
configSchema: channel.userConfig,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unconfigured
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 作用域命名(Scoped Naming)
|
||||||
|
|
||||||
|
插件提供的 MCP Server 会被添加作用域前缀以避免冲突:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// mcpPluginIntegration.ts:341-360
|
||||||
|
function addPluginScopeToServers(
|
||||||
|
servers: Record<string, McpServerConfig>,
|
||||||
|
pluginName: string,
|
||||||
|
pluginSource: string, // e.g., "telegram@anthropic"
|
||||||
|
): Record<string, ScopedMcpServerConfig> {
|
||||||
|
const scopedServers = {}
|
||||||
|
for (const [name, config] of Object.entries(servers)) {
|
||||||
|
const scopedName = `plugin:${pluginName}:${name}`
|
||||||
|
scopedServers[scopedName] = {
|
||||||
|
...config,
|
||||||
|
scope: 'dynamic',
|
||||||
|
pluginSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scopedServers
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**命名转换**:
|
||||||
|
- 输入:`{ "tg": { ... } }` from `telegram@anthropic`
|
||||||
|
- 输出:`{ "plugin:telegram:tg": { scope: 'dynamic', pluginSource: 'telegram@anthropic', ... } }`
|
||||||
|
|
||||||
|
`pluginSource` 被保存在配置上,后续 Channel Gate 的 marketplace 验证步骤会用到它。
|
||||||
|
|
||||||
|
### 7.4 白名单有效来源
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// channelNotification.ts:127-138
|
||||||
|
function getEffectiveChannelAllowlist(sub, orgList) {
|
||||||
|
// Team/Enterprise 组织自定义白名单 → 替代 GrowthBook ledger
|
||||||
|
if ((sub === 'team' || sub === 'enterprise') && orgList) {
|
||||||
|
return { entries: orgList, source: 'org' }
|
||||||
|
}
|
||||||
|
// 默认使用 GrowthBook ledger
|
||||||
|
return { entries: getChannelAllowlist(), source: 'ledger' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
组织管理员可以通过 `allowedChannelPlugins` 完全控制哪些 Channel 插件被信任,而不依赖全局 ledger。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、安全设计
|
||||||
|
|
||||||
|
### 8.1 XML 注入防护
|
||||||
|
|
||||||
|
Channel 消息中的元数据会成为 XML 属性。两道防线:
|
||||||
|
|
||||||
|
1. **键名过滤**:`SAFE_META_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/` 只允许纯标识符
|
||||||
|
2. **值转义**:`escapeXmlAttr()` 对属性值进行 XML 转义
|
||||||
|
|
||||||
|
### 8.2 Marketplace 验证
|
||||||
|
|
||||||
|
`--channels plugin:slack@anthropic` 只是用户的"意图声明"。运行时名称 `plugin:slack:X` 可能来自 `slack@anthropic` 或 `slack@evil`。Gate 验证两者必须匹配:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const actual = pluginSource
|
||||||
|
? parsePluginIdentifier(pluginSource).marketplace
|
||||||
|
: undefined
|
||||||
|
if (actual !== entry.marketplace) {
|
||||||
|
return { action: 'skip', kind: 'marketplace', reason: '...' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 权限中继的信任边界
|
||||||
|
|
||||||
|
来自代码注释中 Kenneth 的分析(PR 讨论 #2956440848):
|
||||||
|
|
||||||
|
> "Claude 会自我审批吗?" 答:审批方是通过 Channel 的人类,不是 Claude。但信任边界不是终端——而是白名单(tengu_harbor_ledger)。一个被妥协的 Channel Server 可以在人类没看到提示的情况下伪造 "yes \<id\>"。这是被接受的风险:一个已妥协的 Channel 本来就有无限的对话注入轮次(可以社会工程攻击、等待 acceptEdits 模式等);注入后自动审批更快,但能力上并不更强。权限对话框减缓了已妥协 Channel 的攻击速度,但不能完全阻止。
|
||||||
|
|
||||||
|
### 8.4 skipSlashCommands
|
||||||
|
|
||||||
|
Channel 消息入队时设置 `skipSlashCommands: true`,确保 IM 用户发送的 `/help` 等文本不会被解释为 Claude Code 的斜杠命令。
|
||||||
|
|
||||||
|
### 8.5 dev 旁路的粒度
|
||||||
|
|
||||||
|
`--dangerously-load-development-channels` 的 `dev` 标记是**每个条目**级别的,不是全局的。接受开发对话框后,只有显式标记为 dev 的条目才旁路白名单,`--channels` 中的正常条目仍然需要通过完整的白名单检查。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、命令行接口
|
||||||
|
|
||||||
|
### 9.1 启动参数
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用已审批的 Channel 插件
|
||||||
|
claude --channels plugin:telegram@anthropic plugin:feishu@anthropic
|
||||||
|
|
||||||
|
# 本地开发模式(旁路白名单)
|
||||||
|
claude --dangerously-load-development-channels plugin:my-channel@local
|
||||||
|
|
||||||
|
# 两者可以同时使用
|
||||||
|
claude --channels plugin:telegram@anthropic \
|
||||||
|
--dangerously-load-development-channels plugin:dev-channel@local
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 参数解析
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// main.tsx
|
||||||
|
const parseChannelEntries = (raw: string[], flag: string): ChannelEntry[] => {
|
||||||
|
// 解析 "plugin:slack@anthropic"、"server:slack" 等格式
|
||||||
|
// 验证格式正确性
|
||||||
|
// 返回 ChannelEntry 数组
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存入全局状态
|
||||||
|
setAllowedChannels(channelEntries)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 特性门控
|
||||||
|
|
||||||
|
这两个 CLI 选项只在特性开关启用时可用:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// main.tsx:3850-3852
|
||||||
|
if (feature('KAIROS') || feature('KAIROS_CHANNELS')) {
|
||||||
|
program.addOption(new Option('--channels <servers...>', '...').hideHelp())
|
||||||
|
program.addOption(new Option('--dangerously-load-development-channels <servers...>', '...').hideHelp())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`hideHelp()` 表示这些选项不会出现在 `--help` 输出中——Channel 功能目前处于隐藏特性阶段。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、特性开关与分析
|
||||||
|
|
||||||
|
### 10.1 特性开关
|
||||||
|
|
||||||
|
| 开关 | 来源 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `KAIROS` / `KAIROS_CHANNELS` | 编译时 | 控制 CLI 参数注册和代码路径 |
|
||||||
|
| `tengu_harbor` | GrowthBook 运行时 | Channel 功能总开关(默认 false) |
|
||||||
|
| `tengu_harbor_ledger` | GrowthBook 运行时 | 审批插件白名单 |
|
||||||
|
| `tengu_harbor_permissions` | GrowthBook 运行时 | 权限中继功能开关 |
|
||||||
|
|
||||||
|
### 10.2 分析事件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Channel Gate 结果
|
||||||
|
tengu_mcp_channel_gate: {
|
||||||
|
gate_kind: 'disabled' | 'auth' | 'policy' | 'session' | 'marketplace' | 'allowlist'
|
||||||
|
plugin: string
|
||||||
|
is_dev: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel 消息
|
||||||
|
tengu_mcp_channel_message: {
|
||||||
|
content_length: number
|
||||||
|
meta_key_count: number
|
||||||
|
entry_kind: 'plugin' | 'server'
|
||||||
|
is_dev: boolean
|
||||||
|
plugin: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动标志
|
||||||
|
tengu_mcp_channel_flags: {
|
||||||
|
channels_count: number
|
||||||
|
dev_count: number
|
||||||
|
plugins: string[]
|
||||||
|
dev_plugins: string[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十一、总结
|
||||||
|
|
||||||
|
Channel 系统是 Claude Code 的 **IM 集成框架**,它的设计体现了几个核心原则:
|
||||||
|
|
||||||
|
### 1. 安全优先
|
||||||
|
|
||||||
|
六层访问控制门确保只有经过审批、用户明确选择的 Channel 才能推送消息。从编译时特性开关到运行时白名单,每一层都可以独立中断。
|
||||||
|
|
||||||
|
### 2. 协议驱动
|
||||||
|
|
||||||
|
Channel 不是特殊代码——它们就是普通的 MCP Server,只是多了一个通知协议。这意味着任何能实现 MCP 的语言都可以编写 Channel 插件。
|
||||||
|
|
||||||
|
### 3. 松耦合
|
||||||
|
|
||||||
|
Channel 失败不会阻断本地工作流。权限中继是多源竞争机制,任一来源的响应都有效。
|
||||||
|
|
||||||
|
### 4. 渐进式信任
|
||||||
|
|
||||||
|
从全局开关 → 认证 → 组织策略 → 会话白名单 → Marketplace 验证 → 白名单,信任级别逐级递增,每一步都有明确的安全目的。
|
||||||
|
|
||||||
|
### 5. 插件友好
|
||||||
|
|
||||||
|
通过 `plugin.json` 的声明式 Channel 配置、自动的用户配置提示流和作用域命名,第三方开发者可以轻松构建自己的 Channel 插件。
|
||||||
|
|
||||||
|
### 源码文件索引
|
||||||
|
|
||||||
|
| 文件 | 行数 | 职责 |
|
||||||
|
|------|------|------|
|
||||||
|
| `src/services/mcp/channelNotification.ts` | ~320 | 门控、消息封装、白名单集成 |
|
||||||
|
| `src/services/mcp/channelPermissions.ts` | ~240 | 权限中继、请求 ID 生成 |
|
||||||
|
| `src/services/mcp/channelAllowlist.ts` | ~80 | GrowthBook 白名单查询 |
|
||||||
|
| `src/services/mcp/useManageMCPConnections.ts` | - | 连接管理、通知处理器注册 |
|
||||||
|
| `src/components/messages/UserChannelMessage.tsx` | ~140 | 终端渲染 Channel 消息 |
|
||||||
|
| `src/components/DevChannelsDialog.tsx` | ~105 | 开发模式确认对话框 |
|
||||||
|
| `src/components/LogoV2/ChannelsNotice.tsx` | - | 启动时状态通知 |
|
||||||
|
| `src/utils/plugins/mcpPluginIntegration.ts` | - | 插件 MCP 集成、作用域命名 |
|
||||||
|
| `src/utils/plugins/schemas.ts` | ~700 | 插件清单 Schema(含 Channel 声明) |
|
||||||
|
| `src/bootstrap/state.ts` | - | 全局 Channel 白名单状态 |
|
||||||
|
| `src/main.tsx` | ~3850 | CLI 参数注册和解析 |
|
||||||
|
After Width: | Height: | Size: 590 KiB |
|
After Width: | Height: | Size: 411 KiB |
|
After Width: | Height: | Size: 547 KiB |
|
After Width: | Height: | Size: 658 KiB |
@@ -0,0 +1,83 @@
|
|||||||
|
# Claude Code Channel 系统文档
|
||||||
|
|
||||||
|
> 通过 IM 平台远程控制 Claude Code Agent 的完整技术解析
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文档目录
|
||||||
|
|
||||||
|
### [01-channel-system.md](./01-channel-system.md) — Channel 系统架构解析
|
||||||
|
|
||||||
|
从源码视角深度剖析 Claude Code Channel 系统的设计与实现,涵盖:
|
||||||
|
|
||||||
|
- **什么是 Channel**:IM 集成的核心概念、MCP 协议基础
|
||||||
|
- **整体架构**:消息流转全链路、组件关系
|
||||||
|
- **消息协议**:入站通知、XML 封装、出站工具调用
|
||||||
|
- **六层访问控制**:能力声明 → 运行时开关 → OAuth 认证 → 组织策略 → 会话白名单 → 插件审批
|
||||||
|
- **权限中继系统**:远程审批工具执行、5 字母请求 ID、多源竞争
|
||||||
|
- **插件架构**:Channel 插件清单声明、用户配置流、作用域命名
|
||||||
|
- **UI 组件**:终端消息渲染、状态通知、开发者警告对话框
|
||||||
|
- **安全设计**:防 XML 注入、marketplace 验证、信任边界分析
|
||||||
|
|
||||||
|
**适合人群**:想了解 AI Agent IM 集成架构的开发者、架构师、插件作者
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 配图说明
|
||||||
|
|
||||||
|
所有配图采用深色背景(#1a1a2e)+ Anthropic 品牌橙铜色(#D97757)风格。
|
||||||
|
|
||||||
|
| 图片 | 说明 | 所属文档 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `01-channel-overview.png` | Channel 系统架构总览 — 组件关系全景 | 架构解析 |
|
||||||
|
| `02-message-flow.png` | 消息流转全链路 — IM → Agent → IM | 架构解析 |
|
||||||
|
| `03-access-control.png` | 六层访问控制 — 层层递进的安全门 | 架构解析 |
|
||||||
|
| `04-permission-relay.png` | 权限中继系统 — 远程审批流程 | 架构解析 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 用户
|
||||||
|
|
||||||
|
1. 阅读 [Channel 系统架构解析](./01-channel-system.md)
|
||||||
|
2. 了解如何通过 `--channels` 启动 IM 集成
|
||||||
|
3. 理解不同平台(Telegram、Feishu、Discord)的接入方式
|
||||||
|
|
||||||
|
### 插件开发者
|
||||||
|
|
||||||
|
1. 阅读架构解析中的 [插件架构](./01-channel-system.md#七插件-channel-架构) 章节
|
||||||
|
2. 了解 `plugin.json` 中 Channel 声明格式
|
||||||
|
3. 实现 MCP Server 的 `notifications/claude/channel` 协议
|
||||||
|
4. 查看源码位置:
|
||||||
|
- `src/services/mcp/channelNotification.ts` — 核心门控与消息封装
|
||||||
|
- `src/services/mcp/channelPermissions.ts` — 权限中继系统
|
||||||
|
- `src/services/mcp/channelAllowlist.ts` — 白名单管理
|
||||||
|
- `src/utils/plugins/mcpPluginIntegration.ts` — 插件 MCP 集成
|
||||||
|
- `src/utils/plugins/schemas.ts` — 插件清单 Channel 声明 Schema
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心概念速查
|
||||||
|
|
||||||
|
| 概念 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **Channel** | 一个声明了 `claude/channel` 能力的 MCP Server,可推送 IM 消息到 Agent |
|
||||||
|
| **Channel Entry** | `--channels` 参数解析后的条目,分 plugin 和 server 两种 |
|
||||||
|
| **Channel Gate** | 六层访问控制门,决定是否注册通知处理器 |
|
||||||
|
| **Permission Relay** | 将工具执行审批提示转发到 IM 平台的机制 |
|
||||||
|
| **Channel Plugin** | 在 `plugin.json` 中声明 `channels` 字段的插件 |
|
||||||
|
| **Scoped Name** | 插件服务器的作用域名称,格式 `plugin:{pluginName}:{serverName}` |
|
||||||
|
| **Short Request ID** | 5 字母权限请求标识符,基于 FNV-1a 哈希生成 |
|
||||||
|
| **Channel Tag** | `<channel>` XML 标签,封装来自 IM 的消息内容和元数据 |
|
||||||
|
| **Dev Channels** | 通过 `--dangerously-load-development-channels` 加载的本地开发频道 |
|
||||||
|
| **tengu_harbor** | GrowthBook 运行时特性开关,控制 Channel 功能总开关 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 相关资源
|
||||||
|
|
||||||
|
- [Claude Code Haha 主页](/)
|
||||||
|
- [Agent 框架解析](/agent/03-agent-framework)
|
||||||
|
- [Skills 系统文档](/skills/01-usage-guide)
|
||||||
|
- [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues)
|
||||||
@@ -0,0 +1,443 @@
|
|||||||
|
# Claude Code Multi-Agent System — Usage Guide
|
||||||
|
|
||||||
|
> Let Claude Code orchestrate multiple specialized agents to handle complex tasks in parallel.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#1-what-is-the-multi-agent-system">Multi-Agent System</a> · <a href="#2-six-built-in-agents">Six Built-in Agents</a> · <a href="#3-how-to-spawn-agents">Spawning Agents</a> · <a href="#4-background-task-management">Background Tasks</a> · <a href="#5-agent-teams--multi-agent-collaboration">Agent Teams</a> · <a href="#6-custom-agents">Custom Agents</a> · <a href="#7-permission-modes">Permission Modes</a> · <a href="#8-quick-reference">Quick Reference</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. What Is the Multi-Agent System?
|
||||||
|
|
||||||
|
Claude Code's multi-agent system is an **intelligent task orchestration framework** that enables the primary agent to spawn multiple specialized subagents, each executing different tasks independently, then aggregating results for the user.
|
||||||
|
|
||||||
|
Core philosophy: **Break large tasks into specialized subtasks, execute them in parallel, and boost efficiency.**
|
||||||
|
|
||||||
|
| Scenario | Traditional Approach | Multi-Agent Approach |
|
||||||
|
|----------|---------------------|---------------------|
|
||||||
|
| Research 5 module architectures | Explore them one by one | 5 Explore agents scan in parallel |
|
||||||
|
| Implement + Test + Document | Complete sequentially | Team members each handle one part |
|
||||||
|
| Code review | Single-threaded, file by file | Multiple reviewers in parallel |
|
||||||
|
| Debug a complex bug | Try one hypothesis at a time | Multiple debuggers verify in parallel |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Six Built-in Agents
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Claude Code ships with 6 specialized agent types, each with a specific tool pool and intended use case:
|
||||||
|
|
||||||
|
### 2.1 general-purpose (General Agent)
|
||||||
|
|
||||||
|
**Use case**: Complex multi-step research, code search, tasks requiring full tool access.
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "Research auth module",
|
||||||
|
prompt: "Analyze all files under src/auth/ for the authentication flow...",
|
||||||
|
subagent_type: "general-purpose"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Tool pool**: All tools (`*`)
|
||||||
|
- **Model**: Inherited from parent
|
||||||
|
- **Characteristics**: The all-rounder — choose this when you are unsure which agent type to use
|
||||||
|
|
||||||
|
### 2.2 Explore (Exploration Agent)
|
||||||
|
|
||||||
|
**Use case**: Quickly search files, find code patterns, answer questions about codebase structure.
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "Search API endpoints",
|
||||||
|
prompt: "Find all REST API endpoint definitions...",
|
||||||
|
subagent_type: "Explore"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Tool pool**: Read-only tools (Glob, Grep, Read, Bash)
|
||||||
|
- **Model**: Haiku (fast, low cost)
|
||||||
|
- **Characteristics**: Cannot modify files; fast; ideal for research
|
||||||
|
|
||||||
|
### 2.3 Plan (Planning Agent)
|
||||||
|
|
||||||
|
**Use case**: Design implementation plans, analyze architectural trade-offs, generate step-by-step plans.
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "Plan refactoring",
|
||||||
|
prompt: "Design a plan to split the monolith into microservices...",
|
||||||
|
subagent_type: "Plan"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Tool pool**: Read-only tools (same as Explore)
|
||||||
|
- **Model**: Inherited from parent (requires strong reasoning)
|
||||||
|
- **Characteristics**: Outputs structured plans including key files and dependency analysis
|
||||||
|
|
||||||
|
### 2.4 verification (Verification Agent)
|
||||||
|
|
||||||
|
**Use case**: Independently verify that an implementation is correct, run tests, perform boundary checks.
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "Verify login feature",
|
||||||
|
prompt: "Verify the newly implemented login feature works correctly...",
|
||||||
|
subagent_type: "verification"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Tool pool**: Read-only tools
|
||||||
|
- **Model**: Inherited from parent
|
||||||
|
- **Characteristics**: Always runs in the background; outputs PASS/FAIL/PARTIAL verdicts; displayed with a red badge
|
||||||
|
|
||||||
|
### 2.5 claude-code-guide (Guide Agent)
|
||||||
|
|
||||||
|
**Use case**: Answer questions about Claude Code, Agent SDK, or the Claude API.
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "Query Claude API usage",
|
||||||
|
prompt: "How do I use the tool_use feature...",
|
||||||
|
subagent_type: "claude-code-guide"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Tool pool**: Bash, Read, WebFetch, WebSearch
|
||||||
|
- **Model**: Haiku
|
||||||
|
- **Characteristics**: Focused on documentation queries; uses the dontAsk permission mode
|
||||||
|
|
||||||
|
### 2.6 statusline-setup (Status Bar Configuration Agent)
|
||||||
|
|
||||||
|
**Use case**: Configure the Claude Code status bar display.
|
||||||
|
|
||||||
|
- **Tool pool**: Read + Edit only
|
||||||
|
- **Model**: Sonnet
|
||||||
|
- **Characteristics**: Highly specialized with an extremely narrow scope
|
||||||
|
|
||||||
|
### Agent Type Comparison
|
||||||
|
|
||||||
|
| Agent | Access | Tool Pool | Model | Purpose |
|
||||||
|
|-------|--------|-----------|-------|---------|
|
||||||
|
| general-purpose | Read/Write | All | Inherited | General tasks |
|
||||||
|
| Explore | Read-only | Search + Read | Haiku | Quick exploration |
|
||||||
|
| Plan | Read-only | Search + Read | Inherited | Architecture planning |
|
||||||
|
| verification | Read-only | Search + Read | Inherited | Independent verification |
|
||||||
|
| claude-code-guide | Read-only | Search + Web | Haiku | Documentation guide |
|
||||||
|
| statusline-setup | Read/Write | Read + Edit | Sonnet | Status bar config |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. How to Spawn Agents
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
The Agent tool accepts the following parameters:
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| `description` | string | Yes | 3-5 word task summary |
|
||||||
|
| `prompt` | string | Yes | Full task description |
|
||||||
|
| `subagent_type` | string | No | Agent type (see table above) |
|
||||||
|
| `model` | string | No | Model override: sonnet/opus/haiku |
|
||||||
|
| `run_in_background` | boolean | No | Whether to run in background |
|
||||||
|
| `name` | string | No | Name the agent so it can be addressed via SendMessage |
|
||||||
|
| `team_name` | string | No | Join a specified team |
|
||||||
|
| `mode` | string | No | Permission mode |
|
||||||
|
| `isolation` | string | No | Isolation mode: worktree |
|
||||||
|
|
||||||
|
### Foreground Synchronous Execution (Default)
|
||||||
|
|
||||||
|
The simplest usage — the agent completes and returns its result:
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "Analyze error logs",
|
||||||
|
prompt: "Read the latest error logs under logs/ and summarize common error patterns"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
The primary agent waits for the subagent to finish, then receives the result and continues working.
|
||||||
|
|
||||||
|
### Background Asynchronous Execution
|
||||||
|
|
||||||
|
Suitable for time-consuming tasks where the primary agent can continue with other work:
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "Full code review",
|
||||||
|
prompt: "Review all TypeScript files under src/ for code quality...",
|
||||||
|
run_in_background: true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- The agent immediately returns an `async_launched` status with a taskId
|
||||||
|
- The primary agent continues working without waiting
|
||||||
|
- When the agent completes, a `<task-notification>` is delivered automatically
|
||||||
|
- The notification includes task status, output file path, and a result summary
|
||||||
|
|
||||||
|
### Spawning Multiple Agents in Parallel
|
||||||
|
|
||||||
|
Spawn multiple independent agents in a single message for true parallelism:
|
||||||
|
|
||||||
|
```
|
||||||
|
// Launch 3 explore agents simultaneously
|
||||||
|
Agent({ description: "Explore frontend", prompt: "...", subagent_type: "Explore", run_in_background: true })
|
||||||
|
Agent({ description: "Explore backend", prompt: "...", subagent_type: "Explore", run_in_background: true })
|
||||||
|
Agent({ description: "Explore database", prompt: "...", subagent_type: "Explore", run_in_background: true })
|
||||||
|
```
|
||||||
|
|
||||||
|
### Worktree Isolation
|
||||||
|
|
||||||
|
Let an agent work in an isolated git worktree without affecting the main workspace:
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "Experimental refactor",
|
||||||
|
prompt: "Try refactoring module X into...",
|
||||||
|
isolation: "worktree"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- Automatically creates a git worktree (on an independent branch)
|
||||||
|
- The agent can freely modify files in the isolated environment
|
||||||
|
- If changes were made, returns the worktree path and branch name on completion
|
||||||
|
- If no changes were made, cleans up automatically
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Background Task Management
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Task States
|
||||||
|
|
||||||
|
Background agents have four possible states:
|
||||||
|
|
||||||
|
| State | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `running` | Currently executing |
|
||||||
|
| `completed` | Finished successfully |
|
||||||
|
| `failed` | Execution failed |
|
||||||
|
| `killed` | Manually terminated |
|
||||||
|
|
||||||
|
### Progress Tracking
|
||||||
|
|
||||||
|
Background agent progress updates in real time:
|
||||||
|
|
||||||
|
- **Token usage**: Input/output token counts
|
||||||
|
- **Tool usage**: Number of tools invoked
|
||||||
|
- **Recent activity**: Descriptions of the last 5 tool calls (circular buffer)
|
||||||
|
- **Last activity time**: Used to detect stuck tasks
|
||||||
|
|
||||||
|
### Completion Notifications
|
||||||
|
|
||||||
|
When a background agent finishes, the primary agent receives an XML-formatted notification:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<task-notification>
|
||||||
|
<task-id>abc123</task-id>
|
||||||
|
<status>completed</status>
|
||||||
|
<summary>Agent "Explore frontend" completed</summary>
|
||||||
|
<output-file>~/.claude/temp/.../tasks/abc123.output</output-file>
|
||||||
|
</task-notification>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automatic Backgrounding
|
||||||
|
|
||||||
|
When the `tengu_auto_background_agents` feature flag is enabled, foreground agents that run for more than **120 seconds** are automatically moved to background execution, freeing the primary agent to continue working.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Agent Teams — Multi-Agent Collaboration
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Agent Teams is an advanced multi-agent collaboration mode where multiple agents work as a team, coordinating tasks through message-based communication.
|
||||||
|
|
||||||
|
### Creating a Team
|
||||||
|
|
||||||
|
```
|
||||||
|
TeamCreate({
|
||||||
|
team_name: "feature-team",
|
||||||
|
description: "Develop user authentication feature"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
After team creation:
|
||||||
|
- A team configuration file is generated: `~/.claude/teams/{team_name}/config.json`
|
||||||
|
- A shared task directory is created: `~/.claude/tasks/{team_name}/`
|
||||||
|
- The current agent automatically becomes the **Team Lead**
|
||||||
|
|
||||||
|
### Adding Team Members
|
||||||
|
|
||||||
|
Spawn teammates by specifying `name` and `team_name` in the Agent tool:
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent({
|
||||||
|
description: "Frontend development",
|
||||||
|
prompt: "Implement the login page React components...",
|
||||||
|
name: "frontend-dev",
|
||||||
|
team_name: "feature-team"
|
||||||
|
})
|
||||||
|
|
||||||
|
Agent({
|
||||||
|
description: "Backend development",
|
||||||
|
prompt: "Implement the authentication API endpoints...",
|
||||||
|
name: "backend-dev",
|
||||||
|
team_name: "feature-team"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Teammate Communication
|
||||||
|
|
||||||
|
Send messages using the SendMessage tool:
|
||||||
|
|
||||||
|
```
|
||||||
|
// Send to a specific teammate
|
||||||
|
SendMessage({
|
||||||
|
to: "frontend-dev",
|
||||||
|
message: "API interface is ready, the format is...",
|
||||||
|
summary: "Notify API interface format"
|
||||||
|
})
|
||||||
|
|
||||||
|
// Broadcast to all teammates
|
||||||
|
SendMessage({
|
||||||
|
to: "*",
|
||||||
|
message: "Everyone pause, requirements have changed...",
|
||||||
|
summary: "Broadcast requirements change"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Shutdown Coordination
|
||||||
|
|
||||||
|
When the task is complete, the Team Lead requests teammates to shut down:
|
||||||
|
|
||||||
|
```
|
||||||
|
// 1. Send shutdown request
|
||||||
|
SendMessage({
|
||||||
|
to: "frontend-dev",
|
||||||
|
message: { type: "shutdown_request", reason: "Task completed" }
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. Teammate responds with approval
|
||||||
|
SendMessage({
|
||||||
|
to: "team-lead",
|
||||||
|
message: { type: "shutdown_response", request_id: "...", approve: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. After all teammates shut down, clean up the team
|
||||||
|
TeamDelete()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Execution Backends
|
||||||
|
|
||||||
|
Agent Teams supports two execution backends:
|
||||||
|
|
||||||
|
| Backend | Description | Use Case |
|
||||||
|
|---------|-------------|----------|
|
||||||
|
| **in-process** | Runs in the same process, isolated via AsyncLocalStorage | Default mode; lightweight and efficient |
|
||||||
|
| **tmux** | Runs in a separate tmux pane | When an independent terminal view is needed |
|
||||||
|
| **iTerm2** | Runs in a separate iTerm2 window | For macOS iTerm2 users |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Custom Agents
|
||||||
|
|
||||||
|
In addition to built-in agents, you can create your own specialized agents.
|
||||||
|
|
||||||
|
### Definition Format
|
||||||
|
|
||||||
|
Create a `.md` file in the `.claude/agents/` directory:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: code-reviewer
|
||||||
|
description: Professional code review agent
|
||||||
|
tools:
|
||||||
|
- Read
|
||||||
|
- Grep
|
||||||
|
- Glob
|
||||||
|
- Bash
|
||||||
|
model: sonnet
|
||||||
|
permissionMode: dontAsk
|
||||||
|
maxTurns: 10
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a professional code reviewer. Check the following aspects:
|
||||||
|
|
||||||
|
1. Code quality and readability
|
||||||
|
2. Potential security vulnerabilities
|
||||||
|
3. Performance issues
|
||||||
|
4. Adherence to best practices
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configurable Fields
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `name` | string | Agent type name |
|
||||||
|
| `description` | string | Description of when to use this agent |
|
||||||
|
| `tools` | string[] | Allowed tool list (`['*']` for all) |
|
||||||
|
| `disallowedTools` | string[] | Disallowed tool list |
|
||||||
|
| `model` | string | Model to use (sonnet/opus/haiku/inherit) |
|
||||||
|
| `permissionMode` | string | Permission mode |
|
||||||
|
| `maxTurns` | number | Maximum conversation turns |
|
||||||
|
| `mcpServers` | object[] | Required MCP servers |
|
||||||
|
| `hooks` | object | Agent-specific hooks |
|
||||||
|
| `skills` | string[] | Available skills |
|
||||||
|
| `memory` | string | Memory scope (user/project/local) |
|
||||||
|
| `isolation` | string | Isolation mode (worktree/remote) |
|
||||||
|
| `background` | boolean | Whether to run in background by default |
|
||||||
|
|
||||||
|
### Loading Priority
|
||||||
|
|
||||||
|
Custom agents are loaded according to the following priority:
|
||||||
|
|
||||||
|
1. **Built-in agents** (built-in) — System predefined
|
||||||
|
2. **Plugin agents** (plugin) — Registered via plugins
|
||||||
|
3. **User agents** (user) — `~/.claude/agents/`
|
||||||
|
4. **Project agents** (project) — `.claude/agents/` (project-level)
|
||||||
|
5. **Flag agents** (flag) — Registered via API
|
||||||
|
6. **Policy agents** (policy) — Organization policies
|
||||||
|
|
||||||
|
Agents with the same name are overridden according to priority.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Permission Modes
|
||||||
|
|
||||||
|
Each agent can be configured with a different permission mode:
|
||||||
|
|
||||||
|
| Mode | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `default` | Normal permission requests requiring user confirmation |
|
||||||
|
| `plan` | All operations require explicit approval |
|
||||||
|
| `acceptEdits` | File edits are auto-approved; other operations require confirmation |
|
||||||
|
| `bypassPermissions` | Skip all permission checks |
|
||||||
|
| `dontAsk` | Reject all operations not pre-approved |
|
||||||
|
| `auto` | AI-driven permission classification (Anthropic internal only) |
|
||||||
|
| `bubble` | Permission prompts bubble up to the parent agent's terminal |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Quick Reference
|
||||||
|
|
||||||
|
| Action | Method |
|
||||||
|
|--------|--------|
|
||||||
|
| Spawn a subagent | `Agent({ prompt: "...", subagent_type: "Explore" })` |
|
||||||
|
| Run in background | `Agent({ ..., run_in_background: true })` |
|
||||||
|
| Spawn in parallel | Send multiple Agent calls in a single message |
|
||||||
|
| Worktree isolation | `Agent({ ..., isolation: "worktree" })` |
|
||||||
|
| Create a team | `TeamCreate({ team_name: "..." })` |
|
||||||
|
| Send a message | `SendMessage({ to: "name", message: "..." })` |
|
||||||
|
| Broadcast a message | `SendMessage({ to: "*", message: "..." })` |
|
||||||
|
| Request shutdown | `SendMessage({ to: "name", message: { type: "shutdown_request" } })` |
|
||||||
|
| Delete a team | `TeamDelete()` |
|
||||||
|
| Custom agent | Create a definition file in `.claude/agents/*.md` |
|
||||||
|
| Specify a model | `Agent({ ..., model: "haiku" })` |
|
||||||
|
| Name an agent | `Agent({ ..., name: "researcher" })` |
|
||||||
@@ -0,0 +1,856 @@
|
|||||||
|
# Claude Code Multi-Agent System — Implementation Details
|
||||||
|
|
||||||
|
> A deep dive into the architecture, spawn flow, context passing, and collaboration mechanisms of multi-agent orchestration.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#1-architecture-overview">Architecture</a> · <a href="#2-agent-spawn-flow--four-paths">Spawn Flow</a> · <a href="#3-tool-pool-system--three-layer-filtering">Tool Pool</a> · <a href="#4-context-passing-mechanism">Context Passing</a> · <a href="#5-agent-teams-internals">Teams Internals</a> · <a href="#6-background-task-engine">Task Engine</a> · <a href="#7-dreamtask--automatic-memory-consolidation">DreamTask</a> · <a href="#8-worktree-isolation-implementation">Worktree Isolation</a> · <a href="#9-permission-synchronization">Permission Sync</a> · <a href="#10-agent-lifecycle-end-to-end-data-flow">Lifecycle Data Flow</a> · <a href="#11-key-source-file-index">Source Index</a> · <a href="#12-feature-flags">Feature Flags</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Architecture Overview
|
||||||
|
|
||||||
|
Claude Code's multi-agent system consists of the following core modules:
|
||||||
|
|
||||||
|
### 5 Core Modules
|
||||||
|
|
||||||
|
| Module | Responsibility | Key Files |
|
||||||
|
|--------|---------------|-----------|
|
||||||
|
| **Agent Tool** | Primary entry point, routing & dispatch, parameter parsing | `src/tools/AgentTool/AgentTool.tsx` |
|
||||||
|
| **Execution Engine** | Agent lifecycle management, query loop | `src/tools/AgentTool/runAgent.ts` |
|
||||||
|
| **Context Manager** | System prompt construction, cache-safe parameters | `src/utils/forkedAgent.ts` |
|
||||||
|
| **Task System** | State tracking, progress updates, notification queue | `src/tasks/LocalAgentTask/` |
|
||||||
|
| **Swarm Infrastructure** | Team management, mailbox communication, permission sync | `src/utils/swarm/` |
|
||||||
|
|
||||||
|
### 5 Agent Categories
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Agent Tool │
|
||||||
|
│ (Entry & Route Dispatch) │
|
||||||
|
├───────────┬───────────┬───────────┬─────────────┤
|
||||||
|
│ Subagent │ Fork │ Teammate │ Remote │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ Standalone│ Inherited │ Team │ CCR │
|
||||||
|
│ context │ context │ collab │ environment │
|
||||||
|
│ Type-based│ Cache │ Mailbox │ Remote │
|
||||||
|
│ tool pool │ sharing │ comms │ execution │
|
||||||
|
│ │ Byte-level│ Permission│ Poll for │
|
||||||
|
│ │ consistent│ sync │ results │
|
||||||
|
└───────────┴───────────┴───────────┴─────────────┘
|
||||||
|
│
|
||||||
|
┌─────┴─────┐
|
||||||
|
│ DreamTask │
|
||||||
|
│ (Memory │
|
||||||
|
│ Consolidation) │
|
||||||
|
│ Scheduled │
|
||||||
|
│ background│
|
||||||
|
└───────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Agent Spawn Flow — Four Paths
|
||||||
|
|
||||||
|
### Entry Point: `AgentTool.call()`
|
||||||
|
|
||||||
|
The `call()` function in `src/tools/AgentTool/AgentTool.tsx` is the entry point for all agent spawning. Based on input parameters, it routes to one of four spawn paths:
|
||||||
|
|
||||||
|
```
|
||||||
|
AgentTool.call(input)
|
||||||
|
│
|
||||||
|
├─ team_name + name? ──────→ Path 1: spawnTeammate()
|
||||||
|
│
|
||||||
|
├─ run_in_background? ────→ Path 2: registerAsyncAgent()
|
||||||
|
│ └─ agent.background?
|
||||||
|
│
|
||||||
|
├─ subagent_type omitted? ─→ Path 3: Fork (buildForkedMessages())
|
||||||
|
│ └─ fork experiment on?
|
||||||
|
│
|
||||||
|
└─ default ────────────────→ Path 4: runAgent() synchronous execution
|
||||||
|
```
|
||||||
|
|
||||||
|
### Path 1: Teammate Spawn
|
||||||
|
|
||||||
|
**Trigger**: Both `team_name` and `name` are present
|
||||||
|
|
||||||
|
**Entry function**: `spawnTeammate()` — `src/tools/shared/spawnMultiAgent.ts`
|
||||||
|
|
||||||
|
**Flow**:
|
||||||
|
|
||||||
|
1. Detect execution backend (tmux / iTerm2 / in-process)
|
||||||
|
2. Generate a unique `agentId` for the teammate: `formatAgentId(name, teamName)`
|
||||||
|
3. Assign a color (from a predefined palette)
|
||||||
|
4. Create the execution environment:
|
||||||
|
- **in-process**: Start in the same process via `spawnInProcessTeammate()`
|
||||||
|
- **tmux**: Create a new pane via `TmuxBackend`
|
||||||
|
- **iTerm2**: Create a new window via `ITerm2Backend`
|
||||||
|
5. Write to the TeamFile member list
|
||||||
|
6. Return `TeammateSpawnedOutput` (including pane ID, agent ID, etc.)
|
||||||
|
|
||||||
|
**In-Process Teammate Isolation**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/swarm/spawnInProcess.ts
|
||||||
|
export async function spawnInProcessTeammate(config, context) {
|
||||||
|
// 1. Independent AbortController (not tied to leader's cancellation)
|
||||||
|
const abortController = new AbortController()
|
||||||
|
|
||||||
|
// 2. AsyncLocalStorage context isolation
|
||||||
|
runWithTeammateContext(teammateContext, async () => {
|
||||||
|
// 3. Independent task state
|
||||||
|
// 4. Independent message loop
|
||||||
|
// 5. Shared permission pipeline (via mailbox)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Path 2: Async Subagent
|
||||||
|
|
||||||
|
**Trigger**: `run_in_background=true` or `background: true` in the agent definition
|
||||||
|
|
||||||
|
**Flow**:
|
||||||
|
|
||||||
|
```
|
||||||
|
registerAsyncAgent()
|
||||||
|
│
|
||||||
|
├─ Create LocalAgentTask (status: 'running')
|
||||||
|
├─ Register in agentNameRegistry (if named)
|
||||||
|
├─ Create output file symlink
|
||||||
|
├─ Create AbortController (linked to parent)
|
||||||
|
├─ Emit SDK event: task_started
|
||||||
|
│
|
||||||
|
└─ void runAsyncAgentLifecycle() ← fire-and-forget async
|
||||||
|
│
|
||||||
|
├─ Create ProgressTracker
|
||||||
|
├─ Iterate makeStream() generator
|
||||||
|
│ ├─ Append messages to agentMessages[]
|
||||||
|
│ ├─ Update progress (tokens, tools, activities)
|
||||||
|
│ └─ Emit SDK progress events
|
||||||
|
│
|
||||||
|
└─ On completion:
|
||||||
|
├─ finalizeAgentTool() (extract result)
|
||||||
|
├─ completeAgentTask() (mark complete)
|
||||||
|
├─ Clean up worktree (if isolated)
|
||||||
|
└─ enqueuePendingNotification() (notify primary agent)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key implementation**: `src/tools/AgentTool/agentToolUtils.ts` — `runAsyncAgentLifecycle()`
|
||||||
|
|
||||||
|
### Path 3: Fork Subagent
|
||||||
|
|
||||||
|
**Trigger**: `subagent_type` is omitted and the fork experiment is enabled
|
||||||
|
|
||||||
|
**Core optimization**: Achieves **prompt cache hits** through byte-level consistent API request prefixes.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Flow**:
|
||||||
|
|
||||||
|
```
|
||||||
|
buildForkedMessages(directive, assistantMessage)
|
||||||
|
│
|
||||||
|
├─ Preserve parent agent's complete assistant message (all tool_use blocks)
|
||||||
|
├─ Build user message:
|
||||||
|
│ ├─ Create placeholder tool_result for each tool_use (byte-consistent)
|
||||||
|
│ └─ Append per-child directive (the only divergent part)
|
||||||
|
│
|
||||||
|
└─ Result: byte-level consistent API prefix → prompt cache hit!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fork child behavioral constraints** (injected via `FORK_BOILERPLATE_TAG`):
|
||||||
|
|
||||||
|
```
|
||||||
|
1. You are a forked worker process, not the primary agent
|
||||||
|
2. Do not converse, ask questions, or suggest next steps
|
||||||
|
3. Use tools directly (Bash, Read, Write, etc.)
|
||||||
|
4. If you modify files, commit changes before reporting
|
||||||
|
5. Do not output text between tool calls
|
||||||
|
6. Stay strictly within the scope of your directive
|
||||||
|
7. Keep your report under 500 words
|
||||||
|
8. Your response must begin with "Scope:"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Anti-recursion protection**: `isInForkChild()` detects whether execution is inside a fork child, preventing nested forks.
|
||||||
|
|
||||||
|
### Path 4: Synchronous Subagent
|
||||||
|
|
||||||
|
**Trigger**: Default path (no team_name, no background, not a fork)
|
||||||
|
|
||||||
|
**Flow**:
|
||||||
|
|
||||||
|
```
|
||||||
|
runAgent(promptMessages, toolUseContext, options)
|
||||||
|
│
|
||||||
|
├─ Resolve agent definition (getSystemPrompt, tools, permissions)
|
||||||
|
├─ Build system prompt (buildEffectiveSystemPrompt)
|
||||||
|
├─ Create isolated ToolUseContext (createSubagentContext)
|
||||||
|
├─ Start query loop (query() async generator)
|
||||||
|
│ ├─ Send API request
|
||||||
|
│ ├─ Process streaming events
|
||||||
|
│ ├─ Execute tool calls
|
||||||
|
│ └─ Accumulate messages and usage
|
||||||
|
│
|
||||||
|
└─ Return AgentToolResult
|
||||||
|
├─ content: text from the last assistant message
|
||||||
|
├─ totalToolUseCount
|
||||||
|
├─ totalDurationMs
|
||||||
|
└─ totalTokens
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Tool Pool System — Three-Layer Filtering
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Layer 1: Global Disallow List
|
||||||
|
|
||||||
|
`ALL_AGENT_DISALLOWED_TOOLS` — tools disallowed for all agents:
|
||||||
|
|
||||||
|
| Tool | Reason for Disallowing |
|
||||||
|
|------|----------------------|
|
||||||
|
| TaskOutput | Only the primary agent may read task output |
|
||||||
|
| ExitPlanMode | Only the primary agent may exit plan mode |
|
||||||
|
| EnterPlanMode | Only the primary agent may enter plan mode |
|
||||||
|
| AskUserQuestion | Subagents should not directly ask the user |
|
||||||
|
| TaskStop | Only the primary agent may terminate tasks |
|
||||||
|
| Agent | Prevent recursive spawning (Anthropic internal exception) |
|
||||||
|
|
||||||
|
### Layer 2: Agent Type Filtering
|
||||||
|
|
||||||
|
`filterToolsForAgent()` — tool filtering based on agent type:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/tools/AgentTool/agentToolUtils.ts
|
||||||
|
function filterToolsForAgent(tools, agentDef) {
|
||||||
|
// 1. Remove ALL_AGENT_DISALLOWED_TOOLS
|
||||||
|
// 2. If not a built-in agent, also remove CUSTOM_AGENT_DISALLOWED_TOOLS
|
||||||
|
// 3. If async agent, restrict to ASYNC_AGENT_ALLOWED_TOOLS
|
||||||
|
// 4. MCP tools are always allowed
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**ASYNC_AGENT_ALLOWED_TOOLS** (15 tools):
|
||||||
|
|
||||||
|
```
|
||||||
|
Read, WebSearch, TodoWrite, Grep, WebFetch, Glob,
|
||||||
|
Bash/PowerShell, FileEdit, FileWrite, NotebookEdit,
|
||||||
|
Skill, SyntheticOutput, ToolSearch, EnterWorktree, ExitWorktree
|
||||||
|
```
|
||||||
|
|
||||||
|
### Layer 3: Agent Definition Filtering
|
||||||
|
|
||||||
|
`resolveAgentTools()` — tool resolution based on agent definition:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function resolveAgentTools(agentDef, availableTools) {
|
||||||
|
if (tools === ['*'] || undefined) → wildcard, allow all
|
||||||
|
if (tools === ['Read', 'Grep']) → allow only listed tools
|
||||||
|
if (disallowedTools === ['Agent']) → subtract from available tools
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Filtering Pipeline**:
|
||||||
|
|
||||||
|
```
|
||||||
|
All available tools
|
||||||
|
│
|
||||||
|
├─ Subtract ALL_AGENT_DISALLOWED_TOOLS ──→ Global disallow
|
||||||
|
│
|
||||||
|
├─ Not built-in? Subtract CUSTOM_AGENT_DISALLOWED_TOOLS ──→ Custom restrictions
|
||||||
|
│
|
||||||
|
├─ Async? Restrict to ASYNC_AGENT_ALLOWED_TOOLS ──→ Async allowlist
|
||||||
|
│
|
||||||
|
├─ Has tools list? Intersect ──→ Agent allowlist
|
||||||
|
│
|
||||||
|
├─ Has disallowedTools? Subtract ──→ Agent denylist
|
||||||
|
│
|
||||||
|
└─ Final tool pool
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Context Passing Mechanism
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### CacheSafeParams — Cache-Safe Parameters
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/forkedAgent.ts
|
||||||
|
export type CacheSafeParams = {
|
||||||
|
systemPrompt: SystemPrompt // System prompt
|
||||||
|
userContext: { [k: string]: string } // Directory structure, CLAUDE.md, etc.
|
||||||
|
systemContext: { [k: string]: string } // Git status, environment info
|
||||||
|
toolUseContext: ToolUseContext // Tool configuration, model, options
|
||||||
|
forkContextMessages: Message[] // Fork context messages (for cache sharing)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cache Sharing Principle**:
|
||||||
|
|
||||||
|
Fork agents reuse prompt cache by keeping API request prefixes byte-level consistent:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ Shared Prefix (byte-consistent) │
|
||||||
|
│ ┌──────────────────────────────────┐ │
|
||||||
|
│ │ System Prompt │ │
|
||||||
|
│ │ User Context │ │
|
||||||
|
│ │ System Context │ │
|
||||||
|
│ │ Tool Use Context │ │
|
||||||
|
│ │ Conversation History Messages │ │
|
||||||
|
│ │ Assistant Message (all tool_use) │ │
|
||||||
|
│ │ User Message (placeholder results│) │
|
||||||
|
│ └──────────────────────────────────┘ │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ Only divergence: per-child directive │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### System Prompt Construction
|
||||||
|
|
||||||
|
`buildEffectiveSystemPrompt()` — `src/utils/systemPrompt.ts`
|
||||||
|
|
||||||
|
**Priority chain** (highest to lowest):
|
||||||
|
|
||||||
|
```
|
||||||
|
Override System Prompt ← Highest priority, full replacement
|
||||||
|
↓
|
||||||
|
Coordinator System Prompt ← Coordinator mode only
|
||||||
|
↓
|
||||||
|
Agent System Prompt ← agentDefinition.getSystemPrompt()
|
||||||
|
↓ - proactive mode: appended to default
|
||||||
|
↓ - otherwise: replaces default
|
||||||
|
Custom System Prompt ← --system-prompt argument
|
||||||
|
↓
|
||||||
|
Default System Prompt ← Standard Claude Code prompt
|
||||||
|
↓
|
||||||
|
Append System Prompt ← Appended to the end
|
||||||
|
```
|
||||||
|
|
||||||
|
**Agent-specific system prompt enhancement**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/tools/AgentTool/runAgent.ts
|
||||||
|
function getAgentSystemPrompt(agentDef, toolUseContext) {
|
||||||
|
let prompt = agentDef.getSystemPrompt({ toolUseContext })
|
||||||
|
prompt = enhanceSystemPromptWithEnvDetails(prompt)
|
||||||
|
// Adds: working directory, enabled tools list, model info, environment variables
|
||||||
|
return prompt
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SubagentContext — Subagent Context Isolation
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/forkedAgent.ts
|
||||||
|
export type SubagentContextOverrides = {
|
||||||
|
options?: ToolUseContext['options'] // Custom tools, model
|
||||||
|
agentId?: AgentId // Subagent ID
|
||||||
|
agentType?: string // Agent type
|
||||||
|
messages?: Message[] // Custom message history
|
||||||
|
readFileState?: ToolUseContext['readFileState'] // File read cache
|
||||||
|
abortController?: AbortController // Abort controller
|
||||||
|
|
||||||
|
// Explicit opt-in sharing (isolated by default)
|
||||||
|
shareSetAppState?: boolean // Share AppState writes
|
||||||
|
shareSetResponseLength?: boolean // Share response length metrics
|
||||||
|
shareAbortController?: boolean // Share abort controller
|
||||||
|
|
||||||
|
// Experimental injection
|
||||||
|
criticalSystemReminder_EXPERIMENTAL?: string // Re-injected each turn
|
||||||
|
contentReplacementState?: ContentReplacementState
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Isolation vs. Sharing**:
|
||||||
|
|
||||||
|
| Resource | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| readFileState | Cloned | File read cache is independent |
|
||||||
|
| messages | New | Message history is independent |
|
||||||
|
| abortController | New (linked to parent) | Child is cancelled when parent cancels |
|
||||||
|
| setAppState | No-op | Does not affect parent state by default |
|
||||||
|
| contentReplacementState | Cloned | Content replacement state is independent |
|
||||||
|
|
||||||
|
### Model Resolution
|
||||||
|
|
||||||
|
`getAgentModel()` — `src/utils/model/agent.ts`
|
||||||
|
|
||||||
|
**Priority chain**:
|
||||||
|
|
||||||
|
```
|
||||||
|
CLAUDE_CODE_SUBAGENT_MODEL env var ← Highest
|
||||||
|
↓
|
||||||
|
Agent({ model: 'opus' }) parameter ← Specified via tool
|
||||||
|
↓
|
||||||
|
agentDefinition.model ← Agent definition
|
||||||
|
↓
|
||||||
|
'inherit' ← Inherit parent model
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Agent Teams Internals
|
||||||
|
|
||||||
|
### TeamFile Structure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Storage path: ~/.claude/teams/{team_name}/config.json
|
||||||
|
{
|
||||||
|
name: string // Team name
|
||||||
|
description?: string // Team description
|
||||||
|
createdAt: number // Creation timestamp
|
||||||
|
leadAgentId: string // Team Lead's Agent ID
|
||||||
|
leadSessionId?: string // Lead's session UUID
|
||||||
|
hiddenPaneIds?: string[] // Panes hidden in UI
|
||||||
|
teamAllowedPaths?: TeamAllowedPath[] // Team-level shared permissions
|
||||||
|
members: Array<{
|
||||||
|
agentId: string // Member Agent ID
|
||||||
|
name: string // Display name
|
||||||
|
agentType?: string // Role type
|
||||||
|
model?: string // Model used
|
||||||
|
prompt?: string // Initial task
|
||||||
|
color?: string // UI color
|
||||||
|
planModeRequired?: boolean // Whether plan approval is required
|
||||||
|
joinedAt: number // Join timestamp
|
||||||
|
tmuxPaneId: string // Terminal pane ID
|
||||||
|
cwd: string // Working directory
|
||||||
|
worktreePath?: string // Worktree path
|
||||||
|
sessionId?: string // Session ID
|
||||||
|
subscriptions: string[] // Message subscriptions
|
||||||
|
backendType?: 'tmux'|'iterm2'|'in-process'
|
||||||
|
isActive?: boolean // false=idle, true/undefined=active
|
||||||
|
mode?: PermissionMode // Current permission mode
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mailbox System
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Storage path**: `~/.claude/teams/{team_name}/inboxes/{agent_name}.json`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/teammateMailbox.ts
|
||||||
|
type TeammateMessage = {
|
||||||
|
from: string // Sender name
|
||||||
|
text: string // Message content (plain text or JSON)
|
||||||
|
timestamp: string // ISO timestamp
|
||||||
|
read: boolean // Whether the message has been read
|
||||||
|
color?: string // Sender's color
|
||||||
|
summary?: string // 5-10 word summary
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Concurrency safety**: Uses `proper-lockfile` file locks with 10 retries and 5-100ms exponential backoff.
|
||||||
|
|
||||||
|
**Message Types**:
|
||||||
|
|
||||||
|
| Message | Format | Purpose |
|
||||||
|
|---------|--------|---------|
|
||||||
|
| Plain text | `string` | Regular conversation messages |
|
||||||
|
| shutdown_request | `{ type, reason }` | Request a teammate to shut down |
|
||||||
|
| shutdown_response | `{ type, request_id, approve }` | Approve/reject shutdown |
|
||||||
|
| plan_approval_response | `{ type, request_id, approve }` | Approve a plan |
|
||||||
|
| permission_request | `{ type, toolName, path }` | Permission request |
|
||||||
|
| idle_notification | Special format | Idle notification |
|
||||||
|
|
||||||
|
### Inbox Polling
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/hooks/useInboxPoller.ts
|
||||||
|
// Poll interval: 1000ms
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
const messages = await readUnreadMessages(agentName, teamName)
|
||||||
|
|
||||||
|
for (const msg of messages) {
|
||||||
|
if (isShutdownRequest(msg.text)) {
|
||||||
|
// Handle shutdown request
|
||||||
|
} else if (isPlanApprovalResponse(msg.text)) {
|
||||||
|
// Handle plan approval
|
||||||
|
} else if (isPermissionRequest(msg.text)) {
|
||||||
|
// Route to permission system
|
||||||
|
} else {
|
||||||
|
// Plain text message → submit as new conversation turn
|
||||||
|
onSubmitMessage(formatted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, INBOX_POLL_INTERVAL_MS)
|
||||||
|
}, [])
|
||||||
|
```
|
||||||
|
|
||||||
|
**Message Processing States**:
|
||||||
|
|
||||||
|
| State | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `pending` | Newly received, awaiting processing |
|
||||||
|
| `processing` | Currently being processed (e.g., permission requests) |
|
||||||
|
| `processed` | Processing complete |
|
||||||
|
|
||||||
|
### Message Routing
|
||||||
|
|
||||||
|
```
|
||||||
|
SendMessage({ to, message })
|
||||||
|
│
|
||||||
|
├─ to === "*" → Broadcast
|
||||||
|
│ └─ Iterate all teammates, write to each mailbox
|
||||||
|
│
|
||||||
|
├─ agentNameRegistry.has(to) → In-process subagent
|
||||||
|
│ └─ Route via AppState pending messages queue
|
||||||
|
│
|
||||||
|
├─ teamFile.members.find(to) → Process-level teammate
|
||||||
|
│ └─ writeToMailbox(to, message, teamName)
|
||||||
|
│
|
||||||
|
├─ to.startsWith("bridge:") → Remote session
|
||||||
|
│ └─ postInterClaudeMessage(sessionId, message)
|
||||||
|
│
|
||||||
|
└─ to.startsWith("uds:") → Unix Domain Socket
|
||||||
|
└─ sendToUdsSocket(socketPath, message)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Background Task Engine
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### LocalAgentTask State Machine
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/tasks/LocalAgentTask/LocalAgentTask.tsx
|
||||||
|
type LocalAgentTaskState = {
|
||||||
|
type: 'local_agent'
|
||||||
|
agentId: AgentId // Unique identifier
|
||||||
|
status: 'running' | 'completed' | 'failed' | 'killed'
|
||||||
|
isBackgrounded: boolean // Foreground vs. background
|
||||||
|
|
||||||
|
progress: {
|
||||||
|
latestInputTokens: number // Latest input tokens
|
||||||
|
cumulativeOutputTokens: number // Cumulative output tokens
|
||||||
|
toolUseCount: number // Tool invocation count
|
||||||
|
recentActivities: ToolActivity[] // Last 5 activities
|
||||||
|
lastActivity: number // Last activity timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
result?: AgentToolResult // Final result
|
||||||
|
abortController: AbortController // Abort controller
|
||||||
|
retain: boolean // UI retention flag
|
||||||
|
evictAfter?: number // Delayed eviction timestamp
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**State Transitions**:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────┐
|
||||||
|
│ │
|
||||||
|
register │ ┌──── killed ←── abort()
|
||||||
|
│ │ │
|
||||||
|
▼ │ │
|
||||||
|
running ─────┼────┼──── completed ← finalizeAgentTool()
|
||||||
|
│ │
|
||||||
|
│ └──── failed ← error / timeout
|
||||||
|
│
|
||||||
|
└──── evict ← notified && endTime > grace
|
||||||
|
```
|
||||||
|
|
||||||
|
### Progress Tracking
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ProgressTracker
|
||||||
|
function updateProgressFromMessage(tracker, message) {
|
||||||
|
// 1. Track input tokens (take latest value)
|
||||||
|
tracker.latestInputTokens = message.usage?.input_tokens
|
||||||
|
|
||||||
|
// 2. Accumulate output tokens
|
||||||
|
tracker.cumulativeOutputTokens += message.usage?.output_tokens
|
||||||
|
|
||||||
|
// 3. Count tool invocations
|
||||||
|
tracker.toolUseCount += countToolUses(message)
|
||||||
|
|
||||||
|
// 4. Maintain recent activities (circular buffer, max 5)
|
||||||
|
tracker.recentActivities = [...activities].slice(-5)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notification System
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/messageQueueManager.ts
|
||||||
|
function enqueuePendingNotification(taskId, result) {
|
||||||
|
// 1. Atomically set notified flag (prevent duplicates)
|
||||||
|
if (task.notified) return
|
||||||
|
task.notified = true
|
||||||
|
|
||||||
|
// 2. Format XML notification
|
||||||
|
const notification = `
|
||||||
|
<task-notification>
|
||||||
|
<task-id>${taskId}</task-id>
|
||||||
|
<status>${status}</status>
|
||||||
|
<summary>${summary}</summary>
|
||||||
|
<output-file>${outputPath}</output-file>
|
||||||
|
</task-notification>
|
||||||
|
`
|
||||||
|
|
||||||
|
// 3. Enqueue for primary agent consumption
|
||||||
|
pendingNotifications.push(notification)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output Management
|
||||||
|
|
||||||
|
**Storage path**: `~/.claude/temp/{sessionId}/tasks/{taskId}.output`
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
|-----------|-------|
|
||||||
|
| Max capacity | 5GB / file |
|
||||||
|
| Circular buffer | 1000 lines |
|
||||||
|
| Poll interval | 1 second |
|
||||||
|
| Terminal state retention | 3 seconds (30 seconds for task panel) |
|
||||||
|
| Write method | Asynchronous queue writes to prevent memory buildup |
|
||||||
|
| Safety measure | O_NOFOLLOW to prevent symlink attacks |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. DreamTask — Automatic Memory Consolidation
|
||||||
|
|
||||||
|
DreamTask is a special background agent used for cross-session memory consolidation.
|
||||||
|
|
||||||
|
### Trigger Conditions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/services/autoDream/autoDream.ts
|
||||||
|
function executeAutoDream() {
|
||||||
|
// Four gates:
|
||||||
|
if (hoursSinceLastConsolidation < minHours) return // Time gate: default 24h
|
||||||
|
if (sessionsSinceLastConsolidation < minSessions) return // Session gate: default 5
|
||||||
|
if (otherProcessConsolidating) return // Lock gate: mutual exclusion
|
||||||
|
if (timeSinceLastScan < 10min) return // Scan throttle: 10 minutes
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DreamTask State
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type DreamTaskState = {
|
||||||
|
type: 'dream'
|
||||||
|
phase: 'starting' | 'updating' // updating = has started editing files
|
||||||
|
sessionsReviewing: number // Number of sessions being reviewed
|
||||||
|
filesTouched: string[] // File paths that have been edited
|
||||||
|
turns: DreamTurn[] // Conversation turn records
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Worktree Isolation Implementation
|
||||||
|
|
||||||
|
### Creation Flow
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/worktree.ts
|
||||||
|
async function createAgentWorktree(slug) {
|
||||||
|
// 1. Validate slug (prevent directory traversal attacks)
|
||||||
|
validateWorktreeSlug(slug)
|
||||||
|
|
||||||
|
// 2. Create git worktree
|
||||||
|
git worktree add {path} -b {branch}
|
||||||
|
|
||||||
|
// 3. Symlink large directories (save disk space)
|
||||||
|
symlink(node_modules, worktree/node_modules)
|
||||||
|
|
||||||
|
// 4. Apply sparse-checkout (if configured)
|
||||||
|
if (sparseCheckoutPaths) {
|
||||||
|
git sparse-checkout set {paths}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Return WorktreeSession
|
||||||
|
return { worktreePath, worktreeBranch, headCommit }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cleanup Mechanism
|
||||||
|
|
||||||
|
- After agent completion, automatically checks for changes (`hasWorktreeChanges()`)
|
||||||
|
- If changes exist: returns the worktree path and branch name to the user
|
||||||
|
- If no changes: automatically deletes the worktree (`removeAgentWorktree()`)
|
||||||
|
- On abnormal exit: cleanup is ensured via `registerTeamForSessionCleanup()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Permission Synchronization
|
||||||
|
|
||||||
|
### Team-Level Permissions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type TeamAllowedPath = {
|
||||||
|
path: string // Absolute directory path
|
||||||
|
toolName: string // Applicable tool (e.g., "Edit", "Write")
|
||||||
|
addedBy: string // Who added it
|
||||||
|
addedAt: number // When it was added
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Teammates automatically inherit team-level permission rules on startup.
|
||||||
|
|
||||||
|
### Bubble Mode
|
||||||
|
|
||||||
|
Fork agents use the `bubble` permission mode — permission prompts bubble up to the parent agent's terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
Fork Agent needs permission
|
||||||
|
│
|
||||||
|
└─ bubble mode → Permission request sent to parent agent
|
||||||
|
│
|
||||||
|
└─ Parent agent's ToolUseConfirm dialog is displayed
|
||||||
|
│
|
||||||
|
├─ User approves → Result relayed back to Fork Agent
|
||||||
|
└─ User denies → Fork Agent receives denial
|
||||||
|
```
|
||||||
|
|
||||||
|
### In-Process Teammate Permissions
|
||||||
|
|
||||||
|
```
|
||||||
|
Teammate needs permission
|
||||||
|
│
|
||||||
|
├─ Has UI bridge → Displayed directly in Leader's confirmation dialog
|
||||||
|
│ └─ With worker badge indicating source
|
||||||
|
│
|
||||||
|
└─ No UI bridge → Queued via mailbox
|
||||||
|
└─ Handled by Leader's useSwarmPermissionPoller
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Agent Lifecycle End-to-End Data Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. User triggers Agent Tool
|
||||||
|
│
|
||||||
|
2. AgentTool.call() routes and dispatches
|
||||||
|
│
|
||||||
|
3. Resolve agent definition
|
||||||
|
├─ Look up agent type (built-in > plugin > user > project)
|
||||||
|
├─ Load system prompt
|
||||||
|
├─ Resolve tool pool (three-layer filtering)
|
||||||
|
└─ Determine permission mode and model
|
||||||
|
│
|
||||||
|
4. Create isolated context
|
||||||
|
├─ createSubagentContext() (clone readFileState)
|
||||||
|
├─ Generate agentId
|
||||||
|
├─ Create AbortController
|
||||||
|
└─ Optional: create worktree
|
||||||
|
│
|
||||||
|
5. Register task state
|
||||||
|
├─ registerAsyncAgent() or registerAgentForeground()
|
||||||
|
├─ Emit SDK event: task_started
|
||||||
|
└─ Register Perfetto trace
|
||||||
|
│
|
||||||
|
6. Execute query loop
|
||||||
|
├─ query() async generator
|
||||||
|
│ ├─ Build API request (with CacheSafeParams)
|
||||||
|
│ ├─ Process streaming response
|
||||||
|
│ ├─ Execute tool calls
|
||||||
|
│ └─ Accumulate usage metrics
|
||||||
|
├─ Update progress (ProgressTracker)
|
||||||
|
└─ Record transcript
|
||||||
|
│
|
||||||
|
7. Completion handling
|
||||||
|
├─ finalizeAgentTool() (extract result text)
|
||||||
|
├─ completeAgentTask() (mark complete)
|
||||||
|
├─ Clean up resources
|
||||||
|
│ ├─ Release file state cache
|
||||||
|
│ ├─ Close MCP connections
|
||||||
|
│ └─ Delete worktree (if applicable)
|
||||||
|
├─ enqueuePendingNotification() (notify primary agent)
|
||||||
|
└─ Emit SDK event: task_completed
|
||||||
|
│
|
||||||
|
8. Primary agent consumes result
|
||||||
|
├─ Synchronous: directly receives AgentToolResult
|
||||||
|
└─ Asynchronous: processes after receiving <task-notification>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Key Source File Index
|
||||||
|
|
||||||
|
### Agent Tool Core
|
||||||
|
|
||||||
|
| File | Responsibility |
|
||||||
|
|------|---------------|
|
||||||
|
| `src/tools/AgentTool/AgentTool.tsx` | Main tool implementation, routing & dispatch |
|
||||||
|
| `src/tools/AgentTool/runAgent.ts` | Execution engine, query loop |
|
||||||
|
| `src/tools/AgentTool/agentToolUtils.ts` | Tool pool resolution, result finalization |
|
||||||
|
| `src/tools/AgentTool/forkSubagent.ts` | Fork semantics, message inheritance |
|
||||||
|
| `src/tools/AgentTool/loadAgentsDir.ts` | Agent definition types, parsing & loading |
|
||||||
|
| `src/tools/AgentTool/builtInAgents.ts` | Built-in agent registry |
|
||||||
|
| `src/tools/AgentTool/prompt.ts` | Agent tool schema and documentation |
|
||||||
|
| `src/tools/AgentTool/agentMemory.ts` | Agent persistent memory |
|
||||||
|
|
||||||
|
### Swarm Infrastructure
|
||||||
|
|
||||||
|
| File | Responsibility |
|
||||||
|
|------|---------------|
|
||||||
|
| `src/tools/TeamCreateTool/TeamCreateTool.ts` | Team creation |
|
||||||
|
| `src/tools/TeamDeleteTool/TeamDeleteTool.ts` | Team cleanup |
|
||||||
|
| `src/tools/SendMessageTool/SendMessageTool.ts` | Inter-agent communication |
|
||||||
|
| `src/tools/shared/spawnMultiAgent.ts` | Teammate spawn entry point |
|
||||||
|
| `src/utils/swarm/spawnInProcess.ts` | In-process teammate spawning |
|
||||||
|
| `src/utils/swarm/teamHelpers.ts` | Team file read/write |
|
||||||
|
| `src/utils/swarm/constants.ts` | Constant definitions |
|
||||||
|
| `src/utils/swarm/teammateInit.ts` | Teammate initialization |
|
||||||
|
| `src/utils/swarm/permissionSync.ts` | Permission synchronization |
|
||||||
|
| `src/utils/teammate.ts` | Teammate identity resolution |
|
||||||
|
| `src/utils/teammateMailbox.ts` | Mailbox message queue |
|
||||||
|
| `src/utils/teamDiscovery.ts` | Team discovery |
|
||||||
|
| `src/hooks/useInboxPoller.ts` | Inbox polling |
|
||||||
|
|
||||||
|
### Context Management
|
||||||
|
|
||||||
|
| File | Responsibility |
|
||||||
|
|------|---------------|
|
||||||
|
| `src/utils/forkedAgent.ts` | Cache-safe parameters, subagent context |
|
||||||
|
| `src/utils/systemPrompt.ts` | System prompt priority construction |
|
||||||
|
| `src/utils/model/agent.ts` | Agent model resolution |
|
||||||
|
| `src/utils/worktree.ts` | Git worktree isolation |
|
||||||
|
|
||||||
|
### Task System
|
||||||
|
|
||||||
|
| File | Responsibility |
|
||||||
|
|------|---------------|
|
||||||
|
| `src/tasks/LocalAgentTask/LocalAgentTask.tsx` | Local agent task |
|
||||||
|
| `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx` | Remote agent task |
|
||||||
|
| `src/tasks/InProcessTeammateTask/` | In-process teammate task |
|
||||||
|
| `src/tasks/DreamTask/DreamTask.ts` | Memory consolidation task |
|
||||||
|
| `src/utils/task/framework.ts` | Task registration, state updates |
|
||||||
|
| `src/utils/task/diskOutput.ts` | Task output file management |
|
||||||
|
| `src/utils/messageQueueManager.ts` | Notification queue |
|
||||||
|
|
||||||
|
### Coordinator
|
||||||
|
|
||||||
|
| File | Responsibility |
|
||||||
|
|------|---------------|
|
||||||
|
| `src/coordinator/coordinatorMode.ts` | Coordinator mode configuration |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Feature Flags
|
||||||
|
|
||||||
|
| Flag | Controls |
|
||||||
|
|------|----------|
|
||||||
|
| `FORK_SUBAGENT` | Enable fork path (when subagent_type is omitted) |
|
||||||
|
| `BUILTIN_EXPLORE_PLAN_AGENTS` | Enable Explore/Plan agents |
|
||||||
|
| `VERIFICATION_AGENT` | Enable verification agent |
|
||||||
|
| `COORDINATOR_MODE` | Enable coordinator mode |
|
||||||
|
| `KAIROS` | Enable cwd parameter |
|
||||||
|
| `tengu_auto_background_agents` | Auto-background after 120 seconds |
|
||||||
|
| `tengu_slim_subagent_claudemd` | Omit CLAUDE.md for read-only agents |
|
||||||
|
| `tengu_agent_list_attach` | Inject agent list via attachment |
|
||||||
@@ -0,0 +1,790 @@
|
|||||||
|
# Claude Code Agent Framework Deep Dive
|
||||||
|
|
||||||
|
> Deconstructing the architecture behind the world's most popular AI code editor — from source code to design philosophy.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#1-the-core-agent-loop">Core Loop</a> · <a href="#2-system-prompt-engineering">Prompt Engineering</a> · <a href="#3-tool-system-design">Tool System</a> · <a href="#4-context-management-compression">Context Management</a> · <a href="#5-skills-plugin-ecosystem">Skills & Plugins</a> · <a href="#6-permission-security-model">Permissions</a> · <a href="#7-fault-recovery-mechanisms">Recovery</a> · <a href="#8-how-it-differs-from-langchain-react">vs LangChain</a> · <a href="#9-why-claude-code-is-so-good">Why It Works</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Preface: A Fundamental Question
|
||||||
|
|
||||||
|
If you observe Claude Code closely, you'll notice some remarkable behaviors:
|
||||||
|
|
||||||
|
- It can modify dozens of files in a single conversation with extremely few errors
|
||||||
|
- It automatically recovers from edge cases (token overflow, API timeouts, tool failures)
|
||||||
|
- It can simultaneously manage multiple subagents collaborating on complex tasks
|
||||||
|
- Long conversations don't degrade — they actually become more precise over time
|
||||||
|
|
||||||
|
Behind these capabilities lies a carefully engineered Agent framework. This document deconstructs that framework from the source code level, revealing its core design philosophy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. The Core Agent Loop
|
||||||
|
|
||||||
|
### 1.1 Not ReAct — An Async Generator State Machine
|
||||||
|
|
||||||
|
Most agent frameworks (including LangChain) adopt the classic **ReAct** pattern:
|
||||||
|
|
||||||
|
```
|
||||||
|
Thought → Action → Observation → Thought → ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Claude Code does **not** use this pattern. Its core is an **async generator-driven state machine**, defined in `src/query.ts` (~1730 lines):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/query.ts:219
|
||||||
|
export async function* query(params: QueryParams): AsyncGenerator<...>
|
||||||
|
```
|
||||||
|
|
||||||
|
This function is the heart of the entire agent. It's not a simple "think-act-observe" loop but a **streaming state machine** that yields messages in real-time and drives iteration through state assignment (not recursive calls).
|
||||||
|
|
||||||
|
### 1.2 The State Structure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/query.ts:204-217
|
||||||
|
type State = {
|
||||||
|
messages: Message[] // Full conversation history
|
||||||
|
toolUseContext: ToolUseContext // Tool execution context
|
||||||
|
autoCompactTracking: AutoCompactTracking // Auto-compaction tracking
|
||||||
|
maxOutputTokensRecoveryCount: number // Output recovery counter
|
||||||
|
hasAttemptedReactiveCompact: boolean // Whether reactive compact was tried
|
||||||
|
maxOutputTokensOverride: number // Output token override
|
||||||
|
pendingToolUseSummary: Promise<...> // Pending tool summary
|
||||||
|
stopHookActive: boolean // Stop hook state
|
||||||
|
turnCount: number // Conversation turn count
|
||||||
|
transition: Continue | undefined // Transition reason
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Five Phases of the Core Loop
|
||||||
|
|
||||||
|
The entire `while (true)` loop (`src/query.ts:307-1728`) consists of five phases:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### Phase 1: Message Preparation & Smart Compression (lines 365-543)
|
||||||
|
|
||||||
|
Before calling the API, conversation history goes through four layers of compression:
|
||||||
|
|
||||||
|
| Compression Strategy | Mechanism | Trigger |
|
||||||
|
|---------------------|-----------|---------|
|
||||||
|
| **Snip Compression** | Smart deletion of redundant tokens in old messages | Every turn |
|
||||||
|
| **Micro Compression** | In-place modification of cached message content | Every turn |
|
||||||
|
| **Context Collapse** | Staged summarization of historical messages | When context nears limit |
|
||||||
|
| **Auto Compact** | Full summary generation via Claude | When context is critically low |
|
||||||
|
|
||||||
|
This is the key to Claude Code handling **extremely long conversations** without degradation — it doesn't simply truncate history, but **intelligently compresses while preserving critical information**.
|
||||||
|
|
||||||
|
#### Phase 2: Streaming API Call (lines 652-954)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/query.ts:659-708
|
||||||
|
for await (const message of deps.callModel({
|
||||||
|
messages: prependUserContext(messagesForQuery, userContext),
|
||||||
|
systemPrompt: fullSystemPrompt,
|
||||||
|
thinkingConfig,
|
||||||
|
tools: toolUseContext.options.tools,
|
||||||
|
signal: abortController.signal,
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
Key design: **tools begin executing during streaming**, not after the model generates a complete response. This is achieved through `StreamingToolExecutor` — when the model generates `tool_use` blocks, tools start running immediately.
|
||||||
|
|
||||||
|
#### Phase 3: Decision Point (lines 1062-1358)
|
||||||
|
|
||||||
|
```
|
||||||
|
Model response complete
|
||||||
|
│
|
||||||
|
├─ Has tool calls? ──→ Continue loop (Phase 4)
|
||||||
|
│
|
||||||
|
└─ No tool calls? ──→ Run stop hooks → Check token budget → Return result
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Phase 4: Tool Orchestration (lines 1363-1409)
|
||||||
|
|
||||||
|
Tool execution isn't simple sequential invocation — it uses a carefully designed **orchestration strategy** (`src/services/tools/toolOrchestration.ts`):
|
||||||
|
|
||||||
|
```
|
||||||
|
Tool call list
|
||||||
|
│
|
||||||
|
├─ Partition: read-only vs. write
|
||||||
|
│
|
||||||
|
├─ Read-only tools ──→ Parallel execution (up to 10 concurrent)
|
||||||
|
│
|
||||||
|
└─ Write tools ──→ Serial execution (prevent race conditions)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Phase 5: State Update & Loop (lines 1704-1728)
|
||||||
|
|
||||||
|
This is the most elegant part of the design — **driving the loop through state assignment rather than recursive calls**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/query.ts:1715-1728
|
||||||
|
const next: State = {
|
||||||
|
messages: [...messagesForQuery, ...assistantMessages, ...toolResults],
|
||||||
|
toolUseContext: toolUseContextWithQueryTracking,
|
||||||
|
autoCompactTracking: tracking,
|
||||||
|
turnCount: nextTurnCount,
|
||||||
|
transition: { reason: 'next_turn' },
|
||||||
|
}
|
||||||
|
state = next
|
||||||
|
// Back to top of while(true) loop
|
||||||
|
```
|
||||||
|
|
||||||
|
No recursion, no callback hell — just simple `state = next` followed by `continue`. This guarantees:
|
||||||
|
- **Memory stability**: No stack overflow from deep recursion
|
||||||
|
- **State traceability**: Every transition reason is recorded
|
||||||
|
- **Controllable recovery**: Errors at any phase can be recovered by modifying state
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. System Prompt Engineering
|
||||||
|
|
||||||
|
### 2.1 Layered Construction Architecture
|
||||||
|
|
||||||
|
The system prompt isn't a static string — it's dynamically assembled through a **layered pipeline** (`src/constants/prompts.ts:444-577`):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Static Cacheable Zone │
|
||||||
|
│ ┌───────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Role Def │ System Rules │ Task Guide │ Tool Desc │ Style│ │
|
||||||
|
│ └───────────────────────────────────────────────────────┘ │
|
||||||
|
├─────────────────────── Cache Boundary ──────────────────────┤
|
||||||
|
│ Dynamic Variable Zone │
|
||||||
|
│ ┌───────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Session Guide │ Memory │ Env Info │ MCP Instr │ Budget │ │
|
||||||
|
│ └───────────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
The **cache boundary (`SYSTEM_PROMPT_DYNAMIC_BOUNDARY`)** is a critical design element:
|
||||||
|
|
||||||
|
- **Above the boundary**: Content universal across users and organizations, cached with `scope: 'global'`
|
||||||
|
- **Below the boundary**: User/session-specific content, cached with `scope: 'ephemeral'`
|
||||||
|
|
||||||
|
This means Claude Code's system prompt **doesn't need to be reprocessed every time** — the static portion is shared globally, dramatically reducing latency and cost.
|
||||||
|
|
||||||
|
### 2.2 Two Section Types
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/constants/systemPromptSections.ts
|
||||||
|
|
||||||
|
// Type 1: Cached Section (computed once, reused for entire session)
|
||||||
|
systemPromptSection('memory', async () => {
|
||||||
|
return buildMemoryLines() // Load CLAUDE.md, memory files, etc.
|
||||||
|
})
|
||||||
|
|
||||||
|
// Type 2: Cache-Breaking Section (recomputed every turn)
|
||||||
|
DANGEROUS_uncachedSystemPromptSection('mcp_instructions', async () => {
|
||||||
|
return getMcpInstructions() // MCP servers may connect/disconnect mid-session
|
||||||
|
}, 'MCP servers can connect/disconnect mid-session')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 CLAUDE.md Loading Mechanism
|
||||||
|
|
||||||
|
CLAUDE.md is the custom instruction system, loaded by **priority from low to high** (`src/utils/claudemd.ts`):
|
||||||
|
|
||||||
|
```
|
||||||
|
/etc/claude-code/CLAUDE.md ← Global managed config (lowest priority)
|
||||||
|
↓
|
||||||
|
~/.claude/CLAUDE.md ← User-level global instructions
|
||||||
|
↓
|
||||||
|
project-root/CLAUDE.md ← Project-level instructions
|
||||||
|
project-root/.claude/CLAUDE.md
|
||||||
|
project-root/.claude/rules/*.md
|
||||||
|
↓
|
||||||
|
project-root/CLAUDE.local.md ← Local private instructions (highest priority)
|
||||||
|
```
|
||||||
|
|
||||||
|
Supports `@path` syntax for recursive file inclusion, with automatic circular reference prevention.
|
||||||
|
|
||||||
|
### 2.4 System Prompt Priority Resolution
|
||||||
|
|
||||||
|
The final system prompt is determined through `buildEffectiveSystemPrompt()` (`src/utils/systemPrompt.ts:41-123`):
|
||||||
|
|
||||||
|
1. **Override prompt** — Complete replacement (used in loop mode)
|
||||||
|
2. **Coordinator prompt** — Coordinator mode
|
||||||
|
3. **Agent prompt** — Custom agent definition
|
||||||
|
4. **Custom prompt** — `--system-prompt` CLI flag
|
||||||
|
5. **Default prompt** — Standard system prompt
|
||||||
|
6. **Append prompt** — Always appended at the end
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Tool System Design
|
||||||
|
|
||||||
|
### 3.1 Tools: More Than Function Calls
|
||||||
|
|
||||||
|
Claude Code's tools aren't simple "name + params + execute". Each tool is a **complete lifecycle management unit** (`src/Tool.ts:362-695`):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type Tool<Input, Output> = {
|
||||||
|
// Identity
|
||||||
|
name: string
|
||||||
|
aliases?: string[] // Backward-compatible old names
|
||||||
|
searchHint?: string // ToolSearch keyword matching
|
||||||
|
|
||||||
|
// Capability declarations
|
||||||
|
isEnabled(): boolean
|
||||||
|
isConcurrencySafe(input): boolean // Can run in parallel?
|
||||||
|
isReadOnly(input): boolean // Read-only operation?
|
||||||
|
isDestructive(input): boolean // Destructive operation?
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
validateInput(input, context) // Input validation
|
||||||
|
checkPermissions(input, context) // Permission check
|
||||||
|
call(input, context, ...) // Actual execution
|
||||||
|
|
||||||
|
// Output & rendering
|
||||||
|
renderToolUseMessage(input) // Render invocation info
|
||||||
|
renderToolResultMessage(content) // Render result info
|
||||||
|
renderToolUseProgressMessage(...) // Render progress
|
||||||
|
mapToolResultToToolResultBlockParam() // Map to API format
|
||||||
|
|
||||||
|
// Smart features
|
||||||
|
inputSchema: Zod schema // Zod type validation
|
||||||
|
maxResultSizeChars: number // Result size threshold
|
||||||
|
toAutoClassifierInput(input) // Security classifier input
|
||||||
|
getToolUseSummary?(input): string // Tool usage summary
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This design makes every tool **self-describing, self-validating, and self-rendering** — the framework doesn't need to understand tool internals, just call standard interfaces.
|
||||||
|
|
||||||
|
### 3.2 Tool Registration: Three-Stage Pipeline
|
||||||
|
|
||||||
|
Tool discovery and registration happens in three stages (`src/tools.ts`):
|
||||||
|
|
||||||
|
```
|
||||||
|
Stage 1: Base Tool Pool (getAllBaseTools)
|
||||||
|
│ ~48 built-in tools
|
||||||
|
│ + Feature-flag-gated conditional tools
|
||||||
|
│
|
||||||
|
Stage 2: Filtering (getTools)
|
||||||
|
│ Filter by permission mode
|
||||||
|
│ Filter by REPL mode
|
||||||
|
│ Filter by isEnabled()
|
||||||
|
│
|
||||||
|
Stage 3: MCP Merge (assembleToolPool)
|
||||||
|
+ Dynamic tools from MCP servers
|
||||||
|
Deduplication (built-in takes precedence)
|
||||||
|
Sorting (cache stability)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Tool Execution Pipeline
|
||||||
|
|
||||||
|
Each tool invocation passes through a **7-step pipeline** (`src/services/tools/toolExecution.ts`):
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Tool Lookup → 2. Input Parsing (Zod) → 3. Custom Validation
|
||||||
|
│
|
||||||
|
4. Pre-Tool Hooks → 5. Permission Check → 6. Actual Execution → 7. Post-Tool Hooks
|
||||||
|
```
|
||||||
|
|
||||||
|
Each step can **interrupt, modify, or enhance** the execution flow. This isn't a simple `try { tool.call(input) } catch` — it's a full middleware pipeline.
|
||||||
|
|
||||||
|
### 3.4 Deferred Tool Loading
|
||||||
|
|
||||||
|
Claude Code has 48+ built-in tools. Sending all tool definitions to the model on every API call would waste massive tokens. The solution:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Tools can be marked for deferred loading
|
||||||
|
{
|
||||||
|
shouldDefer: true, // Only list name in ToolSearch
|
||||||
|
alwaysLoad: false, // Don't include full schema in initial prompt
|
||||||
|
searchHint: "notebook" // Search keywords
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The model dynamically retrieves full definitions via the `ToolSearch` tool when needed. This dramatically reduces system prompt size.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Context Management & Compression
|
||||||
|
|
||||||
|
### 4.1 The Secret Behind Unlimited Conversations
|
||||||
|
|
||||||
|
Claude Code claims "conversations have no context limit." Behind this is a **four-level compression system**:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### Level 1: Snip Compression
|
||||||
|
|
||||||
|
Smart trimming of processed messages — removes duplicate file content, overly long tool outputs, etc.
|
||||||
|
|
||||||
|
#### Level 2: Micro Compression
|
||||||
|
|
||||||
|
Modifies cached message content without changing the cache key. An "in-place optimization" strategy.
|
||||||
|
|
||||||
|
#### Level 3: Context Collapse
|
||||||
|
|
||||||
|
Staged summarization of historical messages. Not all-at-once summarization, but **progressive folding** — summarize the oldest messages first, keeping recent details intact.
|
||||||
|
|
||||||
|
#### Level 4: Auto Compact
|
||||||
|
|
||||||
|
When all local optimizations are insufficient, Claude itself generates a complete conversation summary that replaces all historical messages.
|
||||||
|
|
||||||
|
### 4.2 System Context Injection
|
||||||
|
|
||||||
|
Before every API call, two types of context are automatically injected (`src/context.ts`):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// System context (memoized, cached for entire session)
|
||||||
|
getSystemContext() → {
|
||||||
|
gitStatus, // Current branch, recent commits, file status
|
||||||
|
cacheBreakerInjection // System-level injection
|
||||||
|
}
|
||||||
|
|
||||||
|
// User context (memoized, cleared when CLAUDE.md changes)
|
||||||
|
getUserContext() → {
|
||||||
|
claudeMdContent, // Merged content from all CLAUDE.md files
|
||||||
|
currentDate, // Current date
|
||||||
|
mcpInstructions // MCP server instructions
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 System Reminders
|
||||||
|
|
||||||
|
System reminders are special **attachment messages** injected into tool results or user messages (`src/utils/attachments.ts`):
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<system-reminder>
|
||||||
|
System-level context information, unrelated to specific tool results.
|
||||||
|
</system-reminder>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use cases include:
|
||||||
|
- Security warnings during file reads
|
||||||
|
- Memory staleness notifications
|
||||||
|
- Accompanying information for side questions
|
||||||
|
- Availability notices for deferred tools
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Skills & Plugin Ecosystem
|
||||||
|
|
||||||
|
### 5.1 Skills System
|
||||||
|
|
||||||
|
Skills are one of Claude Code's most powerful extension mechanisms. They're not simple "command aliases" but **complete AI behavior definitions**.
|
||||||
|
|
||||||
|
#### Skill Definition Structure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type BundledSkillDefinition = {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
whenToUse?: string // Model auto-determines when to use
|
||||||
|
allowedTools?: string[] // Restrict tool pool
|
||||||
|
model?: string // Specify model
|
||||||
|
hooks?: HooksSettings // Lifecycle hooks
|
||||||
|
context?: 'inline' | 'fork' // Inline or independent context
|
||||||
|
agent?: string // Associated agent type
|
||||||
|
getPromptForCommand: (args, context) => Promise<ContentBlockParam[]>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Two Execution Contexts
|
||||||
|
|
||||||
|
| Context | Behavior | Use Case |
|
||||||
|
|---------|----------|----------|
|
||||||
|
| `inline` | Skill content expands directly into current conversation | Simple instructions, format templates |
|
||||||
|
| `fork` | Skill runs as a subagent in an independent context | Complex workflows, independent token budget |
|
||||||
|
|
||||||
|
#### Skill Discovery Sources
|
||||||
|
|
||||||
|
```
|
||||||
|
Bundled skills (bundled) ← Compiled into CLI, 15+
|
||||||
|
↓
|
||||||
|
Plugin skills (plugin) ← Plugin-registered
|
||||||
|
↓
|
||||||
|
User skills (~/.claude/skills/) ← User-global
|
||||||
|
↓
|
||||||
|
Project skills (.claude/skills/) ← Project-level
|
||||||
|
↓
|
||||||
|
Policy skills (policy) ← Organization-managed
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Plugin System
|
||||||
|
|
||||||
|
Plugins are higher-level extension units that can contain **skills, hooks, MCP servers, and LSP servers**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type BuiltinPluginDefinition = {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
skills?: BundledSkillDefinition[] // Skill collection
|
||||||
|
hooks?: HooksSettings // Lifecycle hooks
|
||||||
|
mcpServers?: Record<string, McpServerConfig> // MCP servers
|
||||||
|
lspServers?: Record<string, LspServerConfig> // LSP servers
|
||||||
|
isAvailable?: () => boolean // Availability check
|
||||||
|
defaultEnabled?: boolean // Default enabled state
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The key plugin design: **users can toggle enable/disable**, unlike directly registered skills.
|
||||||
|
|
||||||
|
### 5.3 Hooks System
|
||||||
|
|
||||||
|
Hooks are **programmable interception points** across the entire lifecycle:
|
||||||
|
|
||||||
|
```
|
||||||
|
SessionStart → UserPromptSubmit → PreToolUse → [Tool Execution]
|
||||||
|
│ │
|
||||||
|
│ PostToolUse
|
||||||
|
│ │
|
||||||
|
└── SubagentStart ←── Stop ←── TaskCompleted ←─┘
|
||||||
|
│
|
||||||
|
SubagentStop → SessionEnd
|
||||||
|
```
|
||||||
|
|
||||||
|
Hooks execute as shell commands, with exit codes controlling behavior:
|
||||||
|
- **0**: Success, stdout content processed per event type
|
||||||
|
- **2**: stderr content shown to model or user
|
||||||
|
- **Other**: Shown to user only
|
||||||
|
|
||||||
|
### 5.4 MCP: Model Context Protocol
|
||||||
|
|
||||||
|
MCP is the standard protocol for Claude Code's interaction with the external world. Tool naming convention:
|
||||||
|
|
||||||
|
```
|
||||||
|
mcp__{normalized_server_name}__{tool_name}
|
||||||
|
e.g.: mcp__chrome_devtools__take_screenshot
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported transports: `stdio`, `sse`, `http`, `websocket`, `sdk`
|
||||||
|
|
||||||
|
MCP tools are discovered at runtime and **seamlessly merged** into the unified tool pool alongside built-in tools.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Permission & Security Model
|
||||||
|
|
||||||
|
### 6.1 Layered Permission Model
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ Permission Rules │
|
||||||
|
│ Sources: userSettings, project, │
|
||||||
|
│ flagSettings, policy │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ Permission Modes │
|
||||||
|
│ default | plan | acceptEdits │
|
||||||
|
│ bypassPermissions | auto | bubble │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ Hooks │
|
||||||
|
│ PreToolUse can intercept/modify │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ Security Classifier │
|
||||||
|
│ ML model evaluates tool call safety│
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Permission Decision Flow
|
||||||
|
|
||||||
|
Permission check for every tool invocation:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type PermissionResult =
|
||||||
|
| { behavior: 'allow', updatedInput?, decisionReason }
|
||||||
|
| { behavior: 'ask', message, suggestions }
|
||||||
|
| { behavior: 'deny', message, decisionReason }
|
||||||
|
| { behavior: 'passthrough', message }
|
||||||
|
```
|
||||||
|
|
||||||
|
Decision reason traceability:
|
||||||
|
- `type: 'rule'` — Matched a permission rule
|
||||||
|
- `type: 'mode'` — Determined by permission mode
|
||||||
|
- `type: 'hook'` — Hook interception
|
||||||
|
- `type: 'classifier'` — ML classifier decision
|
||||||
|
|
||||||
|
### 6.3 Permission Rule Pattern Matching
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Exact match
|
||||||
|
{ tool: 'Bash', behavior: 'deny' }
|
||||||
|
|
||||||
|
// Parameter pattern matching
|
||||||
|
{ tool: 'Bash(git *)', behavior: 'allow' } // Allow all git commands
|
||||||
|
{ tool: 'Bash(rm -rf *)', behavior: 'deny' } // Block rm -rf
|
||||||
|
|
||||||
|
// Wildcard
|
||||||
|
{ tool: 'File*', behavior: 'allow' } // Allow all File* tools
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Fault Recovery Mechanisms
|
||||||
|
|
||||||
|
This is one of Claude Code's most sophisticated designs. The core loop in `src/query.ts` has **6 built-in recovery strategies**:
|
||||||
|
|
||||||
|
| Recovery Strategy | Trigger | Recovery Method |
|
||||||
|
|-------------------|---------|-----------------|
|
||||||
|
| `collapse_drain_retry` | Prompt too long | Drain staged context collapses, retry |
|
||||||
|
| `reactive_compact_retry` | Still too long | Generate summary via Claude, retry |
|
||||||
|
| `max_output_tokens_escalate` | Hit 8k default limit | Escalate to 64k limit, retry |
|
||||||
|
| `max_output_tokens_recovery` | Hit any limit | Inject "continue" nudge, retry (up to 3x) |
|
||||||
|
| `stop_hook_blocking` | Stop hook blocked | Inject blocking errors into context, retry |
|
||||||
|
| `token_budget_continuation` | Budget remaining | Inject budget nudge, continue |
|
||||||
|
|
||||||
|
Each recovery works by modifying `state`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Example: prompt-too-long recovery
|
||||||
|
if (error.type === 'prompt_too_long') {
|
||||||
|
// Drain all staged collapses
|
||||||
|
const compacted = drainStagedCollapses(state.messages)
|
||||||
|
state = { ...state, messages: compacted, transition: { reason: 'collapse_drain_retry' } }
|
||||||
|
continue // Back to loop top to retry
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.1 Model Fallback
|
||||||
|
|
||||||
|
When the primary model's stream fails, the system:
|
||||||
|
1. Cleans up orphaned incomplete messages
|
||||||
|
2. Switches to a fallback model
|
||||||
|
3. Retries with the new model
|
||||||
|
|
||||||
|
### 7.2 Media Size Recovery
|
||||||
|
|
||||||
|
When images or other media cause token overflow:
|
||||||
|
- Triggers reactive compaction
|
||||||
|
- Automatically strips image content
|
||||||
|
- Retains text information and retries
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. How It Differs from LangChain/ReAct
|
||||||
|
|
||||||
|
### 8.1 Architecture Paradigm Comparison
|
||||||
|
|
||||||
|
| Dimension | LangChain | Claude Code |
|
||||||
|
|-----------|-----------|-------------|
|
||||||
|
| **Core Pattern** | ReAct (Think→Act→Observe) | Async Generator State Machine |
|
||||||
|
| **Execution Model** | Synchronous blocking | Streaming non-blocking |
|
||||||
|
| **Tool Execution** | After complete model response | During streaming |
|
||||||
|
| **State Management** | External Memory objects | Built-in state assignment + loop |
|
||||||
|
| **Error Recovery** | Manual orchestration required | 6 built-in recovery strategies |
|
||||||
|
| **Context Compression** | Simple truncation or summary | Four-level progressive compression |
|
||||||
|
| **Multi-Agent** | Chain/Graph explicit orchestration | Unified tool interface + state machine |
|
||||||
|
| **Extension Mechanisms** | Python class inheritance | Skills + Plugins + Hooks + MCP |
|
||||||
|
| **Caching Strategy** | None | Global / session / per-turn three-level cache |
|
||||||
|
|
||||||
|
### 8.2 Why Not ReAct?
|
||||||
|
|
||||||
|
The ReAct pattern has several inherent limitations:
|
||||||
|
|
||||||
|
1. **Serial bottleneck**: Each step must wait for the complete "think→act→observe" cycle
|
||||||
|
2. **No streaming capability**: Tools can't execute until the model completes its full response
|
||||||
|
3. **Recovery difficulty**: No unified state representation makes automatic recovery hard
|
||||||
|
4. **Cache-unfriendly**: Prompt structure changes significantly each cycle, making caching difficult
|
||||||
|
|
||||||
|
Claude Code's Async Generator pattern solves all these problems:
|
||||||
|
|
||||||
|
- **Streaming execution**: Tools run while the model generates
|
||||||
|
- **Controllable state**: The `State` object contains all needed info; recovery means just modifying state
|
||||||
|
- **Cache optimization**: Static prompts cached globally, dynamic parts minimized
|
||||||
|
- **Parallel capability**: Read-only tools auto-parallelize, write tools serialize for ordering
|
||||||
|
|
||||||
|
### 8.3 Specific Differences from LangChain Agents
|
||||||
|
|
||||||
|
```
|
||||||
|
LangChain Agent:
|
||||||
|
agent = initialize_agent(tools, llm, agent="zero-shot-react-description")
|
||||||
|
result = agent.run("do something")
|
||||||
|
# Internal: LLM → parse → tool → LLM → parse → tool → ... → final answer
|
||||||
|
# Each step is an independent LLM call
|
||||||
|
|
||||||
|
Claude Code Agent:
|
||||||
|
for await (const msg of query({ messages, tools, systemPrompt })) {
|
||||||
|
yield msg // Real-time message output
|
||||||
|
// Internal: streaming LLM → streaming tool execution → state update → continue
|
||||||
|
// A single API call can trigger multiple tools, which execute during streaming
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Key differences:
|
||||||
|
- Each LangChain "step" is a complete LLM call
|
||||||
|
- Each Claude Code "turn" can include multiple tool calls, with tools executing during streaming
|
||||||
|
- LangChain requires an OutputParser to parse tool calls from model output
|
||||||
|
- Claude Code directly uses Anthropic API's native `tool_use` capability — no parsing needed
|
||||||
|
|
||||||
|
### 8.4 Comparison with LangGraph
|
||||||
|
|
||||||
|
LangGraph is LangChain's evolution, introducing graph structures:
|
||||||
|
|
||||||
|
| Dimension | LangGraph | Claude Code |
|
||||||
|
|-----------|-----------|-------------|
|
||||||
|
| **State Flow** | Explicit graph nodes + edges | Implicit state machine (while + continue) |
|
||||||
|
| **Visualization** | Exportable as graph | Transition reasons are traceable |
|
||||||
|
| **Persistence** | Checkpoint + State | File system + message history |
|
||||||
|
| **Human-in-Loop** | interrupt_before/after | Permission system + hooks |
|
||||||
|
| **Multi-Agent** | Requires explicit orchestration | Unified AgentTool interface |
|
||||||
|
|
||||||
|
Claude Code's advantage is **simplicity** — no need to define graph structures; a single while loop handles everything.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Why Claude Code Is So Good
|
||||||
|
|
||||||
|
From source code analysis, we can distill these core design principles:
|
||||||
|
|
||||||
|
### 9.1 Streaming First
|
||||||
|
|
||||||
|
The entire architecture is designed around `AsyncGenerator` — everything is streamed:
|
||||||
|
- Model responses are streamed
|
||||||
|
- Tools execute during streaming
|
||||||
|
- Progress updates in real-time
|
||||||
|
- Compression strategies are progressive
|
||||||
|
|
||||||
|
Users **never have to wait** — they see the model thinking, tools executing, and results emerging.
|
||||||
|
|
||||||
|
### 9.2 Intelligent Caching
|
||||||
|
|
||||||
|
Three-level prompt caching system (`src/services/api/claude.ts:3213-3237`):
|
||||||
|
|
||||||
|
```
|
||||||
|
Global Cache (cross-org) ← Static system prompt
|
||||||
|
↓
|
||||||
|
Ephemeral Cache (session) ← Dynamic system prompt
|
||||||
|
↓
|
||||||
|
Section Cache (per-turn) ← systemPromptSection memoization
|
||||||
|
```
|
||||||
|
|
||||||
|
This dramatically reduces latency and cost for every API call.
|
||||||
|
|
||||||
|
### 9.3 Graceful Degradation
|
||||||
|
|
||||||
|
Six recovery strategies ensure Claude Code **almost never interrupts the user's workflow due to technical issues**:
|
||||||
|
- Token overflow? Auto-compress
|
||||||
|
- API timeout? Auto-retry
|
||||||
|
- Model failure? Fall back to alternate model
|
||||||
|
- Tool failure? Log error, continue conversation
|
||||||
|
|
||||||
|
### 9.4 Minimal Abstraction Principle
|
||||||
|
|
||||||
|
Unlike LangChain's "abstract everything" philosophy, Claude Code's core has only:
|
||||||
|
- **One loop** (`while (true)` in `query()`)
|
||||||
|
- **One state** (`State` object)
|
||||||
|
- **One interface** (`Tool` type)
|
||||||
|
|
||||||
|
No Agent → AgentExecutor → Chain → Memory → Callback nesting layers. This makes the code **easy to understand, debug, and extend**.
|
||||||
|
|
||||||
|
### 9.5 Native API Integration
|
||||||
|
|
||||||
|
Claude Code directly leverages Anthropic API's native capabilities:
|
||||||
|
- **Native tool calling**: No OutputParser needed, directly uses `tool_use` blocks
|
||||||
|
- **Native streaming**: No wrapper layers, directly consumes SSE streams
|
||||||
|
- **Native caching**: Leverages API's prompt caching feature
|
||||||
|
- **Native chain-of-thought**: Directly uses extended thinking
|
||||||
|
|
||||||
|
This avoids the "framework tax" — the abstraction layer that frameworks like LangChain add between the LLM and the developer.
|
||||||
|
|
||||||
|
### 9.6 Tool-Driven Agent
|
||||||
|
|
||||||
|
Claude Code's philosophy: **an agent's capability equals the capability of its tools**.
|
||||||
|
|
||||||
|
- Spawn a subagent? That's a tool (`AgentTool`)
|
||||||
|
- Manage a team? That's a tool (`TeamCreate`/`SendMessage`)
|
||||||
|
- Edit a file? That's a tool (`FileEdit`)
|
||||||
|
- Execute a skill? That's a tool (`SkillTool`)
|
||||||
|
|
||||||
|
**All capabilities are exposed through the unified tool interface**, and the model uses natural language reasoning to decide which tool to use. No explicit orchestration logic needed — the model itself is the orchestrator.
|
||||||
|
|
||||||
|
### 9.7 Deep Developer Experience Integration
|
||||||
|
|
||||||
|
Claude Code isn't "generic agent + code plugin" — it's **deeply optimized for coding scenarios from the ground up**:
|
||||||
|
|
||||||
|
- **Git-aware**: Automatically injects git status, understands branches, commits, diffs
|
||||||
|
- **Filesystem-aware**: Understands project structure, intelligently searches files
|
||||||
|
- **Worktree isolation**: Safe experimental modification environments
|
||||||
|
- **LSP integration**: Language Server Protocol provides type information and diagnostics
|
||||||
|
- **MCP ecosystem**: Connects to various external tools via standard protocol
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Architecture Summary
|
||||||
|
|
||||||
|
### Core Component Relationships
|
||||||
|
|
||||||
|
```
|
||||||
|
User Input
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
QueryEngine (src/QueryEngine.ts)
|
||||||
|
│
|
||||||
|
├─ Build system prompt (prompts.ts + context.ts + claudemd.ts)
|
||||||
|
├─ Assemble tool pool (tools.ts + MCP)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
query() async generator loop (src/query.ts)
|
||||||
|
│
|
||||||
|
├─ Phase 1: Message compression (snip → micro → collapse → compact)
|
||||||
|
├─ Phase 2: Streaming API call (callModel + StreamingToolExecutor)
|
||||||
|
├─ Phase 3: Decision point (continue or complete)
|
||||||
|
├─ Phase 4: Tool orchestration (parallel read-only + serial write)
|
||||||
|
└─ Phase 5: State update (state = next → continue)
|
||||||
|
│
|
||||||
|
├─ Recovery strategies (6 types)
|
||||||
|
├─ Hook system (PreToolUse / PostToolUse / Stop / ...)
|
||||||
|
└─ Subagent spawning (AgentTool → runAgent → new query() instance)
|
||||||
|
│
|
||||||
|
├─ Synchronous foreground
|
||||||
|
├─ Async background (LocalAgentTask)
|
||||||
|
├─ Fork (inherit context)
|
||||||
|
└─ Teammate (mailbox communication)
|
||||||
|
```
|
||||||
|
|
||||||
|
### One-Line Summary
|
||||||
|
|
||||||
|
> **Claude Code's agent framework is a streaming state machine powered by AsyncGenerator, exposing all capabilities through a unified tool interface, combined with four-level context compression, three-level prompt caching, and six fault recovery strategies — an AI system that autonomously completes complex programming tasks without explicit orchestration.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Key Source File Index
|
||||||
|
|
||||||
|
| Component | File Path | Description |
|
||||||
|
|-----------|-----------|-------------|
|
||||||
|
| Core Loop | `src/query.ts` | Main agent loop (~1730 lines) |
|
||||||
|
| Query Engine | `src/QueryEngine.ts` | High-level wrapper (~687 lines) |
|
||||||
|
| Tool Definition | `src/Tool.ts` | Tool type system (~792 lines) |
|
||||||
|
| Tool Registry | `src/tools.ts` | Tool discovery and registration (~389 lines) |
|
||||||
|
| Tool Execution | `src/services/tools/toolExecution.ts` | Execution pipeline (~1500 lines) |
|
||||||
|
| Tool Orchestration | `src/services/tools/toolOrchestration.ts` | Parallel/serial strategy |
|
||||||
|
| System Prompt | `src/constants/prompts.ts` | Prompt assembly (~577 lines) |
|
||||||
|
| Prompt Sections | `src/constants/systemPromptSections.ts` | Section caching |
|
||||||
|
| Context Management | `src/context.ts` | System/user context |
|
||||||
|
| CLAUDE.md | `src/utils/claudemd.ts` | User instruction loading |
|
||||||
|
| Memory System | `src/memdir/memdir.ts` | Persistent memory |
|
||||||
|
| Agent Spawning | `src/tools/AgentTool/AgentTool.tsx` | Agent tool entry point |
|
||||||
|
| Agent Execution | `src/tools/AgentTool/runAgent.ts` | Agent execution logic |
|
||||||
|
| Fork Agent | `src/tools/AgentTool/forkSubagent.ts` | Fork cache optimization |
|
||||||
|
| Team Management | `src/utils/swarm/teamHelpers.ts` | Teams infrastructure |
|
||||||
|
| Mailbox Communication | `src/utils/teammateMailbox.ts` | Async message queue |
|
||||||
|
| Skills System | `src/skills/bundledSkills.ts` | Skill registration and management |
|
||||||
|
| Plugin System | `src/plugins/builtinPlugins.ts` | Plugin framework |
|
||||||
|
| Hook System | `src/utils/hooks/hooksConfigManager.ts` | Hook management |
|
||||||
|
| Permission System | `src/utils/permissions/permissions.ts` | Permission checking |
|
||||||
|
| State Management | `src/state/AppStateStore.ts` | Global state |
|
||||||
|
| Cost Tracking | `src/cost-tracker.ts` | API cost calculation |
|
||||||
|
| API Client | `src/services/api/claude.ts` | Anthropic API wrapper |
|
||||||
|
| MCP Client | `src/services/mcp/client.ts` | MCP protocol implementation |
|
||||||
|
| Coordinator Mode | `src/coordinator/coordinatorMode.ts` | Multi-agent orchestration |
|
||||||
|
| Remote Sessions | `src/remote/RemoteSessionManager.ts` | CCR connection management |
|
||||||
|
| Bridge | `src/bridge/bridgeMain.ts` | Remote bridge |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Further Reading
|
||||||
|
|
||||||
|
- [Usage Guide](./01-usage-guide.md) — User-facing multi-agent manual
|
||||||
|
- [Implementation Details](./02-implementation.md) — Technical deep dive into multi-agent orchestration
|
||||||
|
- [Anthropic API Docs](https://docs.anthropic.com/) — Native API capabilities
|
||||||
|
- [MCP Protocol Spec](https://modelcontextprotocol.io/) — Model Context Protocol
|
||||||
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 5.0 MiB |
|
After Width: | Height: | Size: 646 KiB |
|
After Width: | Height: | Size: 470 KiB |
|
After Width: | Height: | Size: 665 KiB |
@@ -0,0 +1,129 @@
|
|||||||
|
# Claude Code Multi-Agent System Documentation
|
||||||
|
|
||||||
|
> Complete guide and technical reference for multi-agent orchestration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation Index
|
||||||
|
|
||||||
|
### [01-usage-guide.md](./01-usage-guide.md) — Usage Guide
|
||||||
|
|
||||||
|
A comprehensive user-facing manual covering:
|
||||||
|
|
||||||
|
- **Agent Tool**: Parameter reference, spawn methods, background execution
|
||||||
|
- **Six Built-in Agents**: general-purpose, Explore, Plan, verification, claude-code-guide, statusline-setup
|
||||||
|
- **Background Tasks**: Asynchronous execution, progress tracking, completion notifications
|
||||||
|
- **Agent Teams**: Team creation, member collaboration, message communication
|
||||||
|
- **Worktree Isolation**: Independent environments, branch management, secure contexts
|
||||||
|
- **Custom Agents**: Definition format, tool pool configuration, system prompts
|
||||||
|
|
||||||
|
**Target Audience**: All Claude Code users
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [02-implementation.md](./02-implementation.md) — Implementation Details
|
||||||
|
|
||||||
|
A deep technical reference for developers covering:
|
||||||
|
|
||||||
|
- **Architecture Overview**: 5 agent categories, 4 spawn paths
|
||||||
|
- **Agent Spawn Flow**: Detailed walkthrough of Sync / Async / Fork / Teammate paths
|
||||||
|
- **Tool Pool System**: Three-layer filtering, constant definitions, permission mapping
|
||||||
|
- **Context Passing**: CacheSafeParams, system prompt construction, fork cache optimization
|
||||||
|
- **Teams Internals**: TeamFile structure, mailbox system, inbox polling, message routing
|
||||||
|
- **Background Task Engine**: LocalAgentTask lifecycle, progress tracking, notification queue
|
||||||
|
- **Permission Synchronization**: Team-level permissions, mode propagation, bubble mode
|
||||||
|
- **End-to-End Data Flow**: From Agent Tool invocation to result delivery
|
||||||
|
|
||||||
|
**Target Audience**: Contributors, architects, and developers seeking deep implementation understanding
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [03-agent-framework.md](./03-agent-framework.md) — Agent Framework Deep Dive
|
||||||
|
|
||||||
|
Deconstructing the architecture behind Claude Code's agent framework from source code, covering:
|
||||||
|
|
||||||
|
- **Core Agent Loop**: AsyncGenerator state machine, five-phase while(true) loop
|
||||||
|
- **System Prompt Engineering**: Layered construction, cache boundary, CLAUDE.md loading
|
||||||
|
- **Tool System Design**: Full lifecycle management, three-stage registration, 7-step execution pipeline
|
||||||
|
- **Context Management & Compression**: Four-level progressive compression, system context injection
|
||||||
|
- **Skills & Plugin Ecosystem**: Skill definition and discovery, plugins, hooks, MCP integration
|
||||||
|
- **Permission & Security Model**: Layered permission model, rule pattern matching
|
||||||
|
- **Fault Recovery Mechanisms**: 6 built-in recovery strategies, model fallback
|
||||||
|
- **Comparison with LangChain/ReAct**: Architecture paradigm differences, why not ReAct
|
||||||
|
- **Why Claude Code Is So Good**: 7 core design principles
|
||||||
|
|
||||||
|
**Target Audience**: Architects studying AI agent design, AI application developers, technical researchers
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Illustration Notes
|
||||||
|
|
||||||
|
All diagrams use a dark background (#1a1a2e) with Anthropic brand copper-orange (#D97757), consistent with Claude Code's official documentation style.
|
||||||
|
|
||||||
|
| Image | Description | Document |
|
||||||
|
|-------|-------------|----------|
|
||||||
|
| `01-agent-overview.png` | Multi-Agent System Overview — Architecture panorama | Usage Guide |
|
||||||
|
| `02-agent-types.png` | Six Built-in Agents — Type comparison matrix | Usage Guide |
|
||||||
|
| `03-spawn-flow.png` | Agent Spawn Flow — Four-path decision tree | Usage Guide |
|
||||||
|
| `04-agent-teams.png` | Agent Teams Collaboration — Team communication topology | Usage Guide |
|
||||||
|
| `05-architecture.png` | Implementation Architecture — Core module relationships | Implementation |
|
||||||
|
| `06-context-passing.png` | Context Passing — CacheSafeParams data flow | Implementation |
|
||||||
|
| `07-tool-pool.png` | Tool Pool System — Three-layer filtering pipeline | Implementation |
|
||||||
|
| `08-background-task.png` | Background Task Engine — Lifecycle state machine | Implementation |
|
||||||
|
| `09-teams-mailbox.png` | Teams Mailbox System — Message routing topology | Implementation |
|
||||||
|
| `10-fork-cache.png` | Fork Cache Optimization — Byte-level consistent sharing | Implementation |
|
||||||
|
| `11-agent-framework-overview.png` | Agent Framework Overview — Core component relationships | Framework Deep Dive |
|
||||||
|
| `12-agent-core-loop.png` | Core Agent Loop — Five-phase state machine | Framework Deep Dive |
|
||||||
|
| `13-system-prompt-pipeline.png` | System Prompt Pipeline — Layered cache pipeline | Framework Deep Dive |
|
||||||
|
| `14-context-compression.png` | Context Compression — Four-level progressive strategy | Framework Deep Dive |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### For Users
|
||||||
|
|
||||||
|
1. Read the [Usage Guide](./01-usage-guide.md)
|
||||||
|
2. Learn about the six built-in agents and their use cases
|
||||||
|
3. Try spawning subagents using the Agent tool in a conversation
|
||||||
|
4. Explore multi-agent collaboration with Agent Teams
|
||||||
|
|
||||||
|
### For Developers
|
||||||
|
|
||||||
|
1. Read the [Implementation Details](./02-implementation.md)
|
||||||
|
2. Browse the source code:
|
||||||
|
- `src/tools/AgentTool/` — Agent tool implementation
|
||||||
|
- `src/tools/TeamCreateTool/` — Team creation
|
||||||
|
- `src/tools/SendMessageTool/` — Inter-agent communication
|
||||||
|
- `src/utils/swarm/` — Swarm collaboration infrastructure
|
||||||
|
- `src/utils/forkedAgent.ts` — Fork agent context
|
||||||
|
- `src/tasks/` — Task management system
|
||||||
|
3. Understand the four spawn paths and context passing mechanisms
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Concepts Quick Reference
|
||||||
|
|
||||||
|
| Concept | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| **Agent Tool** | Primary entry point — accepts a prompt + subagent_type to spawn a subagent |
|
||||||
|
| **Subagent** | An independent child agent that executes tasks with its own tool pool and permissions |
|
||||||
|
| **Fork Agent** | A forked agent that inherits the parent's full context and shares prompt cache |
|
||||||
|
| **Teammate** | A collaborative member within an Agent Team, communicating via mailbox |
|
||||||
|
| **Worktree** | Git worktree isolation mode providing an independent file environment |
|
||||||
|
| **LocalAgentTask** | Local agent task state, tracking running/completed/failed status |
|
||||||
|
| **DreamTask** | Automatic memory consolidation task that runs periodically in the background |
|
||||||
|
| **CacheSafeParams** | Cache-safe parameters ensuring byte-level consistency of API request prefixes |
|
||||||
|
| **TeamFile** | Team configuration file storing the member list and permissions |
|
||||||
|
| **Mailbox** | File-based message queue supporting asynchronous communication between teammates |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Resources
|
||||||
|
|
||||||
|
- [Claude Code Haha Home](/)
|
||||||
|
- [Memory System Documentation](/en/memory/01-usage-guide)
|
||||||
|
- [Agent Tool Source Code](https://github.com/NanmiCoder/cc-haha/tree/main/src/tools/AgentTool/)
|
||||||
|
- [Swarm Infrastructure](https://github.com/NanmiCoder/cc-haha/tree/main/src/utils/swarm/)
|
||||||
|
- [Task Management System](https://github.com/NanmiCoder/cc-haha/tree/main/src/tasks/)
|
||||||
|
- [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues)
|
||||||
@@ -0,0 +1,785 @@
|
|||||||
|
# Channel System Architecture
|
||||||
|
|
||||||
|
> A deep dive into how Claude Code enables remote Agent control via IM platforms
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#1-what-is-a-channel">Concepts</a> ·
|
||||||
|
<a href="#2-architecture-overview">Architecture</a> ·
|
||||||
|
<a href="#3-message-protocol">Protocol</a> ·
|
||||||
|
<a href="#4-six-layer-access-control">Access Control</a> ·
|
||||||
|
<a href="#5-permission-relay-system">Permission Relay</a> ·
|
||||||
|
<a href="#6-ui-components">UI</a> ·
|
||||||
|
<a href="#7-plugin-channel-architecture">Plugins</a> ·
|
||||||
|
<a href="#8-security-design">Security</a> ·
|
||||||
|
<a href="#9-command-line-interface">CLI</a> ·
|
||||||
|
<a href="#10-feature-flags-and-analytics">Feature Flags</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. What is a Channel
|
||||||
|
|
||||||
|
A Channel is Claude Code's **IM integration system** that allows users to remotely control a running Claude Code Agent through instant messaging platforms such as Telegram, Feishu (Lark), Discord, and Slack.
|
||||||
|
|
||||||
|
### Core Idea
|
||||||
|
|
||||||
|
Traditional AI coding assistants can only interact through the terminal. The Channel system breaks this limitation — you can send messages to Claude Code from your phone via Telegram, and it will understand and execute your requests just as it would in the terminal, replying directly to your chat window.
|
||||||
|
|
||||||
|
### The Essence of a Channel
|
||||||
|
|
||||||
|
From a technical perspective, a Channel is simply a special **MCP (Model Context Protocol) Server** that must:
|
||||||
|
|
||||||
|
1. **Declare capability**: Announce `experimental['claude/channel']` during MCP handshake
|
||||||
|
2. **Push messages**: Send inbound messages via `notifications/claude/channel` notifications
|
||||||
|
3. **Expose tools**: Provide MCP tools like `reply`, `react`, `edit_message` for the Agent to respond through
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Two forms of Channel entries
|
||||||
|
type ChannelEntry =
|
||||||
|
| { kind: 'plugin'; name: string; marketplace: string; dev?: boolean }
|
||||||
|
| { kind: 'server'; name: string; dev?: boolean }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Plugin kind**: Verified plugins from a marketplace (e.g., `plugin:telegram@anthropic`)
|
||||||
|
**Server kind**: Directly specified MCP server names (always requires dev bypass)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Architecture Overview
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### End-to-End Message Flow
|
||||||
|
|
||||||
|
The Channel system follows a clear bidirectional message path:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Inbound (IM → Agent) │
|
||||||
|
│ │
|
||||||
|
│ Telegram/Feishu/Discord │
|
||||||
|
│ ↓ │
|
||||||
|
│ Channel Plugin (MCP Server) │
|
||||||
|
│ ↓ │
|
||||||
|
│ notifications/claude/channel { content, meta } │
|
||||||
|
│ ↓ │
|
||||||
|
│ useManageMCPConnections → registerNotificationHandler │
|
||||||
|
│ ↓ │
|
||||||
|
│ wrapChannelMessage() → <channel source="..." user="..."> │
|
||||||
|
│ ↓ │
|
||||||
|
│ enqueue({ priority: 'next', isMeta: true }) │
|
||||||
|
│ ↓ │
|
||||||
|
│ SleepTool polls hasCommandsInQueue() every ~1s │
|
||||||
|
│ ↓ │
|
||||||
|
│ Model sees <channel> tag, understands message source │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Outbound (Agent → IM) │
|
||||||
|
│ │
|
||||||
|
│ Model decides which tool to use for reply │
|
||||||
|
│ ↓ │
|
||||||
|
│ callTool() → Channel's MCP tools │
|
||||||
|
│ (reply / react / edit_message / download_attachment) │
|
||||||
|
│ ↓ │
|
||||||
|
│ MCP protocol calls Channel Server │
|
||||||
|
│ ↓ │
|
||||||
|
│ Channel Server sends message to IM platform │
|
||||||
|
│ ↓ │
|
||||||
|
│ Telegram/Feishu/Discord user receives reply │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Core Component Map
|
||||||
|
|
||||||
|
| Component | File | Responsibility |
|
||||||
|
|-----------|------|---------------|
|
||||||
|
| **Channel Gate** | `channelNotification.ts` | Six-layer access control |
|
||||||
|
| **Message Wrapper** | `channelNotification.ts` | XML message wrapping |
|
||||||
|
| **Permission Relay** | `channelPermissions.ts` | Remote permission approval |
|
||||||
|
| **Allowlist** | `channelAllowlist.ts` | GrowthBook allowlist management |
|
||||||
|
| **MCP Connection** | `useManageMCPConnections.ts` | Connection mgmt & notification registration |
|
||||||
|
| **Channel Message UI** | `UserChannelMessage.tsx` | Terminal rendering of channel messages |
|
||||||
|
| **Dev Dialog** | `DevChannelsDialog.tsx` | Development mode confirmation dialog |
|
||||||
|
| **Channels Notice** | `ChannelsNotice.tsx` | Startup channel status notifications |
|
||||||
|
| **Plugin Integration** | `mcpPluginIntegration.ts` | Plugin scoped naming |
|
||||||
|
| **State** | `bootstrap/state.ts` | Global channel allowlist state |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Message Protocol
|
||||||
|
|
||||||
|
### 3.1 Inbound Notification Schema
|
||||||
|
|
||||||
|
The notification format Channel Servers push to Claude Code:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// channelNotification.ts
|
||||||
|
const ChannelMessageNotificationSchema = z.object({
|
||||||
|
method: z.literal('notifications/claude/channel'),
|
||||||
|
params: z.object({
|
||||||
|
content: z.string(),
|
||||||
|
// Opaque passthrough — thread_id, user, etc.
|
||||||
|
// Rendered as attributes on the <channel> tag
|
||||||
|
meta: z.record(z.string(), z.string()).optional(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 XML Wrapping
|
||||||
|
|
||||||
|
After receiving the notification, the system wraps it in a `<channel>` XML tag:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// channelNotification.ts:106-116
|
||||||
|
function wrapChannelMessage(
|
||||||
|
serverName: string,
|
||||||
|
content: string,
|
||||||
|
meta?: Record<string, string>,
|
||||||
|
): string {
|
||||||
|
const attrs = Object.entries(meta ?? {})
|
||||||
|
.filter(([k]) => SAFE_META_KEY.test(k)) // Prevent XML injection
|
||||||
|
.map(([k, v]) => ` ${k}="${escapeXmlAttr(v)}"`)
|
||||||
|
.join('')
|
||||||
|
return `<channel source="${escapeXmlAttr(serverName)}"${attrs}>
|
||||||
|
${content}
|
||||||
|
</channel>`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example output**:
|
||||||
|
```xml
|
||||||
|
<channel source="plugin:telegram:tg" user="alice" chat_id="123456">
|
||||||
|
Can you check what's wrong with main.ts?
|
||||||
|
</channel>
|
||||||
|
```
|
||||||
|
|
||||||
|
When the model sees this tag, it knows the message came from Telegram user "alice" and will use Telegram's `reply` tool to respond.
|
||||||
|
|
||||||
|
### 3.3 Safe Metadata Filtering
|
||||||
|
|
||||||
|
Meta keys become XML attribute names. A crafted key like `x="" injected="y` could break out of the attribute structure. The system uses strict regex filtering:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Only allow plain identifier-format key names
|
||||||
|
const SAFE_META_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/
|
||||||
|
```
|
||||||
|
|
||||||
|
In practice, channel servers only send safe keys like `chat_id`, `user`, `thread_ts`, `message_id`.
|
||||||
|
|
||||||
|
### 3.4 Message Enqueuing
|
||||||
|
|
||||||
|
Wrapped messages are pushed into the message queue:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
enqueue({
|
||||||
|
mode: 'prompt',
|
||||||
|
value: wrapChannelMessage(serverName, content, meta),
|
||||||
|
priority: 'next', // High priority
|
||||||
|
isMeta: true, // Metadata message
|
||||||
|
origin: { kind: 'channel', server: serverName },
|
||||||
|
skipSlashCommands: true // Don't interpret as slash commands
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
SleepTool polls `hasCommandsInQueue()` every ~1 second, waking the Agent when new messages arrive.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Six-Layer Access Control
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The Channel system employs **six progressive access control layers**, each capable of independently blocking Channel registration. This is the cornerstone of the system's security.
|
||||||
|
|
||||||
|
### Gate Function Signature
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// channelNotification.ts:191-316
|
||||||
|
function gateChannelServer(
|
||||||
|
serverName: string,
|
||||||
|
capabilities: ServerCapabilities | undefined,
|
||||||
|
pluginSource: string | undefined,
|
||||||
|
): ChannelGateResult // { action: 'register' } | { action: 'skip', kind, reason }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.1 Layer 1: Capability Declaration
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (!capabilities?.experimental?.['claude/channel']) {
|
||||||
|
return { action: 'skip', kind: 'capability',
|
||||||
|
reason: 'server did not declare claude/channel capability' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The MCP Server must declare `experimental['claude/channel']: {}` during handshake. This is MCP's "presence signal" idiom (similar to `tools: {}`), separating Channel Servers from ordinary MCP Servers.
|
||||||
|
|
||||||
|
### 4.2 Layer 2: Runtime Gate
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (!isChannelsEnabled()) {
|
||||||
|
return { action: 'skip', kind: 'disabled',
|
||||||
|
reason: 'channels feature is not currently available' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`isChannelsEnabled()` checks GrowthBook feature flag `tengu_harbor` (default false, 5-minute refresh). This is the global "emergency brake" — flipping this switch immediately disables all Channels without a release.
|
||||||
|
|
||||||
|
### 4.3 Layer 3: OAuth Authentication
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (!getClaudeAIOAuthTokens()?.accessToken) {
|
||||||
|
return { action: 'skip', kind: 'auth',
|
||||||
|
reason: 'channels requires claude.ai authentication (run /login)' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Channels are restricted to OAuth-authenticated users. API key users are blocked because Console doesn't have a `channelsEnabled` admin surface yet.
|
||||||
|
|
||||||
|
### 4.4 Layer 4: Organization Policy
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const sub = getSubscriptionType()
|
||||||
|
const managed = sub === 'team' || sub === 'enterprise'
|
||||||
|
const policy = managed ? getSettingsForSource('policySettings') : undefined
|
||||||
|
if (managed && policy?.channelsEnabled !== true) {
|
||||||
|
return { action: 'skip', kind: 'policy',
|
||||||
|
reason: 'channels not enabled by org policy' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Teams/Enterprise organizations must explicitly enable `channelsEnabled: true` in managed settings. Default is OFF — even a team org with zero configured policy keys is still considered managed and does not fall through to the unmanaged path.
|
||||||
|
|
||||||
|
### 4.5 Layer 5: Session Allowlist
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const entry = findChannelEntry(serverName, getAllowedChannels())
|
||||||
|
if (!entry) {
|
||||||
|
return { action: 'skip', kind: 'session',
|
||||||
|
reason: `server ${serverName} not in --channels list for this session` }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The MCP Server must be in the current session's `--channels` parameter list. Even if a trusted server dynamically adds the `claude/channel` capability, it cannot bypass this — the user must explicitly list it at startup.
|
||||||
|
|
||||||
|
### 4.6 Layer 6: Marketplace Verification + Allowlist
|
||||||
|
|
||||||
|
For plugin-kind channels:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Marketplace verification: ensure installed plugin matches claimed source
|
||||||
|
const actual = pluginSource
|
||||||
|
? parsePluginIdentifier(pluginSource).marketplace
|
||||||
|
: undefined
|
||||||
|
if (actual !== entry.marketplace) {
|
||||||
|
return { action: 'skip', kind: 'marketplace',
|
||||||
|
reason: `tag mismatch: asked for @${entry.marketplace}, installed from ${actual}` }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allowlist check: plugin must be on GrowthBook approved list
|
||||||
|
if (!entry.dev) {
|
||||||
|
const { entries } = getEffectiveChannelAllowlist(sub, policy?.allowedChannelPlugins)
|
||||||
|
if (!entries.some(e => e.plugin === entry.name && e.marketplace === entry.marketplace)) {
|
||||||
|
return { action: 'skip', kind: 'allowlist', reason: 'not on approved list' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dual verification**: First verify that the `@anthropic` tag in `--channels plugin:slack@anthropic` matches the actual installed plugin source (preventing `slack@evil` from impersonating `slack@anthropic`), then check against the approved allowlist.
|
||||||
|
|
||||||
|
**Allowlist source priority**: Team/Enterprise orgs can set `allowedChannelPlugins`, which replaces the GrowthBook ledger allowlist (admin owns the trust decision).
|
||||||
|
|
||||||
|
For server-kind entries, the allowlist always fails (schema is `{marketplace, plugin}` format, server kind cannot match), unless `--dangerously-load-development-channels` is used (setting `entry.dev = true` to bypass).
|
||||||
|
|
||||||
|
### Gate Result Type
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type ChannelGateResult =
|
||||||
|
| { action: 'register' } // Passed all checks, register notification handler
|
||||||
|
| { action: 'skip'; kind: string; reason: string } // Blocked at some layer
|
||||||
|
|
||||||
|
// kind enum: capability | disabled | auth | policy | session | marketplace | allowlist
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Permission Relay System
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 5.1 Why Permission Relay Exists
|
||||||
|
|
||||||
|
When Claude Code needs to execute sensitive operations (like running a Bash command), it shows a permission confirmation dialog. But if the user is controlling the Agent remotely via Telegram, they can't see the local terminal dialog.
|
||||||
|
|
||||||
|
The permission relay system solves this: **forward permission prompts to the IM platform so users can approve or deny operations from their phone**.
|
||||||
|
|
||||||
|
### 5.2 Outbound: CC → Channel (Permission Request)
|
||||||
|
|
||||||
|
When the Agent triggers a permission dialog and a Channel has declared `experimental['claude/channel/permission']` capability:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Notification schema
|
||||||
|
const CHANNEL_PERMISSION_REQUEST_METHOD =
|
||||||
|
'notifications/claude/channel/permission_request'
|
||||||
|
|
||||||
|
type ChannelPermissionRequestParams = {
|
||||||
|
request_id: string // 5-letter identifier (e.g., "tbxkq")
|
||||||
|
tool_name: string // Tool name (e.g., "Bash")
|
||||||
|
description: string // Human-readable description
|
||||||
|
input_preview: string // JSON input preview, truncated to 200 chars
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The Channel Server formats this for its platform (Telegram markdown, Discord embed, etc.) and sends it to the user.
|
||||||
|
|
||||||
|
### 5.3 Short Request ID Generation
|
||||||
|
|
||||||
|
The 5-letter identifier design is thoughtfully crafted:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// channelPermissions.ts:140-152
|
||||||
|
function shortRequestId(toolUseID: string): string {
|
||||||
|
let candidate = hashToId(toolUseID)
|
||||||
|
for (let salt = 0; salt < 10; salt++) {
|
||||||
|
if (!ID_AVOID_SUBSTRINGS.some(bad => candidate.includes(bad))) {
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
candidate = hashToId(`${toolUseID}:${salt}`)
|
||||||
|
}
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Design decisions**:
|
||||||
|
- **25-letter alphabet**: a-z minus `l` (confusion with 1/I), 25^5 ≈ 9.8M combinations
|
||||||
|
- **FNV-1a hash**: Not cryptographic, but stable and fast
|
||||||
|
- **Profanity filter**: 5 random letters can spell offensive words (imagine texting your boss), built-in blocklist
|
||||||
|
- **Letters only**: Phone users don't need to switch keyboard modes (hex alternates between letters and digits)
|
||||||
|
- **Case insensitive**: Accommodates phone autocorrect
|
||||||
|
|
||||||
|
### 5.4 Inbound: Channel → CC (Permission Response)
|
||||||
|
|
||||||
|
Users reply in IM with format: `yes tbxkq` or `no tbxkq`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Server-side parsing regex
|
||||||
|
const PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i
|
||||||
|
|
||||||
|
// Structured notification (server parses and emits, CC doesn't regex-match text)
|
||||||
|
const ChannelPermissionNotificationSchema = z.object({
|
||||||
|
method: z.literal('notifications/claude/channel/permission'),
|
||||||
|
params: z.object({
|
||||||
|
request_id: z.string(),
|
||||||
|
behavior: z.enum(['allow', 'deny']),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key design**: The Channel Server is responsible for parsing the user's reply and emitting a structured event — CC never does text regex matching. This means casual conversation text can never accidentally trigger permission approval.
|
||||||
|
|
||||||
|
### 5.5 Multi-Source Racing
|
||||||
|
|
||||||
|
Permission responses come from four sources, first to resolve wins:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||||
|
│ Local UI │ │ Bridge │ │ Channels │ │ Hooks │
|
||||||
|
│ Terminal │ │ Remote │ │ Telegram etc │ │ Permission │
|
||||||
|
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
|
||||||
|
│ │ │ │
|
||||||
|
└───────────────────┴───────────────────┴──────────────────┘
|
||||||
|
│
|
||||||
|
claim() — first to resolve wins
|
||||||
|
│
|
||||||
|
┌─────┴─────┐
|
||||||
|
│ resolve │
|
||||||
|
│ allow/deny │
|
||||||
|
└───────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
`createChannelPermissionCallbacks()` uses a closure to maintain a pending Map (not at module level, not in AppState — function references in state cause serialization issues), constructed once and stored in AppState.
|
||||||
|
|
||||||
|
### 5.6 Filtering Permission Relay Clients
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// channelPermissions.ts:177-194
|
||||||
|
function filterPermissionRelayClients(clients, isInAllowlist) {
|
||||||
|
return clients.filter(c =>
|
||||||
|
c.type === 'connected' &&
|
||||||
|
isInAllowlist(c.name) &&
|
||||||
|
c.capabilities?.experimental?.['claude/channel'] !== undefined &&
|
||||||
|
c.capabilities?.experimental?.['claude/channel/permission'] !== undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**All three conditions required**: connected + in allowlist + declares BOTH capabilities (`claude/channel` AND `claude/channel/permission`). The second capability is explicit opt-in — a relay-only Channel never accidentally becomes a permission approval surface.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. UI Components
|
||||||
|
|
||||||
|
### 6.1 Terminal Message Rendering (UserChannelMessage)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// UserChannelMessage.tsx
|
||||||
|
// Parses <channel> XML tags and renders them in terminal
|
||||||
|
|
||||||
|
const CHANNEL_RE = new RegExp(
|
||||||
|
`<${CHANNEL_TAG}\\s+source="([^"]+)"([^>]*)>\\n?([\\s\\S]*?)\\n?</${CHANNEL_TAG}>`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Plugin server name display: plugin:slack-channel:slack → slack
|
||||||
|
function displayServerName(name: string): string {
|
||||||
|
const i = name.lastIndexOf(':')
|
||||||
|
return i === -1 ? name : name.slice(i + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const TRUNCATE_AT = 60 // Message body truncation length
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rendered output**:
|
||||||
|
```
|
||||||
|
◁ tg · alice: Can you check what's wrong with main.ts?
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `◁` is the Channel arrow symbol (`CHANNEL_ARROW`), showing the server leaf name and optional username.
|
||||||
|
|
||||||
|
### 6.2 Status Notice (ChannelsNotice)
|
||||||
|
|
||||||
|
Shows the status of `--channels` entries at startup, reporting blockers:
|
||||||
|
|
||||||
|
| Block Type | Meaning |
|
||||||
|
|------------|---------|
|
||||||
|
| `disabled` | Channel feature not enabled (tengu_harbor off) |
|
||||||
|
| `noAuth` | Not OAuth authenticated |
|
||||||
|
| `policyBlocked` | Org policy hasn't enabled channels |
|
||||||
|
| `unmatched` | Not matched in `--channels` list |
|
||||||
|
|
||||||
|
### 6.3 Developer Confirmation Dialog (DevChannelsDialog)
|
||||||
|
|
||||||
|
Warning dialog shown when using `--dangerously-load-development-channels`:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─ WARNING: Loading development channels ──────────────────┐
|
||||||
|
│ │
|
||||||
|
│ --dangerously-load-development-channels is for local │
|
||||||
|
│ channel development only. Do not use this option to │
|
||||||
|
│ run channels you have downloaded off the internet. │
|
||||||
|
│ │
|
||||||
|
│ Please use --channels to run a list of approved channels│
|
||||||
|
│ │
|
||||||
|
│ Channels: plugin:my-channel@local │
|
||||||
|
│ │
|
||||||
|
│ > I am using this for local development │
|
||||||
|
│ Exit │
|
||||||
|
└──────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Selecting "Exit" calls `gracefulShutdownSync(1)` for immediate exit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Plugin Channel Architecture
|
||||||
|
|
||||||
|
### 7.1 Channel Declaration in Plugin Manifest
|
||||||
|
|
||||||
|
Plugins declare channels via the `channels` array in `plugin.json`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// schemas.ts:670-703
|
||||||
|
const PluginManifestChannelsSchema = z.object({
|
||||||
|
channels: z.array(z.object({
|
||||||
|
server: z.string().min(1), // MCP server name, must match key in mcpServers
|
||||||
|
displayName: z.string().optional(), // Config dialog title (e.g., "Telegram")
|
||||||
|
userConfig: z.record( // Fields to prompt user for at install time
|
||||||
|
z.string(),
|
||||||
|
PluginUserConfigOptionSchema()
|
||||||
|
).optional(),
|
||||||
|
}).strict()),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example plugin.json**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "telegram",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"mcpServers": {
|
||||||
|
"tg": {
|
||||||
|
"command": "node",
|
||||||
|
"args": ["./server.js"],
|
||||||
|
"env": {
|
||||||
|
"BOT_TOKEN": "${user_config.bot_token}",
|
||||||
|
"OWNER_ID": "${user_config.owner_id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels": [
|
||||||
|
{
|
||||||
|
"server": "tg",
|
||||||
|
"displayName": "Telegram",
|
||||||
|
"userConfig": {
|
||||||
|
"bot_token": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Telegram Bot API Token",
|
||||||
|
"required": true,
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
"owner_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Your Telegram User ID",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Configuration Flow (PluginOptionsFlow)
|
||||||
|
|
||||||
|
After enabling a plugin, if a Channel has unconfigured `userConfig` fields:
|
||||||
|
|
||||||
|
1. `getUnconfiguredChannels()` detects fields that haven't passed validation
|
||||||
|
2. `PluginOptionsFlow` component prompts the user for each field
|
||||||
|
3. Sensitive values (like bot_token) stored in Keychain
|
||||||
|
4. Regular values stored in `~/.claude/plugins/options/{pluginId}.json`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// mcpPluginIntegration.ts:290-318
|
||||||
|
function getUnconfiguredChannels(plugin: LoadedPlugin): UnconfiguredChannel[] {
|
||||||
|
const channels = plugin.manifest.channels
|
||||||
|
if (!channels || channels.length === 0) return []
|
||||||
|
|
||||||
|
const unconfigured: UnconfiguredChannel[] = []
|
||||||
|
for (const channel of channels) {
|
||||||
|
if (!channel.userConfig) continue
|
||||||
|
const saved = loadMcpServerUserConfig(pluginId, channel.server) ?? {}
|
||||||
|
const validation = validateUserConfig(saved, channel.userConfig)
|
||||||
|
if (!validation.valid) {
|
||||||
|
unconfigured.push({
|
||||||
|
server: channel.server,
|
||||||
|
displayName: channel.displayName ?? channel.server,
|
||||||
|
configSchema: channel.userConfig,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unconfigured
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Scoped Naming
|
||||||
|
|
||||||
|
Plugin-provided MCP Servers get a scope prefix to avoid naming conflicts:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// mcpPluginIntegration.ts:341-360
|
||||||
|
function addPluginScopeToServers(
|
||||||
|
servers: Record<string, McpServerConfig>,
|
||||||
|
pluginName: string,
|
||||||
|
pluginSource: string, // e.g., "telegram@anthropic"
|
||||||
|
): Record<string, ScopedMcpServerConfig> {
|
||||||
|
const scopedServers = {}
|
||||||
|
for (const [name, config] of Object.entries(servers)) {
|
||||||
|
const scopedName = `plugin:${pluginName}:${name}`
|
||||||
|
scopedServers[scopedName] = {
|
||||||
|
...config,
|
||||||
|
scope: 'dynamic',
|
||||||
|
pluginSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scopedServers
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Naming transformation**:
|
||||||
|
- Input: `{ "tg": { ... } }` from `telegram@anthropic`
|
||||||
|
- Output: `{ "plugin:telegram:tg": { scope: 'dynamic', pluginSource: 'telegram@anthropic', ... } }`
|
||||||
|
|
||||||
|
`pluginSource` is preserved on the config for later marketplace verification in the Channel Gate.
|
||||||
|
|
||||||
|
### 7.4 Effective Allowlist Source
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// channelNotification.ts:127-138
|
||||||
|
function getEffectiveChannelAllowlist(sub, orgList) {
|
||||||
|
// Team/Enterprise custom allowlist → replaces GrowthBook ledger
|
||||||
|
if ((sub === 'team' || sub === 'enterprise') && orgList) {
|
||||||
|
return { entries: orgList, source: 'org' }
|
||||||
|
}
|
||||||
|
// Default to GrowthBook ledger
|
||||||
|
return { entries: getChannelAllowlist(), source: 'ledger' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Organization admins can fully control which Channel plugins are trusted via `allowedChannelPlugins`, independent of the global ledger.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Security Design
|
||||||
|
|
||||||
|
### 8.1 XML Injection Prevention
|
||||||
|
|
||||||
|
Channel message metadata becomes XML attributes. Two lines of defense:
|
||||||
|
|
||||||
|
1. **Key name filtering**: `SAFE_META_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/` — only plain identifiers
|
||||||
|
2. **Value escaping**: `escapeXmlAttr()` applies XML escaping to attribute values
|
||||||
|
|
||||||
|
### 8.2 Marketplace Verification
|
||||||
|
|
||||||
|
`--channels plugin:slack@anthropic` is merely the user's "intent declaration." The runtime name `plugin:slack:X` could come from `slack@anthropic` or `slack@evil`. The gate verifies they must match:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const actual = pluginSource
|
||||||
|
? parsePluginIdentifier(pluginSource).marketplace
|
||||||
|
: undefined
|
||||||
|
if (actual !== entry.marketplace) {
|
||||||
|
return { action: 'skip', kind: 'marketplace', reason: '...' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 Trust Boundary of Permission Relay
|
||||||
|
|
||||||
|
From Kenneth's analysis in code comments (PR discussion #2956440848):
|
||||||
|
|
||||||
|
> "Would this let Claude self-approve?" Answer: the approving party is the human via the channel, not Claude. But the trust boundary isn't the terminal — it's the allowlist (tengu_harbor_ledger). A compromised channel server CAN fabricate "yes \<id\>" without the human seeing the prompt. Accepted risk: a compromised channel already has unlimited conversation-injection turns (social-engineer over time, wait for acceptEdits, etc.); inject-then-self-approve is faster, not more capable. The dialog slows a compromised channel; it doesn't stop one.
|
||||||
|
|
||||||
|
### 8.4 skipSlashCommands
|
||||||
|
|
||||||
|
Channel messages are enqueued with `skipSlashCommands: true`, ensuring text like `/help` sent by IM users is not interpreted as Claude Code slash commands.
|
||||||
|
|
||||||
|
### 8.5 Dev Bypass Granularity
|
||||||
|
|
||||||
|
The `dev` flag from `--dangerously-load-development-channels` is **per-entry**, not global. After accepting the dev dialog, only explicitly dev-flagged entries bypass the allowlist — normal `--channels` entries still go through full allowlist verification.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Command-Line Interface
|
||||||
|
|
||||||
|
### 9.1 Startup Parameters
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use approved Channel plugins
|
||||||
|
claude --channels plugin:telegram@anthropic plugin:feishu@anthropic
|
||||||
|
|
||||||
|
# Local development mode (bypass allowlist)
|
||||||
|
claude --dangerously-load-development-channels plugin:my-channel@local
|
||||||
|
|
||||||
|
# Both can be used simultaneously
|
||||||
|
claude --channels plugin:telegram@anthropic \
|
||||||
|
--dangerously-load-development-channels plugin:dev-channel@local
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 Argument Parsing
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// main.tsx
|
||||||
|
const parseChannelEntries = (raw: string[], flag: string): ChannelEntry[] => {
|
||||||
|
// Parse "plugin:slack@anthropic", "server:slack", etc.
|
||||||
|
// Validate format correctness
|
||||||
|
// Return ChannelEntry array
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store in global state
|
||||||
|
setAllowedChannels(channelEntries)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 Feature Gating
|
||||||
|
|
||||||
|
These CLI options are only available when feature flags are enabled:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// main.tsx:3850-3852
|
||||||
|
if (feature('KAIROS') || feature('KAIROS_CHANNELS')) {
|
||||||
|
program.addOption(new Option('--channels <servers...>', '...').hideHelp())
|
||||||
|
program.addOption(new Option('--dangerously-load-development-channels <servers...>', '...').hideHelp())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`hideHelp()` means these options don't appear in `--help` output — the Channel feature is currently in hidden feature stage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Feature Flags and Analytics
|
||||||
|
|
||||||
|
### 10.1 Feature Flags
|
||||||
|
|
||||||
|
| Flag | Source | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `KAIROS` / `KAIROS_CHANNELS` | Build-time | Controls CLI argument registration and code paths |
|
||||||
|
| `tengu_harbor` | GrowthBook runtime | Channel system master switch (default false) |
|
||||||
|
| `tengu_harbor_ledger` | GrowthBook runtime | Approved plugin allowlist |
|
||||||
|
| `tengu_harbor_permissions` | GrowthBook runtime | Permission relay feature switch |
|
||||||
|
|
||||||
|
### 10.2 Analytics Events
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Channel Gate result
|
||||||
|
tengu_mcp_channel_gate: {
|
||||||
|
gate_kind: 'disabled' | 'auth' | 'policy' | 'session' | 'marketplace' | 'allowlist'
|
||||||
|
plugin: string
|
||||||
|
is_dev: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel message
|
||||||
|
tengu_mcp_channel_message: {
|
||||||
|
content_length: number
|
||||||
|
meta_key_count: number
|
||||||
|
entry_kind: 'plugin' | 'server'
|
||||||
|
is_dev: boolean
|
||||||
|
plugin: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Startup flags
|
||||||
|
tengu_mcp_channel_flags: {
|
||||||
|
channels_count: number
|
||||||
|
dev_count: number
|
||||||
|
plugins: string[]
|
||||||
|
dev_plugins: string[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Summary
|
||||||
|
|
||||||
|
The Channel system is Claude Code's **IM integration framework**, and its design embodies several core principles:
|
||||||
|
|
||||||
|
### 1. Security First
|
||||||
|
|
||||||
|
Six access control layers ensure only approved, user-explicitly-chosen Channels can push messages. From build-time feature flags to runtime allowlists, each layer can independently interrupt the flow.
|
||||||
|
|
||||||
|
### 2. Protocol-Driven
|
||||||
|
|
||||||
|
Channels aren't special code — they're ordinary MCP Servers with an additional notification protocol. This means any language that can implement MCP can write Channel plugins.
|
||||||
|
|
||||||
|
### 3. Loose Coupling
|
||||||
|
|
||||||
|
Channel failures don't block local workflows. Permission relay is a multi-source racing mechanism where any source's response is valid.
|
||||||
|
|
||||||
|
### 4. Progressive Trust
|
||||||
|
|
||||||
|
From global switch → authentication → org policy → session allowlist → marketplace verification → allowlist, trust levels increase progressively, with each step serving a clear security purpose.
|
||||||
|
|
||||||
|
### 5. Plugin-Friendly
|
||||||
|
|
||||||
|
Through declarative Channel configuration in `plugin.json`, automatic user config prompting, and scoped naming, third-party developers can easily build their own Channel plugins.
|
||||||
|
|
||||||
|
### Source File Index
|
||||||
|
|
||||||
|
| File | Lines | Responsibility |
|
||||||
|
|------|-------|---------------|
|
||||||
|
| `src/services/mcp/channelNotification.ts` | ~320 | Gating, message wrapping, allowlist integration |
|
||||||
|
| `src/services/mcp/channelPermissions.ts` | ~240 | Permission relay, request ID generation |
|
||||||
|
| `src/services/mcp/channelAllowlist.ts` | ~80 | GrowthBook allowlist queries |
|
||||||
|
| `src/services/mcp/useManageMCPConnections.ts` | — | Connection management, notification handler registration |
|
||||||
|
| `src/components/messages/UserChannelMessage.tsx` | ~140 | Terminal rendering of Channel messages |
|
||||||
|
| `src/components/DevChannelsDialog.tsx` | ~105 | Development mode confirmation dialog |
|
||||||
|
| `src/components/LogoV2/ChannelsNotice.tsx` | — | Startup status notifications |
|
||||||
|
| `src/utils/plugins/mcpPluginIntegration.ts` | — | Plugin MCP integration, scoped naming |
|
||||||
|
| `src/utils/plugins/schemas.ts` | ~700 | Plugin manifest schema (incl. Channel declarations) |
|
||||||
|
| `src/bootstrap/state.ts` | — | Global Channel allowlist state |
|
||||||
|
| `src/main.tsx` | ~3850 | CLI argument registration and parsing |
|
||||||
|
After Width: | Height: | Size: 556 KiB |
|
After Width: | Height: | Size: 487 KiB |
|
After Width: | Height: | Size: 449 KiB |
|
After Width: | Height: | Size: 463 KiB |
@@ -0,0 +1,83 @@
|
|||||||
|
# Claude Code Channel System Documentation
|
||||||
|
|
||||||
|
> Complete technical analysis of remote Agent control via IM platforms
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
### [01-channel-system.md](./01-channel-system.md) — Channel System Architecture
|
||||||
|
|
||||||
|
A deep dive into the design and implementation of Claude Code's Channel system from a source code perspective, covering:
|
||||||
|
|
||||||
|
- **What is a Channel**: Core concepts of IM integration, MCP protocol foundations
|
||||||
|
- **Architecture Overview**: End-to-end message flow, component relationships
|
||||||
|
- **Message Protocol**: Inbound notifications, XML wrapping, outbound tool calls
|
||||||
|
- **Six-Layer Access Control**: Capability → Runtime gate → OAuth → Org policy → Session allowlist → Plugin approval
|
||||||
|
- **Permission Relay System**: Remote tool execution approval, 5-letter request IDs, multi-source racing
|
||||||
|
- **Plugin Architecture**: Channel manifest declarations, user config flow, scoped naming
|
||||||
|
- **UI Components**: Terminal message rendering, status notices, developer warning dialogs
|
||||||
|
- **Security Design**: XML injection prevention, marketplace verification, trust boundary analysis
|
||||||
|
|
||||||
|
**Target audience**: Developers, architects, and plugin authors interested in AI Agent IM integration architecture
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Illustrations
|
||||||
|
|
||||||
|
All illustrations use dark background (#1a1a2e) with Anthropic brand copper-orange (#D97757) accent color.
|
||||||
|
|
||||||
|
| Image | Description | Document |
|
||||||
|
|-------|-------------|----------|
|
||||||
|
| `01-channel-overview.png` | Channel system architecture overview | Architecture |
|
||||||
|
| `02-message-flow.png` | End-to-end message flow: IM → Agent → IM | Architecture |
|
||||||
|
| `03-access-control.png` | Six-layer access control gates | Architecture |
|
||||||
|
| `04-permission-relay.png` | Permission relay system flow | Architecture |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Users
|
||||||
|
|
||||||
|
1. Read the [Channel System Architecture](./01-channel-system.md)
|
||||||
|
2. Learn how to start IM integration with `--channels`
|
||||||
|
3. Understand how different platforms (Telegram, Feishu, Discord) connect
|
||||||
|
|
||||||
|
### Plugin Developers
|
||||||
|
|
||||||
|
1. Read the [Plugin Architecture](./01-channel-system.md#7-plugin-channel-architecture) section
|
||||||
|
2. Understand the `plugin.json` Channel declaration format
|
||||||
|
3. Implement the MCP Server `notifications/claude/channel` protocol
|
||||||
|
4. Key source files:
|
||||||
|
- `src/services/mcp/channelNotification.ts` — Core gating and message wrapping
|
||||||
|
- `src/services/mcp/channelPermissions.ts` — Permission relay system
|
||||||
|
- `src/services/mcp/channelAllowlist.ts` — Allowlist management
|
||||||
|
- `src/utils/plugins/mcpPluginIntegration.ts` — Plugin MCP integration
|
||||||
|
- `src/utils/plugins/schemas.ts` — Plugin manifest Channel schema
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Concepts Reference
|
||||||
|
|
||||||
|
| Concept | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| **Channel** | An MCP Server declaring `claude/channel` capability that can push IM messages to the Agent |
|
||||||
|
| **Channel Entry** | Parsed `--channels` argument entry, either plugin or server kind |
|
||||||
|
| **Channel Gate** | Six-layer access control gate deciding whether to register notification handlers |
|
||||||
|
| **Permission Relay** | Mechanism for forwarding tool execution approval prompts to IM platforms |
|
||||||
|
| **Channel Plugin** | A plugin declaring `channels` field in its `plugin.json` |
|
||||||
|
| **Scoped Name** | Plugin server name with scope prefix: `plugin:{pluginName}:{serverName}` |
|
||||||
|
| **Short Request ID** | 5-letter permission request identifier generated via FNV-1a hash |
|
||||||
|
| **Channel Tag** | `<channel>` XML tag wrapping IM message content and metadata |
|
||||||
|
| **Dev Channels** | Channels loaded via `--dangerously-load-development-channels` for local development |
|
||||||
|
| **tengu_harbor** | GrowthBook runtime feature flag controlling the Channel system master switch |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Resources
|
||||||
|
|
||||||
|
- [Claude Code Haha Home](/en/)
|
||||||
|
- [Agent Framework Deep Dive](/en/agent/03-agent-framework)
|
||||||
|
- [Skills System Documentation](/en/skills/01-usage-guide)
|
||||||
|
- [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues)
|
||||||
@@ -0,0 +1,602 @@
|
|||||||
|
# Computer Use Architecture Deep Dive
|
||||||
|
|
||||||
|
> A deep dive into the Computer Use implementation: from MCP tool definitions to Python Bridge, from 9-layer security gates to feature flag bypasses.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#1-patch-environment-overview">Patch Environment</a> ·
|
||||||
|
<a href="#2-layered-architecture">Layered Architecture</a> ·
|
||||||
|
<a href="#3-mcp-tool-layer">MCP Tool Layer</a> ·
|
||||||
|
<a href="#4-security-gate-system">Security Gates</a> ·
|
||||||
|
<a href="#5-python-bridge-mechanism">Python Bridge</a> ·
|
||||||
|
<a href="#6-screenshot-analyze-act-loop">Interaction Loop</a> ·
|
||||||
|
<a href="#7-key-source-file-index">Source File Index</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Patch Environment Overview
|
||||||
|
|
||||||
|
The original Claude Code's Computer Use feature (internal codename **Chicago**) depends on three components that are not publicly available:
|
||||||
|
|
||||||
|
| Component | Purpose | Availability |
|
||||||
|
|-----------|---------|-------------|
|
||||||
|
| `@ant/computer-use-swift` | Screenshots, display enumeration | Anthropic private npm package |
|
||||||
|
| `@ant/computer-use-input` | Mouse/keyboard simulation | Anthropic private npm package |
|
||||||
|
| GrowthBook remote config | Feature flags, kill switch | Anthropic internal service |
|
||||||
|
|
||||||
|
Our approach: **preserve the original MCP tool definitions and security mechanisms, only replace the execution layer and feature flag controls**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### What We Changed
|
||||||
|
|
||||||
|
```
|
||||||
|
Original Claude Code Claude Code Haha (Patched)
|
||||||
|
──────────────────── ─────────────────────────
|
||||||
|
@ant/computer-use-swift ──replaced──→ Python Bridge (mac_helper.py)
|
||||||
|
@ant/computer-use-input ──replaced──→ pyautogui + pyobjc
|
||||||
|
GrowthBook feature flags ──bypassed──→ gates.ts hardcoded return true
|
||||||
|
Subscription check (Max/Pro) ──bypassed──→ getChicagoEnabled() = true
|
||||||
|
Build macro CHICAGO_MCP ──replaced──→ true
|
||||||
|
isDefaultDisabledBuiltin ──modified──→ returns false
|
||||||
|
```
|
||||||
|
|
||||||
|
### What We Kept Intact
|
||||||
|
|
||||||
|
- **MCP tool definitions** (24 tools with their schema and parameter validation)
|
||||||
|
- **9-layer security gates** (TCC permissions, app allowlist, permission tiers, pixel validation, etc.)
|
||||||
|
- **App classification system** (191 bundle IDs categorized with permission mappings)
|
||||||
|
- **Session context management** (global lock, screenshot cache, state synchronization)
|
||||||
|
- **Keyboard shortcut blocklist** (system-level dangerous operation interception)
|
||||||
|
|
||||||
|
### Feature Flag Bypass Details
|
||||||
|
|
||||||
|
The original code uses three layers of gating to restrict Computer Use access:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Original code (simplified)
|
||||||
|
function getChicagoEnabled(): boolean {
|
||||||
|
// Layer 1: GrowthBook remote config
|
||||||
|
const config = getDynamicConfig('tengu_malort_pedway')
|
||||||
|
// Layer 2: Subscription check
|
||||||
|
const hasSubscription = hasRequiredSubscription() // Max/Pro
|
||||||
|
// Layer 3: Build-time macro
|
||||||
|
return feature('CHICAGO_MCP') && config.enabled && hasSubscription
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Our modification:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// gates.ts — our change
|
||||||
|
export function getChicagoEnabled(): boolean {
|
||||||
|
return true // ← all three gate layers bypassed
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: Sub-gates (pixelValidation, mouseAnimation, etc.) still retain the original logic and can be controlled via configuration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Layered Architecture
|
||||||
|
|
||||||
|
Computer Use uses a **6-layer architecture** with clear responsibilities and boundaries:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Layer 1 — MCP Tool Interface │
|
||||||
|
│ tools.ts: 24 tool schemas + parameter validation │
|
||||||
|
│ buildComputerUseTools() → MCP Tool Definition │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ Layer 2 — Tool Dispatch & Security Control │
|
||||||
|
│ toolCalls.ts: handleToolCall() + 9 security gates │
|
||||||
|
│ deniedApps.ts: 191 app classifications + permission tiers │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ Layer 3 — MCP Server Binding │
|
||||||
|
│ mcpServer.ts: session context + global lock + screenshot │
|
||||||
|
│ bindSessionContext() → per-call overrides │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ Layer 4 — CLI Integration │
|
||||||
|
│ wrapper.tsx: permission dialogs + state read/write │
|
||||||
|
│ setup.ts: MCP config initialization │
|
||||||
|
│ gates.ts: feature flags (bypassed) │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ Layer 5 — Python Bridge IPC [PATCH] │
|
||||||
|
│ pythonBridge.ts: venv mgmt + JSON RPC + error handling │
|
||||||
|
│ callPythonHelper<T>(command, payload) → T │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ Layer 6 — Python Runtime Execution [PATCH] │
|
||||||
|
│ mac_helper.py: pyautogui + mss + pyobjc │
|
||||||
|
│ 660 lines of Python implementing all system interactions │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Layers marked **[PATCH]** are our replaced/new code. **All other layers** are preserved from the original Claude Code.
|
||||||
|
|
||||||
|
### Why This Layering?
|
||||||
|
|
||||||
|
| Layer | Source | Reusability |
|
||||||
|
|-------|--------|------------|
|
||||||
|
| Layer 1-2 | `vendor/computer-use-mcp/` | Platform-agnostic, reusable for Electron, Web hosts |
|
||||||
|
| Layer 3 | `vendor/computer-use-mcp/` | Platform-agnostic, standard MCP protocol |
|
||||||
|
| Layer 4 | `utils/computerUse/` | CLI-specific, bound to app state |
|
||||||
|
| Layer 5-6 | `utils/computerUse/` + `runtime/` | macOS-specific, Python implementation |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. MCP Tool Layer
|
||||||
|
|
||||||
|
### 24 Tools Overview
|
||||||
|
|
||||||
|
Computer Use exposes 24 tools to the model via MCP (Model Context Protocol):
|
||||||
|
|
||||||
|
| Category | Tools | Permission Tier |
|
||||||
|
|----------|-------|----------------|
|
||||||
|
| **Permission** | `request_access`, `list_granted_applications` | None required |
|
||||||
|
| **Screenshot** | `screenshot`, `zoom` | read |
|
||||||
|
| **Mouse Click** | `left_click`, `double_click`, `triple_click` | click |
|
||||||
|
| **Mouse Advanced** | `right_click`, `middle_click`, `left_click_drag` | full |
|
||||||
|
| **Mouse Move** | `mouse_move`, `cursor_position`, `scroll` | click |
|
||||||
|
| **Mouse Low-level** | `left_mouse_down`, `left_mouse_up` | full |
|
||||||
|
| **Keyboard** | `type`, `key`, `hold_key` | full |
|
||||||
|
| **Application** | `open_application`, `switch_display` | full |
|
||||||
|
| **Clipboard** | `read_clipboard`, `write_clipboard` | full |
|
||||||
|
| **Batch** | `computer_batch` | Inherits from sub-operations |
|
||||||
|
| **Wait** | `wait` | None required |
|
||||||
|
|
||||||
|
### Coordinate System
|
||||||
|
|
||||||
|
The model interacts with the screen through two coordinate modes:
|
||||||
|
|
||||||
|
```
|
||||||
|
pixels mode (default):
|
||||||
|
Model sees screenshot size (1176 x 784)
|
||||||
|
Model outputs coordinate [588, 392]
|
||||||
|
scaleCoord() conversion:
|
||||||
|
x_logical = (588 * displayWidth / 1176) + originX
|
||||||
|
y_logical = (392 * displayHeight / 784) + originY
|
||||||
|
|
||||||
|
normalized_0_100 mode:
|
||||||
|
Model outputs coordinate [50, 50] (percentage)
|
||||||
|
scaleCoord() conversion:
|
||||||
|
x_logical = (50 / 100) * displayWidth + originX
|
||||||
|
y_logical = (50 / 100) * displayHeight + originY
|
||||||
|
```
|
||||||
|
|
||||||
|
Screenshot dimensions are calculated by `imageResize.ts` to ensure:
|
||||||
|
- Long edge <= 1568 pixels
|
||||||
|
- Token budget <= 1568 (vision encoder at 28px/token)
|
||||||
|
- Aspect ratio preserved
|
||||||
|
|
||||||
|
### App Classification System
|
||||||
|
|
||||||
|
`deniedApps.ts` precisely classifies 191 applications:
|
||||||
|
|
||||||
|
**Browsers** (55 bundle IDs) -> tier `read`
|
||||||
|
```
|
||||||
|
Safari, Chrome, Firefox, Arc, Edge, Opera, Brave, Vivaldi...
|
||||||
|
Reason: browser operations should use Chrome MCP, not blind clicking
|
||||||
|
```
|
||||||
|
|
||||||
|
**Terminals** (102 bundle IDs) -> tier `click`
|
||||||
|
```
|
||||||
|
Terminal, iTerm2, VS Code, Cursor, JetBrains IDEs, Xcode...
|
||||||
|
Reason: terminal operations should use Bash Tool, limited to click only
|
||||||
|
```
|
||||||
|
|
||||||
|
**Trading** (34 bundle IDs) -> tier `read`
|
||||||
|
```
|
||||||
|
Webull, Fidelity, Interactive Brokers, Binance, Kraken...
|
||||||
|
Reason: financial operations are extremely high-risk, screenshot only
|
||||||
|
```
|
||||||
|
|
||||||
|
**Completely Blocked** (policy deny list):
|
||||||
|
```
|
||||||
|
Netflix, Spotify, Apple Music, Kindle...
|
||||||
|
Reason: copyright compliance, rejected without permission dialog
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Security Gate System
|
||||||
|
|
||||||
|
Every input action (click, keyboard, drag) must pass through **9 security gates** before execution:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Gate Details
|
||||||
|
|
||||||
|
#### Gate 1: Kill Switch
|
||||||
|
```typescript
|
||||||
|
if (adapter.isDisabled()) return errorResult("Computer Use is disabled")
|
||||||
|
```
|
||||||
|
Reads `getChicagoEnabled()` — always returns `true` in patched version.
|
||||||
|
|
||||||
|
#### Gate 2: TCC Permission Check
|
||||||
|
```typescript
|
||||||
|
await adapter.ensureOsPermissions()
|
||||||
|
// → Python: check_permissions
|
||||||
|
// → Accessibility: osascript "tell System Events..."
|
||||||
|
// → Screen Recording: CGDisplayCaptureDisplay()
|
||||||
|
```
|
||||||
|
Reports error if macOS Accessibility or Screen Recording permissions are missing.
|
||||||
|
|
||||||
|
#### Gate 3: Global Mutex Lock
|
||||||
|
```typescript
|
||||||
|
await tryAcquireComputerUseLock(sessionId)
|
||||||
|
// File lock: ~/.claude/computer-use.lock
|
||||||
|
// JSON: { sessionId, pid, acquiredAt }
|
||||||
|
```
|
||||||
|
Ensures only one Claude session can control the computer at a time. Supports stale PID recovery.
|
||||||
|
|
||||||
|
#### Gate 4: Hide Non-Allowlisted Apps
|
||||||
|
```typescript
|
||||||
|
await executor.prepareForAction(allowlistBundleIds)
|
||||||
|
// Hide all app windows not in the allowlist
|
||||||
|
// Ensures screenshots only contain authorized apps
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Gate 5: Frontmost App Check
|
||||||
|
```typescript
|
||||||
|
const frontmost = await executor.getFrontmostApp()
|
||||||
|
if (!allowlist.includes(frontmost.bundleId)) {
|
||||||
|
return errorResult("Application not authorized")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Even after passing the allowlist, verifies the current foreground app is authorized.
|
||||||
|
|
||||||
|
#### Gate 6: Permission Tier Check
|
||||||
|
|
||||||
|
Three-tier permission model:
|
||||||
|
|
||||||
|
| Tier | Allowed Operations | Prohibited Operations |
|
||||||
|
|------|-------------------|----------------------|
|
||||||
|
| `read` | Screenshot viewing | Any input action |
|
||||||
|
| `click` | Left click, scroll | Right-click, drag, keyboard input |
|
||||||
|
| `full` | All operations | No restrictions |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function tierSatisfies(tier: CuAppPermTier, required: ActionKind): boolean {
|
||||||
|
const order = { read: 0, click: 1, full: 2 }
|
||||||
|
return order[tier] >= order[required]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Anti-subversion**: If permissions are insufficient, the response includes a `TIER_ANTI_SUBVERSION` hint to prevent the model from bypassing restrictions via AppleScript or System Events.
|
||||||
|
|
||||||
|
#### Gate 7: Clipboard Guard
|
||||||
|
|
||||||
|
**Threat model**:
|
||||||
|
```
|
||||||
|
1. Agent calls write_clipboard("rm -rf /")
|
||||||
|
2. Switches to Terminal (click-tier allows clicking)
|
||||||
|
3. Model clicks Terminal's paste button
|
||||||
|
4. Malicious command executed
|
||||||
|
```
|
||||||
|
|
||||||
|
**Protection**:
|
||||||
|
```
|
||||||
|
When click-tier app becomes frontmost:
|
||||||
|
→ Save current clipboard content (stash)
|
||||||
|
→ Clear clipboard
|
||||||
|
→ Re-clear after each operation
|
||||||
|
|
||||||
|
When non-click-tier app becomes frontmost:
|
||||||
|
→ Restore original clipboard content
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Gate 8: Pixel Validation (Staleness Guard)
|
||||||
|
|
||||||
|
```
|
||||||
|
Last screenshot Current actual screen
|
||||||
|
┌────────────┐ ┌────────────┐
|
||||||
|
│ Button A │ │ Dialog │ ← UI has changed
|
||||||
|
│ [756,342] │ │ Confirm? │
|
||||||
|
└────────────┘ └────────────┘
|
||||||
|
|
||||||
|
Validation: sample 9x9 pixel grid at [756,342]
|
||||||
|
→ Compare last screenshot vs live screenshot pixels
|
||||||
|
→ Different → reject click + prompt to re-screenshot
|
||||||
|
→ Same → allow click
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: In patched version, `pixelValidation` is off by default (`hostAdapter.cropRawPatch()` returns null).
|
||||||
|
|
||||||
|
#### Gate 9: System Shortcut Interception
|
||||||
|
|
||||||
|
`keyBlocklist.ts` blocks dangerous shortcuts:
|
||||||
|
|
||||||
|
| Shortcut | Dangerous Action |
|
||||||
|
|----------|-----------------|
|
||||||
|
| `Cmd+Q` | Quit application |
|
||||||
|
| `Shift+Cmd+Q` | Log out |
|
||||||
|
| `Option+Cmd+Esc` | Force quit dialog |
|
||||||
|
| `Cmd+Tab` | App switcher |
|
||||||
|
| `Cmd+Space` | Spotlight |
|
||||||
|
| `Ctrl+Cmd+Q` | Lock screen |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Python Bridge Mechanism
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Architecture Design
|
||||||
|
|
||||||
|
Original Claude Code uses compiled Swift native modules (.node NAPI plugins) to directly call macOS APIs. We replaced this layer with **Python subprocess + JSON RPC**:
|
||||||
|
|
||||||
|
```
|
||||||
|
TypeScript (Bun Runtime) Python (venv)
|
||||||
|
┌────────────────────┐ ┌────────────────────┐
|
||||||
|
│ executor.ts │ │ mac_helper.py │
|
||||||
|
│ │ execFile() │ │
|
||||||
|
│ callPythonHelper │ ──────────────→ │ main() │
|
||||||
|
│ ('click', │ command + │ ├─ parse argv │
|
||||||
|
│ {x:756,y:342}) │ --payload JSON │ ├─ dispatch() │
|
||||||
|
│ │ │ └─ click() │
|
||||||
|
│ ← JSON.parse ──── │ ←────────────── │ pyautogui │
|
||||||
|
│ {ok:true, │ stdout JSON │ │
|
||||||
|
│ result:true} │ │ json_output(...) │
|
||||||
|
└────────────────────┘ └────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bootstrap Flow
|
||||||
|
|
||||||
|
First call to `callPythonHelper()` automatically completes environment setup:
|
||||||
|
|
||||||
|
```
|
||||||
|
ensureBootstrapped()
|
||||||
|
│
|
||||||
|
├─ Check if .runtime/venv/bin/python3 exists
|
||||||
|
│ └─ Missing → python3 -m venv .runtime/venv/
|
||||||
|
│
|
||||||
|
├─ Check if pip is available
|
||||||
|
│ └─ Missing → python3 -m ensurepip --upgrade
|
||||||
|
│
|
||||||
|
├─ Compute SHA256 of runtime/requirements.txt
|
||||||
|
│ └─ Compare with .runtime/requirements.sha256
|
||||||
|
│ └─ Different → pip install -r requirements.txt
|
||||||
|
│ Write new SHA256 hash
|
||||||
|
│ └─ Same → skip installation
|
||||||
|
│
|
||||||
|
└─ Ready, return venv Python path
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dependencies** (`runtime/requirements.txt`):
|
||||||
|
|
||||||
|
| Library | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| `mss` | High-performance screen capture |
|
||||||
|
| `Pillow` | JPEG encoding and image processing |
|
||||||
|
| `pyautogui` | Mouse click, keyboard input |
|
||||||
|
| `pyobjc-core` | macOS Objective-C bridge |
|
||||||
|
| `pyobjc-framework-Cocoa` | NSWorkspace (app management), NSPasteboard (clipboard) |
|
||||||
|
| `pyobjc-framework-Quartz` | CGDisplay (monitors), CGWindow (window list) |
|
||||||
|
|
||||||
|
### Command Mapping
|
||||||
|
|
||||||
|
`mac_helper.py` (660 lines) implements the following commands:
|
||||||
|
|
||||||
|
| Command | Python Implementation | Return Value |
|
||||||
|
|---------|----------------------|-------------|
|
||||||
|
| `screenshot` | `mss.grab()` + `PIL.Image` JPEG encoding | `{base64, width, height, displayWidth, displayHeight}` |
|
||||||
|
| `zoom` | `mss.grab(region)` region capture | `{base64, width, height}` |
|
||||||
|
| `click` | `pyautogui.moveTo()` + `pyautogui.click()` | `true` |
|
||||||
|
| `key` | `pyautogui.hotkey()` / `pyautogui.press()` | `true` |
|
||||||
|
| `type` | `pyautogui.write(interval=0.008)` | `true` |
|
||||||
|
| `drag` | `pyautogui.dragTo(duration=0.2)` | `true` |
|
||||||
|
| `scroll` | `pyautogui.scroll()` / `pyautogui.hscroll()` | `true` |
|
||||||
|
| `hold_key` | `pyautogui.keyDown()` + `sleep` + `pyautogui.keyUp()` | `true` |
|
||||||
|
| `frontmost_app` | `NSWorkspace.frontmostApplication()` | `{bundleId, displayName}` |
|
||||||
|
| `list_displays` | `CGGetActiveDisplayList()` + `CGDisplayBounds()` | `[DisplayGeometry...]` |
|
||||||
|
| `open_app` | `NSWorkspace.launchApplicationAtURL_options_` | `void` |
|
||||||
|
| `read_clipboard` | `NSPasteboard.stringForType_()` | `string` |
|
||||||
|
| `write_clipboard` | `NSPasteboard.setString_forType_()` | `void` |
|
||||||
|
| `check_permissions` | `osascript` + `CGDisplayCaptureDisplay` | `{accessibility, screenRecording}` |
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
```python
|
||||||
|
# mac_helper.py unified error handling
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
result = dispatch(command, payload)
|
||||||
|
json_output({"ok": True, "result": result})
|
||||||
|
except Exception as e:
|
||||||
|
error_output({"ok": False, "error": {"message": str(e)}})
|
||||||
|
```
|
||||||
|
|
||||||
|
TypeScript side:
|
||||||
|
```typescript
|
||||||
|
// pythonBridge.ts
|
||||||
|
const parsed = JSON.parse(stdout)
|
||||||
|
if (!parsed.ok) {
|
||||||
|
throw new Error(parsed.error.message) // → MCP tool error
|
||||||
|
}
|
||||||
|
return parsed.result
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Screenshot-Analyze-Act Loop
|
||||||
|
|
||||||
|
A complete Computer Use interaction consists of multiple **screenshot-analyze-act** cycles:
|
||||||
|
|
||||||
|
### Typical Interaction Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
User: "Open NetEase Music and search for a song"
|
||||||
|
|
||||||
|
┌─ Cycle 1: Discover and open application ─────────────────┐
|
||||||
|
│ │
|
||||||
|
│ Step 1: request_access │
|
||||||
|
│ → Permission dialog, user authorizes allowed apps │
|
||||||
|
│ → Set allowedApps, grantFlags │
|
||||||
|
│ │
|
||||||
|
│ Step 2: screenshot │
|
||||||
|
│ → Full screen capture → JPEG encode → base64 │
|
||||||
|
│ → Cache screenshot dimensions (lastScreenshotDims) │
|
||||||
|
│ → Return to model │
|
||||||
|
│ │
|
||||||
|
│ Step 3: Model analyzes screenshot │
|
||||||
|
│ → "NetEase Music not on desktop, need to open it" │
|
||||||
|
│ → Decides to call open_application │
|
||||||
|
│ │
|
||||||
|
│ Step 4: open_application("com.netease.163music") │
|
||||||
|
│ → Gates 1-9 all pass │
|
||||||
|
│ → Python: NSWorkspace.launchApplicationAtURL_() │
|
||||||
|
│ │
|
||||||
|
└───────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─ Cycle 2: Locate search box ─────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ Step 5: screenshot │
|
||||||
|
│ → Full screen (app now open) │
|
||||||
|
│ → Update lastScreenshotDims │
|
||||||
|
│ │
|
||||||
|
│ Step 6: Model analyzes screenshot │
|
||||||
|
│ → Vision identifies search box at (756, 342) │
|
||||||
|
│ → Decides to click search box │
|
||||||
|
│ │
|
||||||
|
│ Step 7: left_click({coordinate: [756, 342]}) │
|
||||||
|
│ → Gate 4: Hide non-allowlisted apps │
|
||||||
|
│ → Gate 5: Frontmost is NetEase Music ✓ │
|
||||||
|
│ → Gate 6: tier=full >= click ✓ │
|
||||||
|
│ → scaleCoord(756, 342) → screen coordinates │
|
||||||
|
│ → Python: pyautogui.click(x_logical, y_logical) │
|
||||||
|
│ │
|
||||||
|
└───────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─ Cycle 3: Type search query ─────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ Step 8: type({text: "my favorite song"}) │
|
||||||
|
│ → Gate 6: tier=full >= keyboard ✓ │
|
||||||
|
│ → Python: pyautogui.write("...", interval=0.008) │
|
||||||
|
│ │
|
||||||
|
│ Step 9: screenshot │
|
||||||
|
│ → Confirm search results appeared │
|
||||||
|
│ │
|
||||||
|
│ Step 10: left_click({coordinate: [...]}) │
|
||||||
|
│ → Click target song │
|
||||||
|
│ │
|
||||||
|
└───────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Coordinate Conversion
|
||||||
|
|
||||||
|
**What the model sees** vs **actual screen** are different coordinate spaces:
|
||||||
|
|
||||||
|
```
|
||||||
|
Physical screen (2560 x 1600, Retina 2x)
|
||||||
|
├─ Logical size: 1280 x 800
|
||||||
|
└─ Physical pixels: 2560 x 1600
|
||||||
|
|
||||||
|
After imageResize:
|
||||||
|
├─ Scaled size: 1176 x 735 (≤1568px budget)
|
||||||
|
└─ This is the screenshot size the model "sees"
|
||||||
|
|
||||||
|
Model outputs coordinate: [588, 368] (in image space)
|
||||||
|
|
||||||
|
scaleCoord conversion:
|
||||||
|
x_logical = (588 / 1176) * 1280 + originX = 640
|
||||||
|
y_logical = (368 / 735) * 800 + originY = 400
|
||||||
|
|
||||||
|
Python executes:
|
||||||
|
pyautogui.moveTo(640, 400) ← logical coords (macOS handles Retina)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Screenshot Cache & State Sync
|
||||||
|
|
||||||
|
```
|
||||||
|
bindSessionContext closure
|
||||||
|
│
|
||||||
|
├─ lastScreenshot (in-memory)
|
||||||
|
│ ├─ base64: JPEG data (for pixel validation)
|
||||||
|
│ ├─ width/height: model-visible dimensions
|
||||||
|
│ └─ displayWidth/displayHeight/originX/originY: display geometry
|
||||||
|
│
|
||||||
|
└─ AppState.computerUseMcpState (persisted)
|
||||||
|
├─ allowedApps: AppGrant[] — authorized app list
|
||||||
|
├─ grantFlags: {...} — clipboard/system shortcut permissions
|
||||||
|
├─ selectedDisplayId?: number — selected display
|
||||||
|
├─ lastScreenshotDims?: {...} — screenshot geometry (survives restart)
|
||||||
|
└─ hiddenDuringTurn?: Set<string> — apps hidden this turn
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Key Source File Index
|
||||||
|
|
||||||
|
### vendor/computer-use-mcp/ (Original Code Layer)
|
||||||
|
|
||||||
|
| File | Lines | Responsibility |
|
||||||
|
|------|-------|---------------|
|
||||||
|
| `types.ts` | 622 | Permission model, session context, all type definitions |
|
||||||
|
| `tools.ts` | 707 | 24 MCP tool schemas and parameter validation |
|
||||||
|
| `toolCalls.ts` | 1600+ | **Core**: tool dispatch, 9 security gates, permission flow |
|
||||||
|
| `deniedApps.ts` | 554 | 191 app classifications (browser/terminal/trading) and permission mappings |
|
||||||
|
| `sentinelApps.ts` | 44 | Sensitive app warning labels (shell/filesystem/system_settings) |
|
||||||
|
| `mcpServer.ts` | 314 | MCP server factory, session context binding, global lock |
|
||||||
|
| `pixelCompare.ts` | 172 | Click target pixel validation (staleness guard) |
|
||||||
|
| `imageResize.ts` | 109 | Screenshot dimension calculation (API image transcoder) |
|
||||||
|
| `keyBlocklist.ts` | 154 | System shortcut interception (Cmd+Q, Cmd+Tab, etc.) |
|
||||||
|
| `executor.ts` | 101 | ComputerExecutor interface definition |
|
||||||
|
| `subGates.ts` | 20 | Feature flag sub-gate presets |
|
||||||
|
|
||||||
|
### utils/computerUse/ (CLI Adaptation Layer)
|
||||||
|
|
||||||
|
| File | Lines | Responsibility | Patched? |
|
||||||
|
|------|-------|---------------|----------|
|
||||||
|
| `executor.ts` | 231 | ComputerExecutor Python bridge implementation | Yes, rewritten |
|
||||||
|
| `pythonBridge.ts` | 111 | Python subprocess management, venv bootstrap, JSON RPC | Yes, new |
|
||||||
|
| `hostAdapter.ts` | 54 | HostAdapter implementation (permission checks, flag reading) | Partially |
|
||||||
|
| `gates.ts` | 51 | GrowthBook feature flags (`getChicagoEnabled` bypass) | Yes, modified |
|
||||||
|
| `wrapper.tsx` | 300+ | Session context construction, permission dialogs, lock management | Unchanged |
|
||||||
|
| `setup.ts` | 54 | MCP config initialization | Unchanged |
|
||||||
|
| `computerUseLock.ts` | 216 | Global file lock (`~/.claude/computer-use.lock`) | Unchanged |
|
||||||
|
| `common.ts` | 62 | Constants (server name, bundle ID) | Unchanged |
|
||||||
|
| `cleanup.ts` | — | Turn-end cleanup (app restore, clipboard restore) | Unchanged |
|
||||||
|
| `toolRendering.tsx` | — | Tool result UI rendering | Unchanged |
|
||||||
|
|
||||||
|
### runtime/ (Python Runtime)
|
||||||
|
|
||||||
|
| File | Lines | Responsibility | Patched? |
|
||||||
|
|------|-------|---------------|----------|
|
||||||
|
| `mac_helper.py` | 660 | All system interactions in Python | Yes, new |
|
||||||
|
| `requirements.txt` | 6 | Python dependency declarations | Yes, new |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Design Trade-offs
|
||||||
|
|
||||||
|
### Why Python Bridge?
|
||||||
|
|
||||||
|
| Dimension | Native Swift (.node) | Python Bridge |
|
||||||
|
|-----------|---------------------|---------------|
|
||||||
|
| **Performance** | ~0ms (in-process call) | ~50-100ms (subprocess startup) |
|
||||||
|
| **Readability** | Not readable after compilation | 660 lines of clear Python |
|
||||||
|
| **Modifiability** | Requires Swift build environment | Edit .py files directly |
|
||||||
|
| **Dependencies** | Specific Bun version NAPI | Any Python 3.8+ |
|
||||||
|
| **Cross-platform** | macOS only | pyautogui/mss are natively cross-platform |
|
||||||
|
| **User experience** | Imperceptible | Imperceptible (model thinking takes 2-5s) |
|
||||||
|
|
||||||
|
**Conclusion**: The 50-100ms extra latency is completely negligible in Computer Use scenarios — the model typically takes 2-5 seconds for screenshot analysis and decision-making, so users won't notice the additional 100ms for underlying operations.
|
||||||
|
|
||||||
|
### Approaches We Tried But Abandoned
|
||||||
|
|
||||||
|
**Approach 1: Extract native .node modules**
|
||||||
|
- Successfully extracted `computer-use-swift.node` (ARM64 424KB) from the Claude Code binary
|
||||||
|
- Synchronous methods worked, but **Swift async method continuations never resumed**
|
||||||
|
- Root cause: .node files compiled for Claude Code's built-in Bun, incompatible with user's Bun version
|
||||||
|
|
||||||
|
**Approach 2: Empty stub packages**
|
||||||
|
- Code compiled but all operations threw errors — no actual execution capability
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Computer Use Guide](./computer-use.md) — Usage, quick start, environment variables
|
||||||
|
- [Source Fixes](/en/reference/fixes) — Detailed records of other fixes and patches
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
# Computer Use Guide
|
# Computer Use Guide
|
||||||
|
|
||||||
<p align="right"><a href="./computer-use.md">中文</a> | <strong>English</strong></p>
|
|
||||||
|
|
||||||
> **Modified Version**: This feature is a **heavily modified version** of the Computer Use (internal codename "Chicago") found in the leaked Claude Code source. The official implementation relies on Anthropic's private native modules (`@ant/computer-use-swift`, `@ant/computer-use-input`) that are not publicly available. We **replaced the entire underlying operation layer** with a Python bridge (`pyautogui` + `mss` + `pyobjc`), enabling anyone to run Computer Use on macOS.
|
> **Modified Version**: This feature is a **heavily modified version** of the Computer Use (internal codename "Chicago") found in the leaked Claude Code source. The official implementation relies on Anthropic's private native modules (`@ant/computer-use-swift`, `@ant/computer-use-input`) that are not publicly available. We **replaced the entire underlying operation layer** with a Python bridge (`pyautogui` + `mss` + `pyobjc`), enabling anyone to run Computer Use on macOS.
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 888 KiB |
|
After Width: | Height: | Size: 523 KiB |
|
After Width: | Height: | Size: 912 KiB |
|
After Width: | Height: | Size: 800 KiB |
@@ -0,0 +1,61 @@
|
|||||||
|
# Environment Variables
|
||||||
|
|
||||||
|
|
||||||
|
| Variable | Required | Description |
|
||||||
|
|------|------|------|
|
||||||
|
| `ANTHROPIC_API_KEY` | One of two | API key sent via the `x-api-key` header |
|
||||||
|
| `ANTHROPIC_AUTH_TOKEN` | One of two | Auth token sent via the `Authorization: Bearer` header |
|
||||||
|
| `ANTHROPIC_BASE_URL` | No | Custom API endpoint, defaults to Anthropic |
|
||||||
|
| `ANTHROPIC_MODEL` | No | Default model |
|
||||||
|
| `ANTHROPIC_DEFAULT_SONNET_MODEL` | No | Sonnet-tier model mapping |
|
||||||
|
| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | No | Haiku-tier model mapping |
|
||||||
|
| `ANTHROPIC_DEFAULT_OPUS_MODEL` | No | Opus-tier model mapping |
|
||||||
|
| `API_TIMEOUT_MS` | No | API request timeout, default `600000` (10min) |
|
||||||
|
| `DISABLE_TELEMETRY` | No | Set to `1` to disable telemetry |
|
||||||
|
| `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | No | Set to `1` to disable non-essential network traffic |
|
||||||
|
|
||||||
|
## Configuration Methods
|
||||||
|
|
||||||
|
### Option 1: `.env` File
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `.env` (the example below uses [MiniMax](https://platform.minimaxi.com/subscribe/token-plan?code=1TG2Cseab2&source=link) as the API provider — you can replace it with any compatible service):
|
||||||
|
|
||||||
|
```env
|
||||||
|
# API authentication (choose one)
|
||||||
|
ANTHROPIC_API_KEY=sk-xxx # Standard API key via x-api-key header
|
||||||
|
ANTHROPIC_AUTH_TOKEN=sk-xxx # Bearer token via Authorization header
|
||||||
|
|
||||||
|
# API endpoint (optional, defaults to Anthropic)
|
||||||
|
ANTHROPIC_BASE_URL=https://api.minimaxi.com/anthropic
|
||||||
|
|
||||||
|
# Model configuration
|
||||||
|
ANTHROPIC_MODEL=MiniMax-M2.7-highspeed
|
||||||
|
ANTHROPIC_DEFAULT_SONNET_MODEL=MiniMax-M2.7-highspeed
|
||||||
|
ANTHROPIC_DEFAULT_HAIKU_MODEL=MiniMax-M2.7-highspeed
|
||||||
|
ANTHROPIC_DEFAULT_OPUS_MODEL=MiniMax-M2.7-highspeed
|
||||||
|
|
||||||
|
# Timeout in milliseconds
|
||||||
|
API_TIMEOUT_MS=3000000
|
||||||
|
|
||||||
|
# Disable telemetry and non-essential network traffic
|
||||||
|
DISABLE_TELEMETRY=1
|
||||||
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: `~/.claude/settings.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"ANTHROPIC_AUTH_TOKEN": "sk-xxx",
|
||||||
|
"ANTHROPIC_BASE_URL": "https://api.minimaxi.com/anthropic",
|
||||||
|
"ANTHROPIC_MODEL": "MiniMax-M2.7-highspeed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Priority: Environment variables > `.env` file > `~/.claude/settings.json`
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# FAQ
|
||||||
|
|
||||||
|
|
||||||
|
## Q: `undefined is not an object (evaluating 'usage.input_tokens')`
|
||||||
|
|
||||||
|
**Cause**: `ANTHROPIC_BASE_URL` is misconfigured. The API endpoint is returning HTML or another non-JSON format instead of a valid Anthropic protocol response.
|
||||||
|
|
||||||
|
This project uses the **Anthropic Messages API protocol**. `ANTHROPIC_BASE_URL` must point to an endpoint compatible with Anthropic's `/v1/messages` interface. The Anthropic SDK automatically appends `/v1/messages` to the base URL, so:
|
||||||
|
|
||||||
|
- MiniMax: `ANTHROPIC_BASE_URL=https://api.minimaxi.com/anthropic` ✅
|
||||||
|
- OpenRouter: `ANTHROPIC_BASE_URL=https://openrouter.ai/api` ✅
|
||||||
|
- OpenRouter (wrong): `ANTHROPIC_BASE_URL=https://openrouter.ai/anthropic` ❌ (returns HTML)
|
||||||
|
|
||||||
|
If your model provider only supports the OpenAI protocol, you need a proxy like LiteLLM for protocol translation. See the [Third-Party Models Guide](./third-party-models.md).
|
||||||
|
|
||||||
|
## Q: `Cannot find package 'bundle'`
|
||||||
|
|
||||||
|
```
|
||||||
|
error: Cannot find package 'bundle' from '.../claude-code-haha/src/entrypoints/cli.tsx'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause**: Your Bun version is too old and doesn't support the required `bun:bundle` built-in module.
|
||||||
|
|
||||||
|
**Fix**: Upgrade Bun to the latest version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
## Q: How to use OpenAI / DeepSeek / Ollama or other non-Anthropic models?
|
||||||
|
|
||||||
|
This project only supports the Anthropic protocol. If your model provider doesn't natively support the Anthropic protocol, you need a proxy like [LiteLLM](https://github.com/BerriAI/litellm) for protocol translation (OpenAI → Anthropic).
|
||||||
|
|
||||||
|
See the [Third-Party Models Guide](./third-party-models.md) for detailed setup instructions.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Global Usage (Run from Any Directory)
|
||||||
|
|
||||||
|
|
||||||
|
If you want to run `claude-haha` directly from any project directory, set up one of the following. Once configured, `claude-haha` will automatically recognize your current working directory.
|
||||||
|
|
||||||
|
## macOS / Linux
|
||||||
|
|
||||||
|
Add to `~/.bashrc` or `~/.zshrc`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Option 1: Add to PATH (recommended)
|
||||||
|
export PATH="$HOME/path/to/claude-code-haha/bin:$PATH"
|
||||||
|
|
||||||
|
# Option 2: Alias
|
||||||
|
alias claude-haha="$HOME/path/to/claude-code-haha/bin/claude-haha"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then reload the config:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source ~/.bashrc # or source ~/.zshrc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Windows (Git Bash)
|
||||||
|
|
||||||
|
Add to `~/.bashrc`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PATH="$HOME/path/to/claude-code-haha/bin:$PATH"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verify
|
||||||
|
|
||||||
|
After setup, navigate to any project directory and test:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/your-other-project
|
||||||
|
claude-haha
|
||||||
|
# Ask "What is the current directory?" — it should show ~/your-other-project
|
||||||
|
```
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# Quick Start
|
||||||
|
|
||||||
|
## 1. Install Bun
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# macOS / Linux
|
||||||
|
curl -fsSL https://bun.sh/install | bash
|
||||||
|
|
||||||
|
# macOS (Homebrew)
|
||||||
|
brew install bun
|
||||||
|
|
||||||
|
# Windows (PowerShell)
|
||||||
|
powershell -c "irm bun.sh/install.ps1 | iex"
|
||||||
|
```
|
||||||
|
|
||||||
|
> On minimal Linux images, if you see `unzip is required`, run `apt update && apt install -y unzip` first.
|
||||||
|
|
||||||
|
## 2. Install Dependencies and Configure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env with your API key
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Environment Variables](./env-vars.md) for the full reference.
|
||||||
|
|
||||||
|
## 3. Start
|
||||||
|
|
||||||
|
### macOS / Linux
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/claude-haha # Interactive TUI mode
|
||||||
|
./bin/claude-haha -p "your prompt here" # Headless mode
|
||||||
|
./bin/claude-haha --help # Show all options
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
> **Prerequisite**: [Git for Windows](https://git-scm.com/download/win) must be installed.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# PowerShell / cmd — call Bun directly
|
||||||
|
bun --env-file=.env ./src/entrypoints/cli.tsx
|
||||||
|
|
||||||
|
# Or run inside Git Bash
|
||||||
|
./bin/claude-haha
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Global Usage (Optional)
|
||||||
|
|
||||||
|
Add `bin/` to your PATH to run from any directory. See [Global Usage Guide](./global-usage.md):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PATH="$HOME/path/to/claude-code-haha/bin:$PATH"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Recovery Mode
|
||||||
|
|
||||||
|
If the Ink TUI has issues, use the fallback Recovery CLI mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CLAUDE_CODE_FORCE_RECOVERY_CLI=1 ./bin/claude-haha
|
||||||
|
```
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
layout: home
|
||||||
|
|
||||||
|
hero:
|
||||||
|
name: Claude Code Haha
|
||||||
|
text: Locally Runnable Claude Code
|
||||||
|
tagline: Repaired from leaked source, supports any Anthropic-compatible API endpoint (MiniMax, OpenRouter, etc.)
|
||||||
|
image:
|
||||||
|
src: /images/banner.jpg
|
||||||
|
alt: Claude Code Haha
|
||||||
|
actions:
|
||||||
|
- theme: brand
|
||||||
|
text: Quick Start
|
||||||
|
link: /en/guide/quick-start
|
||||||
|
- theme: alt
|
||||||
|
text: GitHub
|
||||||
|
link: https://github.com/NanmiCoder/cc-haha
|
||||||
|
|
||||||
|
features:
|
||||||
|
- icon: "\U0001F5A5"
|
||||||
|
title: Full TUI Experience
|
||||||
|
details: Ink terminal UI matching the official Claude Code interface, with --print headless mode
|
||||||
|
- icon: "\U0001F9E0"
|
||||||
|
title: Memory System
|
||||||
|
details: Cross-session persistent memory with auto-extraction, smart retrieval, and AutoDream consolidation
|
||||||
|
- icon: "\U0001F916"
|
||||||
|
title: Multi-Agent System
|
||||||
|
details: Agent orchestration, parallel tasks, Teams collaboration, Worktree isolation
|
||||||
|
- icon: "\U0001F9E9"
|
||||||
|
title: Skills System
|
||||||
|
details: Extensible capability plugins, custom workflows, conditional activation
|
||||||
|
- icon: "\U0001F4BB"
|
||||||
|
title: Computer Use
|
||||||
|
details: Desktop control — screenshots, mouse, and keyboard automation
|
||||||
|
link: /en/features/computer-use
|
||||||
|
- icon: "\U0001F310"
|
||||||
|
title: Third-Party Models
|
||||||
|
details: Support for OpenAI, DeepSeek, Ollama, and any compatible model
|
||||||
|
link: /en/guide/third-party-models
|
||||||
|
---
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
# Claude Code Memory System — Usage Guide
|
||||||
|
|
||||||
|
> Let Claude Code remember who you are, what you prefer, and what's happening in your project across sessions.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#1-what-is-the-memory-system">Memory System</a> · <a href="#2-four-memory-types">Four Memory Types</a> · <a href="#3-how-to-trigger-memory-saving">Trigger Saving</a> · <a href="#4-where-are-memories-stored">Storage Location</a> · <a href="#5-how-to-manage-memories">Manage Memories</a> · <a href="#6-memory-lifecycle">Lifecycle</a> · <a href="#7-quick-reference">Quick Reference</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. What Is the Memory System?
|
||||||
|
|
||||||
|
Claude Code's memory system is a **file-based persistent knowledge store** that allows Claude to continuously build understanding of you and your project across multiple conversations.
|
||||||
|
|
||||||
|
Core principle: **Only remember things that cannot be inferred from the code itself.**
|
||||||
|
|
||||||
|
| Remembered | Not Remembered |
|
||||||
|
|------------|----------------|
|
||||||
|
| You're a data scientist focused on logging systems | Code architecture, file structure |
|
||||||
|
| "Don't mock the database" | Git history, who changed what |
|
||||||
|
| Non-critical merges frozen after Thursday | Existing CLAUDE.md content |
|
||||||
|
| Bug tracking is in Linear's INGEST project | Debugging solutions (fixes are already in the code) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Four Memory Types
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Claude Code strictly categorizes memories into four types:
|
||||||
|
|
||||||
|
### 2.1 User (User Profile)
|
||||||
|
|
||||||
|
Records your role, goals, skill level, and preferences to help Claude tailor its collaboration approach.
|
||||||
|
|
||||||
|
```
|
||||||
|
User says: I've written Go for ten years, but this is my first time touching the React part of this repo
|
||||||
|
Claude saves: Deep Go experience, React newcomer — explain frontend concepts using backend analogies
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Feedback (Behavioral Feedback)
|
||||||
|
|
||||||
|
Your corrections or affirmations about how Claude works. These memories prevent Claude from repeating the same mistakes.
|
||||||
|
|
||||||
|
```
|
||||||
|
User says: Don't summarize what you did at the end of your reply, I can see the diff
|
||||||
|
Claude saves: User prefers concise replies, no trailing summaries
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**: Not only corrections are recorded -- affirmations are too. If Claude makes a non-obvious choice and you approve, that gets remembered as well.
|
||||||
|
|
||||||
|
### 2.3 Project (Project Context)
|
||||||
|
|
||||||
|
Project context that cannot be derived from the code or Git history: who's doing what, why, and deadlines.
|
||||||
|
|
||||||
|
```
|
||||||
|
User says: We're freezing all non-critical merges after Thursday, the mobile team needs to cut a release branch
|
||||||
|
Claude saves: Merge freeze starting 2026-03-05, flag non-critical PR work after this date
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: Claude converts relative dates ("Thursday") to absolute dates ("2026-03-05") to ensure memories don't become ambiguous over time.
|
||||||
|
|
||||||
|
### 2.4 Reference (External References)
|
||||||
|
|
||||||
|
Pointers to information in external systems: dashboards, issue trackers, Slack channels.
|
||||||
|
|
||||||
|
```
|
||||||
|
User says: On-call monitors the grafana.internal/d/api-latency dashboard
|
||||||
|
Claude saves: grafana.internal/d/api-latency is the on-call latency dashboard — check when editing request path code
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. How to Trigger Memory Saving
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Method 1: Automatic Extraction (Most Common)
|
||||||
|
|
||||||
|
This is the primary method. **You don't need to do anything** -- Claude automatically analyzes conversation content at the end of each conversation turn and extracts information worth remembering.
|
||||||
|
|
||||||
|
Workflow:
|
||||||
|
1. You have a normal conversation with Claude
|
||||||
|
2. Claude finishes its response (no tool calls pending)
|
||||||
|
3. A **memory extraction sub-agent** starts in the background
|
||||||
|
4. The sub-agent analyzes the recent conversation content
|
||||||
|
5. It identifies memories worth saving
|
||||||
|
6. Writes memory files + updates the MEMORY.md index
|
||||||
|
|
||||||
|
The terminal will display a notification:
|
||||||
|
```
|
||||||
|
Memory updated in ~/.claude/projects/.../memory/feedback_testing.md · /memory to edit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 2: Explicit Request
|
||||||
|
|
||||||
|
Directly tell Claude to "remember this":
|
||||||
|
|
||||||
|
```
|
||||||
|
User: Remember, this project must run bun test before deploying
|
||||||
|
Claude: [Immediately saves as a feedback-type memory]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 3: /memory Command
|
||||||
|
|
||||||
|
Type `/memory` in the terminal to open a file picker that lets you edit memory files directly in your editor.
|
||||||
|
|
||||||
|
```
|
||||||
|
> /memory
|
||||||
|
```
|
||||||
|
|
||||||
|
This lists all editable memory files (CLAUDE.md, CLAUDE.local.md, auto-memory, etc.) and opens the selected file with your `$EDITOR` or `$VISUAL`.
|
||||||
|
|
||||||
|
### Method 4: /remember Command
|
||||||
|
|
||||||
|
Type `/remember` to trigger the memory review skill, which will:
|
||||||
|
- Review all automatic memory entries
|
||||||
|
- Propose promoting suitable entries to CLAUDE.md or CLAUDE.local.md
|
||||||
|
- Detect duplicate, outdated, and conflicting memories
|
||||||
|
- **Does not modify anything directly** -- all changes require your approval
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Where Are Memories Stored?
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.claude/
|
||||||
|
└── projects/
|
||||||
|
└── {project-path-hash}/
|
||||||
|
└── memory/ <- Auto-memory directory
|
||||||
|
├── MEMORY.md <- Index file (always loaded into context)
|
||||||
|
├── user_role.md <- User profile memory
|
||||||
|
├── feedback_testing.md <- Behavioral feedback memory
|
||||||
|
├── project_freeze.md <- Project context memory
|
||||||
|
├── reference_linear.md <- External reference memory
|
||||||
|
└── team/ <- Team shared memory (if enabled)
|
||||||
|
├── MEMORY.md
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory File Format
|
||||||
|
|
||||||
|
Each memory file uses YAML frontmatter + Markdown content:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: Testing strategy preference
|
||||||
|
description: Integration tests must use a real database, no mocking
|
||||||
|
type: feedback
|
||||||
|
---
|
||||||
|
|
||||||
|
Integration tests must use a real database, no mocking.
|
||||||
|
|
||||||
|
**Why:** Last quarter, mocked tests passed but production migrations failed — mock/production divergence masked the issues.
|
||||||
|
|
||||||
|
**How to apply:** When writing or reviewing tests, ensure database operations use real connections.
|
||||||
|
```
|
||||||
|
|
||||||
|
### MEMORY.md Index File
|
||||||
|
|
||||||
|
MEMORY.md is an index, not content. It is **always loaded into context**, with one entry per line:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- [User role](user_role.md) — Data scientist, focused on observability/logging
|
||||||
|
- [Testing strategy](feedback_testing.md) — Integration tests use real DB, no mocking
|
||||||
|
- [Merge freeze](project_freeze.md) — Non-critical merges frozen starting 2026-03-05
|
||||||
|
- [Bug tracking](reference_linear.md) — Pipeline bugs tracked in Linear INGEST project
|
||||||
|
```
|
||||||
|
|
||||||
|
**Limit**: Maximum 200 lines or 25KB; content beyond this is truncated.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. How to Manage Memories
|
||||||
|
|
||||||
|
### Ask Claude to Forget
|
||||||
|
|
||||||
|
```
|
||||||
|
User: Forget the memory about the merge freeze
|
||||||
|
Claude: [Finds and deletes the relevant memory file and index entry]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ask Claude to Ignore Memories
|
||||||
|
|
||||||
|
```
|
||||||
|
User: Ignore memories, start from scratch
|
||||||
|
Claude: [Does not use any memory content in this conversation]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Editing
|
||||||
|
|
||||||
|
Directly edit files under `~/.claude/projects/{hash}/memory/`, or use the `/memory` command.
|
||||||
|
|
||||||
|
### Disable Automatic Memory
|
||||||
|
|
||||||
|
| Method | How |
|
||||||
|
|--------|-----|
|
||||||
|
| Environment variable | `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1` |
|
||||||
|
| Settings file | Set `"autoMemoryEnabled": false` in `settings.json` |
|
||||||
|
| Bare mode | Start with `--bare` / `CLAUDE_CODE_SIMPLE=1` |
|
||||||
|
|
||||||
|
### Custom Memory Directory
|
||||||
|
|
||||||
|
Set in `~/.claude/settings.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"autoMemoryDirectory": "~/my-claude-memories"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Supports `~/` expansion. For security reasons, the project-level `.claude/settings.json` is **not allowed** to set this option.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Memory Lifecycle
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
New information learned during conversation
|
||||||
|
|
|
||||||
|
Auto-extraction / Explicit save
|
||||||
|
|
|
||||||
|
Write memory file + index
|
||||||
|
|
|
||||||
|
Next conversation loads MEMORY.md
|
||||||
|
|
|
||||||
|
Intelligent selection of relevant memories (Sonnet)
|
||||||
|
|
|
||||||
|
Inject into conversation context
|
||||||
|
|
|
||||||
|
Memory getting old? Verify before using
|
||||||
|
|
|
||||||
|
Outdated? Update or delete
|
||||||
|
|
|
||||||
|
(After 24h + 5 sessions)
|
||||||
|
|
|
||||||
|
AutoDream consolidates memories in background
|
||||||
|
```
|
||||||
|
|
||||||
|
### AutoDream -- "Dreaming" to Organize Memories
|
||||||
|
|
||||||
|
Claude Code has a hidden **AutoDream** feature, analogous to how the human brain organizes memories during sleep. When the following conditions are met, Claude silently launches a "dreaming" sub-agent in the background:
|
||||||
|
|
||||||
|
- At least **>= 24 hours** since the last consolidation
|
||||||
|
- At least **>= 5 sessions** accumulated in the interim
|
||||||
|
|
||||||
|
The dreaming process has four phases: Orient -> Gather -> Consolidate -> Prune. The bottom status bar shows **"dreaming"**, and you can press `Shift+Down` to view progress or `x` to terminate.
|
||||||
|
|
||||||
|
For a detailed technical analysis, see [AutoDream Memory Consolidation](./03-autodream.md).
|
||||||
|
|
||||||
|
### Freshness Management
|
||||||
|
|
||||||
|
- **Today's/yesterday's memories**: Used directly
|
||||||
|
- **Memories older than 1 day**: Accompanied by a stale warning, reminding Claude to verify before citing
|
||||||
|
- **Memories referencing file paths/function names**: Confirmed via grep before use to ensure they still exist
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Quick Reference
|
||||||
|
|
||||||
|
| Action | Method |
|
||||||
|
|--------|--------|
|
||||||
|
| Ask Claude to remember | "Remember: this project uses bun, not npm" |
|
||||||
|
| Ask Claude to forget | "Forget the memory about XXX" |
|
||||||
|
| Edit memories | `/memory` command |
|
||||||
|
| Review and organize | `/remember` command |
|
||||||
|
| Ignore memories | "Ignore memories" / "Don't use memories" |
|
||||||
|
| Disable auto-memory | `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1` |
|
||||||
|
| Disable AutoDream | Set `"autoDreamEnabled": false` in `settings.json` |
|
||||||
|
| Manually consolidate memories | `/dream` command |
|
||||||
|
| View memory directory | `~/.claude/projects/{hash}/memory/` |
|
||||||
@@ -0,0 +1,480 @@
|
|||||||
|
# Claude Code Memory System — Implementation Details
|
||||||
|
|
||||||
|
> From system prompt injection to background auto-extraction, dissecting every technical detail of the memory system.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#1-overall-architecture">Architecture</a> · <a href="#2-path-resolution-system">Path Resolution</a> · <a href="#3-system-prompt-injection">Prompt Injection</a> · <a href="#4-automatic-memory-extraction">Auto-Extraction</a> · <a href="#5-intelligent-memory-retrieval">Intelligent Retrieval</a> · <a href="#6-memory-scanning-in-detail">Memory Scanning</a> · <a href="#7-agent-memory">Agent Memory</a> · <a href="#8-team-memory-sync">Team Sync</a> · <a href="#9-key-constants-quick-reference">Constants</a> · <a href="#10-data-flow-overview">Data Flow</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Overall Architecture
|
||||||
|
|
||||||
|
The memory system is powered by 5 core modules working in concert:
|
||||||
|
|
||||||
|
| Module | Source Location | Responsibility |
|
||||||
|
|--------|----------------|----------------|
|
||||||
|
| **Path Resolution** | `src/memdir/paths.ts` | Computes memory storage directory, handles overrides and security validation |
|
||||||
|
| **Prompt Construction** | `src/memdir/memdir.ts` | Injects memory instructions into the system prompt |
|
||||||
|
| **Memory Scanning** | `src/memdir/memoryScan.ts` | Scans directories, parses frontmatter, sorts entries |
|
||||||
|
| **Intelligent Retrieval** | `src/memdir/findRelevantMemories.ts` | Uses Sonnet to select memories relevant to the current query |
|
||||||
|
| **Auto-Extraction** | `src/services/extractMemories/` | Background forked agent that extracts memories from conversations |
|
||||||
|
|
||||||
|
Auxiliary modules:
|
||||||
|
|
||||||
|
| Module | Source Location | Responsibility |
|
||||||
|
|--------|----------------|----------------|
|
||||||
|
| **AutoDream** | `src/services/autoDream/` | Background memory consolidation ("dreaming"); see [03-autodream.md](./03-autodream.md) |
|
||||||
|
| **Type Definitions** | `src/memdir/memoryTypes.ts` | Taxonomy and prompt templates for the four memory types |
|
||||||
|
| **Freshness** | `src/memdir/memoryAge.ts` | Calculates memory age, generates stale warnings |
|
||||||
|
| **File Detection** | `src/utils/memoryFileDetection.ts` | Determines whether a path belongs to the memory system |
|
||||||
|
| **Agent Memory** | `src/tools/AgentTool/agentMemory.ts` | Three-level memory directories exclusive to sub-agents |
|
||||||
|
| **Team Sync** | `src/services/teamMemorySync/` | Remote upload/download of memories |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Path Resolution System
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Core Function: `getAutoMemPath()`
|
||||||
|
|
||||||
|
```
|
||||||
|
Path resolution priority (highest to lowest):
|
||||||
|
|
||||||
|
1. CLAUDE_COWORK_MEMORY_PATH_OVERRIDE <- Cowork environment variable (full path)
|
||||||
|
2. settings.json -> autoMemoryDirectory <- User setting (supports ~/ expansion)
|
||||||
|
3. {memoryBase}/projects/{sanitized-git-root}/memory/ <- Default computed path
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key source** `src/memdir/paths.ts:223`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const getAutoMemPath = memoize(
|
||||||
|
(): string => {
|
||||||
|
const override = getAutoMemPathOverride() ?? getAutoMemPathSetting()
|
||||||
|
if (override) return override
|
||||||
|
const projectsDir = join(getMemoryBaseDir(), 'projects')
|
||||||
|
return join(projectsDir, sanitizePath(getAutoMemBase()), AUTO_MEM_DIRNAME) + sep
|
||||||
|
},
|
||||||
|
() => getProjectRoot(), // Cache key = project root
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Path Security Validation
|
||||||
|
|
||||||
|
`validateMemoryPath()` rejects dangerous paths:
|
||||||
|
|
||||||
|
| Rejected Path | Reason |
|
||||||
|
|---------------|--------|
|
||||||
|
| `../foo` | Relative path, CWD-dependent |
|
||||||
|
| `/` or `/a` | Root path or too-short path |
|
||||||
|
| `C:\` | Windows drive root |
|
||||||
|
| `\\server\share` | UNC network path |
|
||||||
|
| Contains `\0` | Null byte, can truncate in system calls |
|
||||||
|
|
||||||
|
**Security restriction**: Project-level `.claude/settings.json` is **not allowed** to set `autoMemoryDirectory`, preventing malicious repositories from gaining write access to sensitive directories like `~/.ssh`.
|
||||||
|
|
||||||
|
### Enable Conditions
|
||||||
|
|
||||||
|
The `isAutoMemoryEnabled()` check chain:
|
||||||
|
|
||||||
|
```
|
||||||
|
CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 -> Disabled
|
||||||
|
CLAUDE_CODE_SIMPLE (--bare) -> Disabled
|
||||||
|
Remote mode without REMOTE_MEMORY_DIR -> Disabled
|
||||||
|
settings.json autoMemoryEnabled -> Follows setting
|
||||||
|
Default -> Enabled
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. System Prompt Injection
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Entry Point: `loadMemoryPrompt()`
|
||||||
|
|
||||||
|
This is the interface between the memory system and the system prompt. It is called once at startup (cached via `systemPromptSection`).
|
||||||
|
|
||||||
|
```
|
||||||
|
loadMemoryPrompt()
|
||||||
|
|-- KAIROS mode? -> buildAssistantDailyLogPrompt() [log append mode]
|
||||||
|
|-- TEAMMEM enabled? -> buildCombinedMemoryPrompt() [personal + team dual directory]
|
||||||
|
|-- Normal mode -> buildMemoryLines() [single directory]
|
||||||
|
|-- Disabled -> return null
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prompt Structure Built by `buildMemoryLines()`
|
||||||
|
|
||||||
|
```
|
||||||
|
# auto memory
|
||||||
|
|
||||||
|
You have a persistent file-based memory system located at `{memoryDir}`...
|
||||||
|
|
||||||
|
## Types of memory <- Definitions and examples for all four types
|
||||||
|
## What NOT to save <- Exclusion rules
|
||||||
|
## How to save memories <- Two-step saving process
|
||||||
|
## When to access memories <- When to consult
|
||||||
|
## Before recommending <- Verify before citing
|
||||||
|
## Memory and other forms <- Distinction from Plan/Task
|
||||||
|
|
||||||
|
## MEMORY.md <- Index content (or "currently empty")
|
||||||
|
```
|
||||||
|
|
||||||
|
### MEMORY.md Truncation Strategy
|
||||||
|
|
||||||
|
`truncateEntrypointContent()` applies dual limits:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// First truncate by line count
|
||||||
|
if (lineCount > 200) -> Truncate to 200 lines
|
||||||
|
|
||||||
|
// Then truncate by byte size (handles extra-long lines)
|
||||||
|
if (bytes > 25,000) -> Truncate at last newline character
|
||||||
|
|
||||||
|
// Append warning
|
||||||
|
"WARNING: MEMORY.md is {reason}. Only part of it was loaded."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automatic Directory Creation
|
||||||
|
|
||||||
|
`ensureMemoryDirExists()` ensures the directory exists when loading the prompt:
|
||||||
|
- Recursively creates with `mkdir` (handles the entire parent chain)
|
||||||
|
- Swallows `EEXIST` (idempotent)
|
||||||
|
- Genuine permission errors are only logged, not thrown (the Write tool will surface the real error)
|
||||||
|
|
||||||
|
The prompt explicitly tells the model the directory already exists, avoiding wasted turns on `ls` or `mkdir`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Automatic Memory Extraction
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Trigger Timing
|
||||||
|
|
||||||
|
Triggered in `handleStopHooks` when the model produces a final response (no tool calls).
|
||||||
|
|
||||||
|
**Key source**: `src/services/extractMemories/extractMemories.ts`
|
||||||
|
|
||||||
|
### Complete Extraction Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Model finishes response (no tool_use)
|
||||||
|
|
|
||||||
|
2. executeExtractMemories() is called
|
||||||
|
|
|
||||||
|
3. Guard checks:
|
||||||
|
- Is this the main agent? (sub-agents don't extract)
|
||||||
|
- Feature gate enabled?
|
||||||
|
- Auto-memory enabled?
|
||||||
|
- Not in remote mode?
|
||||||
|
- No parallel extraction in progress?
|
||||||
|
|
|
||||||
|
4. Frequency control:
|
||||||
|
turnsSinceLastExtraction++
|
||||||
|
if < tengu_bramble_lintel -> skip
|
||||||
|
|
|
||||||
|
5. Mutual exclusion check:
|
||||||
|
Main agent wrote memory itself? -> skip, advance cursor
|
||||||
|
|
|
||||||
|
6. Scan existing memory directory (scanMemoryFiles)
|
||||||
|
Generate manifest (formatMemoryManifest)
|
||||||
|
|
|
||||||
|
7. Build extraction prompt (buildExtractAutoOnlyPrompt)
|
||||||
|
|
|
||||||
|
8. Run forked agent (runForkedAgent)
|
||||||
|
- Shares parent session's prompt cache
|
||||||
|
- Max 5 turns
|
||||||
|
- Restricted tool permissions
|
||||||
|
|
|
||||||
|
9. Extract written file paths
|
||||||
|
Advance cursor to latest message
|
||||||
|
|
|
||||||
|
10. Notify user: "Memory updated in ..."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Forked Agent
|
||||||
|
|
||||||
|
Auto-extraction uses `runForkedAgent` -- a perfect fork of the main session:
|
||||||
|
|
||||||
|
- **Shared prompt cache**: Avoids duplicate API cache creation costs
|
||||||
|
- **Isolated execution**: Does not affect the main session's message history
|
||||||
|
- **Restricted tools**: Only allows Read, Grep, Glob, read-only Bash, and Edit/Write within the memory directory
|
||||||
|
- **No transcript recording**: Prevents race conditions with the main thread
|
||||||
|
|
||||||
|
### Tool Permissions (`createAutoMemCanUseTool`)
|
||||||
|
|
||||||
|
```
|
||||||
|
Allowed: Read, Grep, Glob (unrestricted)
|
||||||
|
Allowed: Bash (read-only commands only: ls, find, grep, cat, stat...)
|
||||||
|
Allowed: Edit/Write (only within auto-memory directory)
|
||||||
|
Denied: MCP, Agent, non-read-only Bash, other write operations
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mutual Exclusion Mechanism
|
||||||
|
|
||||||
|
The main agent and the extraction agent are **mutually exclusive**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function hasMemoryWritesSince(messages, sinceUuid): boolean {
|
||||||
|
// Scan all assistant messages after sinceUuid
|
||||||
|
// If any Edit/Write tool_use targets the auto-memory directory
|
||||||
|
// -> return true (skip extraction, advance cursor)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This prevents duplicate saves: when the main agent has already written a memory, the background extraction is skipped.
|
||||||
|
|
||||||
|
### Merge Mechanism
|
||||||
|
|
||||||
|
If a previous extraction is still running:
|
||||||
|
1. The new context is queued (`pendingContext`)
|
||||||
|
2. After the old extraction completes, a **tail extraction** is immediately launched
|
||||||
|
3. The tail extraction only processes messages added between the two calls
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Intelligent Memory Retrieval
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
Each time the user sends a query, `findRelevantMemories()` is triggered:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. scanMemoryFiles(memoryDir)
|
||||||
|
- Recursively reads all .md files (excludes MEMORY.md)
|
||||||
|
- Parses frontmatter (first 30 lines)
|
||||||
|
- Sorts by modification time in descending order
|
||||||
|
- Max 200 files
|
||||||
|
|
|
||||||
|
2. Filter out previously surfaced memories (alreadySurfaced)
|
||||||
|
|
|
||||||
|
3. Format manifest (formatMemoryManifest)
|
||||||
|
- [type] filename (ISO timestamp): description
|
||||||
|
|
|
||||||
|
4. Sonnet model selection (sideQuery)
|
||||||
|
- System prompt: You are a memory selector...
|
||||||
|
- User message: Query + Available memories + Recently used tools
|
||||||
|
- Output: JSON { selected_memories: string[] }
|
||||||
|
- Max 5 selections
|
||||||
|
|
|
||||||
|
5. Return selected memories as { path, mtimeMs }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sonnet Selector Prompt
|
||||||
|
|
||||||
|
```
|
||||||
|
You are selecting memories useful for Claude Code to handle the user's query.
|
||||||
|
You'll receive the user's query and a list of available memory files (with filenames and descriptions).
|
||||||
|
|
||||||
|
Return at most 5 memory filenames that are clearly useful.
|
||||||
|
- If uncertain whether something is useful, don't select it
|
||||||
|
- If nothing is clearly useful, return an empty list
|
||||||
|
- If a list of recently used tools is provided, don't select usage docs for those tools
|
||||||
|
(but DO select warnings/gotchas/known issues about those tools)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Freshness Warning
|
||||||
|
|
||||||
|
Selected memories are injected into the context with freshness information:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function memoryFreshnessText(mtimeMs: number): string {
|
||||||
|
const d = memoryAgeDays(mtimeMs)
|
||||||
|
if (d <= 1) return '' // Today/yesterday: no warning
|
||||||
|
return `This memory is ${d} days old. Memories are point-in-time observations...
|
||||||
|
Verify against current code before asserting as fact.`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Memory Scanning in Detail
|
||||||
|
|
||||||
|
### `scanMemoryFiles()`
|
||||||
|
|
||||||
|
**Key design**: Single-pass (read-then-sort) to avoid double stat system calls.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
async function scanMemoryFiles(memoryDir, signal): Promise<MemoryHeader[]> {
|
||||||
|
const entries = await readdir(memoryDir, { recursive: true })
|
||||||
|
const mdFiles = entries.filter(f => f.endsWith('.md') && basename(f) !== 'MEMORY.md')
|
||||||
|
|
||||||
|
// Read all files' frontmatter in parallel (first 30 lines)
|
||||||
|
const headerResults = await Promise.allSettled(
|
||||||
|
mdFiles.map(async (relativePath) => {
|
||||||
|
const { content, mtimeMs } = await readFileInRange(filePath, 0, 30)
|
||||||
|
const { frontmatter } = parseFrontmatter(content)
|
||||||
|
return { filename, filePath, mtimeMs, description, type }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filter successful results, sort by time descending, take first 200
|
||||||
|
return fulfilled.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, 200)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `formatMemoryManifest()`
|
||||||
|
|
||||||
|
Generates a manifest consumed by Sonnet or the extraction agent:
|
||||||
|
|
||||||
|
```
|
||||||
|
- [feedback] testing_policy.md (2026-03-15T10:30:00.000Z): Integration tests use real DB
|
||||||
|
- [user] role.md (2026-03-14T08:00:00.000Z): Data scientist, focused on logging
|
||||||
|
- [project] freeze.md (2026-03-10T15:00:00.000Z): Merge freeze starting 3/5
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Agent Memory
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Sub-agents (launched via the Agent tool) have an independent three-level memory system:
|
||||||
|
|
||||||
|
| Scope | Path | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| **user** | `~/.claude/agent-memory/{agentType}/` | Global user-level |
|
||||||
|
| **project** | `.claude/agent-memory/{agentType}/` | Project-level (committed to VCS) |
|
||||||
|
| **local** | `.claude/agent-memory-local/{agentType}/` | Local-level (not committed) |
|
||||||
|
|
||||||
|
Differences from main memory:
|
||||||
|
- No MEMORY.md index step (`skipIndex = true`)
|
||||||
|
- Files can be written directly without the two-step process
|
||||||
|
- Each agent type is isolated (explorer, planner, etc. each have their own directory)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Team Memory Sync
|
||||||
|
|
||||||
|
When the `TEAMMEM` feature flag is enabled:
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.claude/projects/{hash}/memory/
|
||||||
|
├── MEMORY.md <- Personal memory index
|
||||||
|
├── user_*.md <- Personal memories
|
||||||
|
└── team/ <- Team shared directory
|
||||||
|
├── MEMORY.md <- Team memory index
|
||||||
|
└── *.md <- Team memories
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sync API
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/claude_code/team_memory?repo={owner/repo} <- Pull
|
||||||
|
PUT /api/claude_code/team_memory?repo={owner/repo} <- Push
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sync Semantics
|
||||||
|
|
||||||
|
- **Pull**: Server content overwrites local files
|
||||||
|
- **Push**: Only uploads keys with different content hashes (delta upload)
|
||||||
|
- **Deletes don't propagate**: Local deletions do not delete remote entries
|
||||||
|
- **Limits**: Single file max 250KB, upload body max 200KB (batched)
|
||||||
|
|
||||||
|
### Team vs. Personal Routing Rules
|
||||||
|
|
||||||
|
In `memoryTypes.ts`, each type has a `<scope>` directive:
|
||||||
|
|
||||||
|
| Type | Default Scope |
|
||||||
|
|------|---------------|
|
||||||
|
| user | Always personal |
|
||||||
|
| feedback | Personal by default; project-level conventions go to team |
|
||||||
|
| project | Leans toward team |
|
||||||
|
| reference | Usually team |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Key Constants Quick Reference
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Index file
|
||||||
|
ENTRYPOINT_NAME = 'MEMORY.md'
|
||||||
|
MAX_ENTRYPOINT_LINES = 200
|
||||||
|
MAX_ENTRYPOINT_BYTES = 25_000
|
||||||
|
|
||||||
|
// Scanning
|
||||||
|
MAX_MEMORY_FILES = 200
|
||||||
|
FRONTMATTER_MAX_LINES = 30
|
||||||
|
|
||||||
|
// Path
|
||||||
|
AUTO_MEM_DIRNAME = 'memory'
|
||||||
|
|
||||||
|
// Extraction
|
||||||
|
maxTurns = 5 // Forked agent max 5 turns
|
||||||
|
|
||||||
|
// Retrieval
|
||||||
|
Max 5 relevant memories returned
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Data Flow Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ Session Startup │
|
||||||
|
│ │
|
||||||
|
│ loadMemoryPrompt() │
|
||||||
|
│ -> ensureMemoryDirExists() │
|
||||||
|
│ -> buildMemoryLines() + MEMORY.md content │
|
||||||
|
│ -> Inject into system prompt │
|
||||||
|
└────────────────────────┬────────────────────────────┘
|
||||||
|
|
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ User Query │
|
||||||
|
│ │
|
||||||
|
│ findRelevantMemories() │
|
||||||
|
│ -> scanMemoryFiles() [scan + frontmatter] │
|
||||||
|
│ -> Sonnet selects up to 5 relevant memories │
|
||||||
|
│ -> Inject into conversation context │
|
||||||
|
│ + freshness warnings │
|
||||||
|
└────────────────────────┬────────────────────────────┘
|
||||||
|
|
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ Claude's Response │
|
||||||
|
│ │
|
||||||
|
│ Model may write memories directly │
|
||||||
|
│ (following system prompt guidance) │
|
||||||
|
│ Or not -> triggers background extraction │
|
||||||
|
└────────────────────────┬────────────────────────────┘
|
||||||
|
|
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ Background Auto-Extraction │
|
||||||
|
│ │
|
||||||
|
│ executeExtractMemories() │
|
||||||
|
│ -> Mutual exclusion check │
|
||||||
|
│ (main agent already wrote? skip) │
|
||||||
|
│ -> Build extraction prompt + memory manifest │
|
||||||
|
│ -> runForkedAgent() │
|
||||||
|
│ [shared cache, restricted tools, 5 turns] │
|
||||||
|
│ -> Write new memory files + update MEMORY.md │
|
||||||
|
│ -> Notify user │
|
||||||
|
└────────────────────────┬────────────────────────────┘
|
||||||
|
|
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ Background Memory Consolidation (AutoDream) │
|
||||||
|
│ │
|
||||||
|
│ executeAutoDream() │
|
||||||
|
│ [triggers every 24h + 5 sessions] │
|
||||||
|
│ -> Five-gate check │
|
||||||
|
│ (toggle/time/throttle/session/lock) │
|
||||||
|
│ -> buildConsolidationPrompt() │
|
||||||
|
│ -> runForkedAgent() │
|
||||||
|
│ [read-only Bash, memory-dir writes only] │
|
||||||
|
│ -> Four phases: │
|
||||||
|
│ Orient -> Gather -> Consolidate -> Prune │
|
||||||
|
│ -> Merge duplicates / fix stale / compress index │
|
||||||
|
│ -> Notify user: "Improved N memories" │
|
||||||
|
│ │
|
||||||
|
│ See 03-autodream.md for details │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
# Claude Code Memory System — AutoDream Memory Consolidation
|
||||||
|
|
||||||
|
> Claude "dreams" -- silently reviewing recent sessions in the background to consolidate, update, and prune memories, much like the human brain organizes memories during sleep.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#1-what-is-autodream">AutoDream</a> · <a href="#2-trigger-conditions">Trigger Conditions</a> · <a href="#3-four-phase-consolidation-process">Consolidation Process</a> · <a href="#4-security-restrictions">Security</a> · <a href="#5-ui-presentation">UI</a> · <a href="#6-configuration-and-toggles">Configuration</a> · <a href="#7-relationship-with-extractmemories">Comparison</a> · <a href="#8-source-code-navigation">Source Code</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. What Is AutoDream?
|
||||||
|
|
||||||
|
AutoDream is Claude Code's **background memory consolidation mechanism**, internally codenamed **"Dream: Memory Consolidation"**.
|
||||||
|
|
||||||
|
Core metaphor:
|
||||||
|
|
||||||
|
| Human | Claude Code |
|
||||||
|
|-------|-------------|
|
||||||
|
| Jotting down notes throughout the day | `extractMemories` -- extracts new memories after each conversation |
|
||||||
|
| Organizing the notebook while sleeping | `autoDream` -- periodically reviews multiple sessions to consolidate all memories |
|
||||||
|
|
||||||
|
When you're inactive (default interval: 24 hours with 5 accumulated sessions), Claude silently launches a **"dreaming" sub-agent** (forked subagent) in the background. It reviews all recent session transcripts and consolidates scattered memories into structured, deduplicated, up-to-date persistent knowledge.
|
||||||
|
|
||||||
|
**Key source**: `src/services/autoDream/autoDream.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Background memory consolidation. Fires the /dream prompt as a forked
|
||||||
|
// subagent when time-gate passes AND enough sessions have accumulated.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Trigger Conditions
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
AutoDream uses a **five-gate** mechanism, checking in order of increasing cost:
|
||||||
|
|
||||||
|
### Gate Chain
|
||||||
|
|
||||||
|
| # | Gate | Description | Cost |
|
||||||
|
|---|------|-------------|------|
|
||||||
|
| 1 | **Feature toggle** | `isAutoDreamEnabled()` + not KAIROS + not remote mode + autoMemory enabled | Memory read |
|
||||||
|
| 2 | **Time gate** | At least `minHours` since last consolidation (default 24h) | 1 stat call |
|
||||||
|
| 3 | **Scan throttle** | At least 10 minutes since last scan before rescanning | Timestamp comparison |
|
||||||
|
| 4 | **Session gate** | At least `minSessions` new sessions since last consolidation (default 5, excluding current) | Directory scan |
|
||||||
|
| 5 | **Lock gate** | No other process currently dreaming (PID lock file) | stat + read |
|
||||||
|
|
||||||
|
**Key source** `src/services/autoDream/autoDream.ts:63-66`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const DEFAULTS: AutoDreamConfig = {
|
||||||
|
minHours: 24, // At least 24 hours since last consolidation
|
||||||
|
minSessions: 5, // At least 5 sessions accumulated in the interim
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scan Throttle
|
||||||
|
|
||||||
|
When the time gate passes but the session gate doesn't, the lock file's mtime remains unchanged, causing the time gate to pass on every subsequent turn. To avoid frequent directory scans, a **10-minute scan throttle** is in place:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const SESSION_SCAN_INTERVAL_MS = 10 * 60 * 1000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Execution Entry Point
|
||||||
|
|
||||||
|
AutoDream is triggered during the stop hook phase after each AI response (fire-and-forget, does not block the main thread):
|
||||||
|
|
||||||
|
**Key source** `src/query/stopHooks.ts:154-156`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (!toolUseContext.agentId) {
|
||||||
|
void executeAutoDream(stopHookContext, toolUseContext.appendSystemMessage)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Blocking Conditions
|
||||||
|
|
||||||
|
AutoDream will not trigger in the following situations:
|
||||||
|
|
||||||
|
- **KAIROS mode**: Uses a separate disk-skill dream
|
||||||
|
- **Remote mode**: `getIsRemoteMode() === true`
|
||||||
|
- **autoMemory not enabled**
|
||||||
|
- **`--bare` / SIMPLE mode**
|
||||||
|
- **Inside a sub-agent**: Only the main agent triggers it
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Four-Phase Consolidation Process
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Once all gates pass, AutoDream launches a **forked sub-agent** that operates according to the 4-phase prompt defined in `consolidationPrompt.ts`:
|
||||||
|
|
||||||
|
### Phase 1 -- Orient
|
||||||
|
|
||||||
|
```
|
||||||
|
- ls the memory directory, see existing files
|
||||||
|
- Read MEMORY.md index, understand the current knowledge structure
|
||||||
|
- Browse existing topic files to avoid creating duplicates
|
||||||
|
- If logs/ or sessions/ subdirectories exist, check recent entries
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2 -- Gather Recent Signal
|
||||||
|
|
||||||
|
Collects information in order of priority (highest first):
|
||||||
|
|
||||||
|
1. **Daily logs** -- `logs/YYYY/MM/YYYY-MM-DD.md` (append-stream logs)
|
||||||
|
2. **Drifted memories** -- Old facts that contradict the current codebase state
|
||||||
|
3. **Session transcript search** -- Narrow-scope `grep` searches in JSONL transcript files
|
||||||
|
|
||||||
|
```
|
||||||
|
Don't exhaustively read transcript files. Only look for content you already suspect is important.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3 -- Consolidate
|
||||||
|
|
||||||
|
- **Merge** new signals into existing topic files (rather than creating new near-duplicate files)
|
||||||
|
- **Convert** relative dates to absolute dates ("yesterday" -> "2026-04-03")
|
||||||
|
- **Delete** old facts that have been superseded
|
||||||
|
|
||||||
|
### Phase 4 -- Prune and Index
|
||||||
|
|
||||||
|
- Update `MEMORY.md`, keeping it within the line limit and <= 25KB
|
||||||
|
- Remove pointers to outdated memories
|
||||||
|
- Compress verbose entries (index lines >200 characters have their content moved into topic files)
|
||||||
|
- Add pointers to important memories
|
||||||
|
- Resolve conflicts (when two files disagree, fix the incorrect one)
|
||||||
|
|
||||||
|
**Key source**: `src/services/autoDream/consolidationPrompt.ts:10-64`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Security Restrictions
|
||||||
|
|
||||||
|
The AutoDream sub-agent operates under strict tool permission constraints:
|
||||||
|
|
||||||
|
### Bash: Read-Only Only
|
||||||
|
|
||||||
|
```
|
||||||
|
Allowed: ls, find, grep, cat, stat, wc, head, tail
|
||||||
|
Denied: All write, redirect, or state-modifying commands
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Operations: Memory Directory Only
|
||||||
|
|
||||||
|
`createAutoMemCanUseTool()` is the permission function shared by both `extractMemories` and `autoDream`:
|
||||||
|
|
||||||
|
```
|
||||||
|
Allowed: Read / Grep / Glob -- unrestricted
|
||||||
|
Allowed: Edit / Write -- only within the auto-memory directory
|
||||||
|
Denied: MCP / Agent / non-read-only Bash / other write operations
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key source**: `src/services/extractMemories/extractMemories.ts:167-171`
|
||||||
|
|
||||||
|
### Lock File Mechanism
|
||||||
|
|
||||||
|
Uses a `.consolidate-lock` file for process-level mutual exclusion:
|
||||||
|
|
||||||
|
| Mechanism | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| **Lock content** | PID of the holder |
|
||||||
|
| **Timestamp** | Lock file mtime = time of last consolidation |
|
||||||
|
| **Expiry** | Held for more than 1 hour is considered expired (prevents PID reuse issues) |
|
||||||
|
| **Contention** | Two processes write simultaneously -> last writer wins, loser exits on re-read |
|
||||||
|
| **Rollback** | On failure, mtime is rolled back to the pre-acquisition value so the next attempt is unaffected |
|
||||||
|
| **Crash recovery** | Stale mtime + dead PID -> next process reclaims the lock |
|
||||||
|
|
||||||
|
**Key source**: `src/services/autoDream/consolidationLock.ts`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. UI Presentation
|
||||||
|
|
||||||
|
### Bottom Status Bar
|
||||||
|
|
||||||
|
When AutoDream is running, the bottom status bar displays a **"dreaming"** label:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/tasks/pillLabel.ts:61-62
|
||||||
|
case 'dream':
|
||||||
|
return 'dreaming'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task Detail Dialog
|
||||||
|
|
||||||
|
Users can press `Shift+Down` to open the background task dialog and view real-time dream progress:
|
||||||
|
|
||||||
|
- **DreamDetailDialog** component displays:
|
||||||
|
- Number of sessions being reviewed
|
||||||
|
- Current phase: `starting` (analyzing) -> `updating` (modifying memory files)
|
||||||
|
- Latest assistant text response and tool call count
|
||||||
|
- List of file paths touched
|
||||||
|
|
||||||
|
- **Users can press `x` to terminate** an ongoing dream (triggers abort + lock rollback)
|
||||||
|
|
||||||
|
### Completion Notification
|
||||||
|
|
||||||
|
After the dream completes, if files were modified, an inline notification appears in the main session:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
appendSystemMessage({
|
||||||
|
...createMemorySavedMessage(dreamState.filesTouched),
|
||||||
|
verb: 'Improved',
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key source**:
|
||||||
|
- `src/tasks/DreamTask/DreamTask.ts` -- Task state management
|
||||||
|
- `src/components/tasks/DreamDetailDialog.tsx` -- UI component
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Configuration and Toggles
|
||||||
|
|
||||||
|
### settings.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"autoDreamEnabled": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **When explicitly set**: Uses the user's value directly
|
||||||
|
- **When not set**: Controlled by the remote GrowthBook feature flag `tengu_onyx_plover`
|
||||||
|
|
||||||
|
**Key source** `src/services/autoDream/config.ts:13-21`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export function isAutoDreamEnabled(): boolean {
|
||||||
|
const setting = getInitialSettings().autoDreamEnabled
|
||||||
|
if (setting !== undefined) return setting
|
||||||
|
const gb = getFeatureValue_CACHED_MAY_BE_STALE<{ enabled?: unknown } | null>(
|
||||||
|
'tengu_onyx_plover', null,
|
||||||
|
)
|
||||||
|
return gb?.enabled === true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remote Configuration Parameters
|
||||||
|
|
||||||
|
The `tengu_onyx_plover` feature flag can configure:
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `enabled` | boolean | -- | Master feature toggle |
|
||||||
|
| `minHours` | number | 24 | Minimum interval (hours) |
|
||||||
|
| `minSessions` | number | 5 | Minimum session count |
|
||||||
|
|
||||||
|
### Manual Trigger: `/dream`
|
||||||
|
|
||||||
|
In addition to automatic triggering, users can manually trigger memory consolidation via the `/dream` command. Manual triggers call `recordConsolidation()` to update the lock file timestamp.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Relationship with extractMemories
|
||||||
|
|
||||||
|
| Dimension | extractMemories | autoDream |
|
||||||
|
|-----------|----------------|-----------|
|
||||||
|
| **Trigger frequency** | After each conversation turn | Every 24h + 5 sessions |
|
||||||
|
| **Trigger location** | `stopHooks.ts` L149 | `stopHooks.ts` L155 |
|
||||||
|
| **Processing scope** | Recent messages from the current conversation | Historical transcripts from multiple sessions |
|
||||||
|
| **Goal** | Extract new memories | Consolidate/deduplicate/prune existing memories |
|
||||||
|
| **Human analogy** | Jotting down notes during the day | Organizing the notebook while sleeping |
|
||||||
|
| **Shared component** | `createAutoMemCanUseTool` | `createAutoMemCanUseTool` |
|
||||||
|
| **Forked agent** | Max 5 turns | No turn limit |
|
||||||
|
| **Transcript** | Not recorded (`skipTranscript`) | Not recorded (`skipTranscript`) |
|
||||||
|
|
||||||
|
### Collaboration Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
After each conversation turn
|
||||||
|
|
|
||||||
|
extractMemories -> Extract new memory fragments -> Write *.md + MEMORY.md
|
||||||
|
|
|
||||||
|
(After accumulating 24h + 5 sessions)
|
||||||
|
|
|
||||||
|
autoDream -> Review all memories + session transcripts
|
||||||
|
|
|
||||||
|
Merge duplicates / Fix stale data / Delete conflicts / Compress index
|
||||||
|
|
|
||||||
|
MEMORY.md and topic files refreshed
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Source Code Navigation
|
||||||
|
|
||||||
|
| File | Responsibility |
|
||||||
|
|------|----------------|
|
||||||
|
| `src/services/autoDream/autoDream.ts` | Main logic: gate checks, launch forked agent, progress monitoring |
|
||||||
|
| `src/services/autoDream/config.ts` | Toggle control: settings.json or GrowthBook |
|
||||||
|
| `src/services/autoDream/consolidationPrompt.ts` | Dream prompt: 4-phase consolidation workflow |
|
||||||
|
| `src/services/autoDream/consolidationLock.ts` | Lock file mechanism: concurrency prevention, timestamps, rollback |
|
||||||
|
| `src/tasks/DreamTask/DreamTask.ts` | UI task registration: state management, termination, rollback |
|
||||||
|
| `src/tasks/pillLabel.ts` | Bottom status bar label: "dreaming" |
|
||||||
|
| `src/components/tasks/DreamDetailDialog.tsx` | Dream detail dialog UI |
|
||||||
|
| `src/query/stopHooks.ts` | Execution entry point: triggers after each response |
|
||||||
|
| `src/utils/backgroundHousekeeping.ts` | Initialization entry point: `initAutoDream()` |
|
||||||
|
| `src/services/extractMemories/extractMemories.ts` | Shared `createAutoMemCanUseTool` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Analytics Events
|
||||||
|
|
||||||
|
AutoDream records its operational state through the following events:
|
||||||
|
|
||||||
|
| Event | Timing | Attached Data |
|
||||||
|
|-------|--------|---------------|
|
||||||
|
| `tengu_auto_dream_fired` | Dream started | `hours_since`, `sessions_since` |
|
||||||
|
| `tengu_auto_dream_completed` | Dream completed | `cache_read`, `cache_created`, `output`, `sessions_reviewed` |
|
||||||
|
| `tengu_auto_dream_failed` | Dream failed | -- |
|
||||||
|
After Width: | Height: | Size: 490 KiB |
|
After Width: | Height: | Size: 445 KiB |
|
After Width: | Height: | Size: 398 KiB |
|
After Width: | Height: | Size: 508 KiB |
|
After Width: | Height: | Size: 430 KiB |
|
After Width: | Height: | Size: 568 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 681 KiB |
|
After Width: | Height: | Size: 517 KiB |
|
After Width: | Height: | Size: 757 KiB |
|
After Width: | Height: | Size: 484 KiB |
|
After Width: | Height: | Size: 521 KiB |
@@ -0,0 +1,121 @@
|
|||||||
|
# Claude Code Memory System Documentation
|
||||||
|
|
||||||
|
> Complete usage guide and technical implementation documentation for the memory system
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation Index
|
||||||
|
|
||||||
|
### [01-usage-guide.md](./01-usage-guide.md) — Usage Guide
|
||||||
|
|
||||||
|
A comprehensive user-facing manual covering:
|
||||||
|
|
||||||
|
- **Four memory types**: User (user profile), Feedback (behavioral feedback), Project (project context), Reference (external references)
|
||||||
|
- **Four trigger methods**: Automatic extraction, explicit requests, `/memory` command, `/remember` command
|
||||||
|
- **Storage format**: YAML frontmatter + Markdown content
|
||||||
|
- **Management operations**: Forgetting, ignoring, manual editing, disabling, custom directories
|
||||||
|
- **Lifecycle**: From learning to injection, freshness management
|
||||||
|
|
||||||
|
**Target audience**: All Claude Code users
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [02-implementation.md](./02-implementation.md) — Implementation Details
|
||||||
|
|
||||||
|
A deep technical analysis for developers, covering:
|
||||||
|
|
||||||
|
- **5 core modules**: Path resolution, prompt construction, memory scanning, intelligent retrieval, automatic extraction
|
||||||
|
- **Path resolution system**: Priority chain, security validation, enable conditions
|
||||||
|
- **System prompt injection**: `loadMemoryPrompt()` -> `buildMemoryLines()`, MEMORY.md truncation strategy
|
||||||
|
- **Automatic memory extraction**: Forked agent, mutual exclusion mechanism, tool permissions, merge mechanism
|
||||||
|
- **Intelligent retrieval**: `scanMemoryFiles()` -> Sonnet selection -> freshness warnings
|
||||||
|
- **Agent memory**: Three-level scoping (user/project/local)
|
||||||
|
- **Team memory sync**: Pull/Push API, merge semantics
|
||||||
|
- **Complete data flow**: From session startup to context injection
|
||||||
|
|
||||||
|
**Target audience**: Contributors, architects, developers who want a deep understanding of the implementation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [03-autodream.md](./03-autodream.md) — AutoDream Memory Consolidation
|
||||||
|
|
||||||
|
Claude's "dreaming" mechanism -- a deep dive into background silent memory consolidation, covering:
|
||||||
|
|
||||||
|
- **Core concept**: Like how the human brain organizes memories during sleep, periodically reviewing multiple sessions to consolidate knowledge
|
||||||
|
- **Five-gate check**: Feature toggle -> Time gate (24h) -> Scan throttle (10min) -> Session gate (5 sessions) -> Lock gate
|
||||||
|
- **Four-phase process**: Orient -> Gather -> Consolidate -> Prune
|
||||||
|
- **Security restrictions**: Read-only Bash, write operations limited to memory directory, PID lock file mutual exclusion
|
||||||
|
- **UI presentation**: Bottom "dreaming" label, Shift+Down detail dialog, completion notification
|
||||||
|
- **Configuration control**: settings.json local toggle + GrowthBook remote feature flag
|
||||||
|
- **Comparison with extractMemories**: Taking notes during the day vs. organizing the notebook while sleeping
|
||||||
|
|
||||||
|
**Target audience**: Contributors, architects, developers interested in Claude's automated memory management
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Illustrations
|
||||||
|
|
||||||
|
All illustrations use a dark background (#1a1a2e) + Anthropic brand copper-orange (#D97757) style, consistent with the official Claude Code documentation.
|
||||||
|
|
||||||
|
| Image | Description | Size |
|
||||||
|
|-------|-------------|------|
|
||||||
|
| `01-memory-overview.png` | Memory system overview -- four-layer architecture (trigger/type/storage/retrieval) | 632 KB |
|
||||||
|
| `02-memory-types.png` | Four memory types -- 2x2 grid showing User/Feedback/Project/Reference | 507 KB |
|
||||||
|
| `03-memory-trigger.png` | Memory trigger flow -- four paths from conversation to storage | 474 KB |
|
||||||
|
| `04-memory-lifecycle.png` | Memory lifecycle -- complete cycle flow + freshness checks | 1.0 MB |
|
||||||
|
| `05-architecture-overview.png` | Implementation architecture overview -- 5 core modules + auxiliary modules | 3.5 MB |
|
||||||
|
| `06-path-resolution.png` | Path resolution flow -- three-level priority + security validation | 1.0 MB |
|
||||||
|
| `07-prompt-injection.png` | Prompt injection flow -- loadMemoryPrompt dispatch logic | 1.1 MB |
|
||||||
|
| `08-auto-extraction.png` | Auto-extraction flow -- forked agent complete process | 1.2 MB |
|
||||||
|
| `09-memory-retrieval.png` | Intelligent retrieval flow -- Sonnet selection + freshness management | 816 KB |
|
||||||
|
| `10-agent-memory.png` | Agent memory scoping -- three-level nested structure | 523 KB |
|
||||||
|
| `11-autodream-overview.png` | AutoDream overview -- dreaming mechanism core architecture and human sleep analogy | 777 KB |
|
||||||
|
| `12-autodream-trigger.png` | AutoDream trigger flow -- five-gate check chain | 493 KB |
|
||||||
|
| `13-autodream-phases.png` | AutoDream four phases -- Orient/Gather/Consolidate/Prune | 602 KB |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### For Users
|
||||||
|
|
||||||
|
1. Read the [Usage Guide](./01-usage-guide.md)
|
||||||
|
2. Learn about the four memory types and trigger methods
|
||||||
|
3. Try the `/memory` and `/remember` commands
|
||||||
|
|
||||||
|
### For Developers
|
||||||
|
|
||||||
|
1. Read the [Implementation Details](./02-implementation.md)
|
||||||
|
2. Explore the source code:
|
||||||
|
- `src/memdir/paths.ts` -- Path resolution
|
||||||
|
- `src/memdir/memdir.ts` -- Prompt construction
|
||||||
|
- `src/memdir/memoryScan.ts` -- Memory scanning
|
||||||
|
- `src/memdir/findRelevantMemories.ts` -- Intelligent retrieval
|
||||||
|
- `src/services/extractMemories/` -- Automatic extraction
|
||||||
|
3. Understand the data flow and module interactions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Concepts Quick Reference
|
||||||
|
|
||||||
|
| Concept | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| **MEMORY.md** | Index file, always loaded into context (max 200 lines / 25KB) |
|
||||||
|
| **Topic files** | `*.md` files containing frontmatter + content |
|
||||||
|
| **Auto-extraction** | Runs in the background after each response; a forked agent analyzes the conversation |
|
||||||
|
| **AutoDream** | Triggers after 24h + 5 sessions; consolidates/deduplicates/prunes all memories in the background |
|
||||||
|
| **Intelligent retrieval** | Sonnet model selects up to 5 relevant memories from the entire collection |
|
||||||
|
| **Freshness** | <=1 day: no warning; >1 day: stale warning attached |
|
||||||
|
| **Forked agent** | Shares prompt cache, restricted tool permissions, max 5 turns |
|
||||||
|
| **Three-level scoping** | Agent memory: user (global) > project (project-level) > local (local-level) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Resources
|
||||||
|
|
||||||
|
- [Claude Code Haha Home](/en/)
|
||||||
|
- [Memory system source code](https://github.com/NanmiCoder/cc-haha/tree/main/src/memdir/)
|
||||||
|
- [Auto-extraction service](https://github.com/NanmiCoder/cc-haha/tree/main/src/services/extractMemories/)
|
||||||
|
- [AutoDream service](https://github.com/NanmiCoder/cc-haha/tree/main/src/services/autoDream/)
|
||||||
|
- [DreamTask](https://github.com/NanmiCoder/cc-haha/tree/main/src/tasks/DreamTask/)
|
||||||
|
- [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Fixes Compared with the Original Leaked Source
|
||||||
|
|
||||||
|
|
||||||
|
The leaked source could not run directly. This repository mainly fixes the following issues:
|
||||||
|
|
||||||
|
| Issue | Root cause | Fix |
|
||||||
|
|------|------|------|
|
||||||
|
| TUI does not start | The entry script routed no-argument startup to the recovery CLI | Restored the full `cli.tsx` entry |
|
||||||
|
| Startup hangs | The `verify` skill imports a missing `.md` file, causing Bun's text loader to hang indefinitely | Added stub `.md` files |
|
||||||
|
| `--print` hangs | `filePersistence/types.ts` was missing | Added type stub files |
|
||||||
|
| `--print` hangs | `ultraplan/prompt.txt` was missing | Added resource stub files |
|
||||||
|
| **Enter key does nothing** | The `modifiers-napi` native package was missing, `isModifierPressed()` threw, `handleEnter` was interrupted, and `onSubmit` never ran | Added try/catch fault tolerance |
|
||||||
|
| Setup was skipped | `preload.ts` automatically set `LOCAL_RECOVERY=1`, skipping all initialization | Removed the default setting |
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Project Structure
|
||||||
|
|
||||||
|
|
||||||
|
```text
|
||||||
|
bin/claude-haha # Entry script
|
||||||
|
preload.ts # Bun preload (sets MACRO globals)
|
||||||
|
.env.example # Environment variable template
|
||||||
|
src/
|
||||||
|
├── entrypoints/cli.tsx # Main CLI entry
|
||||||
|
├── main.tsx # Main TUI logic (Commander.js + React/Ink)
|
||||||
|
├── localRecoveryCli.ts # Fallback Recovery CLI
|
||||||
|
├── setup.ts # Startup initialization
|
||||||
|
├── screens/REPL.tsx # Interactive REPL screen
|
||||||
|
├── ink/ # Ink terminal rendering engine
|
||||||
|
├── components/ # UI components
|
||||||
|
├── tools/ # Agent tools (Bash, Edit, Grep, etc.)
|
||||||
|
├── commands/ # Slash commands (/commit, /review, etc.)
|
||||||
|
├── skills/ # Skill system
|
||||||
|
├── services/ # Service layer (API, MCP, OAuth, etc.)
|
||||||
|
├── hooks/ # React hooks
|
||||||
|
└── utils/ # Utility functions
|
||||||
|
```
|
||||||
@@ -0,0 +1,393 @@
|
|||||||
|
# Claude Code Skills System -- Usage Guide
|
||||||
|
|
||||||
|
> Skills are the extensible capability engine of Claude Code, allowing you to define custom automated workflows using Markdown files.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#1-what-are-skills">What Are Skills</a> · <a href="#2-six-skill-sources">Six Sources</a> · <a href="#3-skill-definition-format">Definition Format</a> · <a href="#4-invocation-methods">Invocation</a> · <a href="#5-execution-context">Execution Context</a> · <a href="#6-conditional-activation">Conditional Activation</a> · <a href="#7-permission-control">Permissions</a> · <a href="#8-quick-reference">Quick Reference</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. What Are Skills?
|
||||||
|
|
||||||
|
Skills are Claude Code's **extensible capability plugin system**. Each Skill is a Markdown file (with YAML frontmatter) that defines a specialized prompt and behavioral configuration, enabling Claude to execute professional workflows in specific scenarios.
|
||||||
|
|
||||||
|
Core capabilities:
|
||||||
|
|
||||||
|
| Capability | Description |
|
||||||
|
|------------|-------------|
|
||||||
|
| Specialized Workflows | Define standard processes for code review, TDD, debugging, etc. |
|
||||||
|
| Tool Permission Control | Restrict a Skill to only use specified tools |
|
||||||
|
| Model Switching | Assign different models to different Skills |
|
||||||
|
| Execution Isolation | Fork mode runs in an isolated sub-agent |
|
||||||
|
| Conditional Activation | Activate only when specific files are being operated on |
|
||||||
|
| Hook Injection | Automatically register lifecycle hooks when a Skill is invoked |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Six Skill Sources
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Claude Code loads Skills from 6 different sources, ordered by priority from highest to lowest:
|
||||||
|
|
||||||
|
### 1. Bundled (Built-in Skills)
|
||||||
|
|
||||||
|
Compiled into the CLI binary and available to all users. Defined in TypeScript and registered via `registerBundledSkill()`.
|
||||||
|
|
||||||
|
**Current Built-in Skills:**
|
||||||
|
|
||||||
|
| Skill | Description | Special Conditions |
|
||||||
|
|-------|-------------|--------------------|
|
||||||
|
| `/verify` | Verify code changes | -- |
|
||||||
|
| `/debug` | Debugging assistant | -- |
|
||||||
|
| `/simplify` | Code simplification review | -- |
|
||||||
|
| `/remember` | Memory management | Requires auto-memory enabled |
|
||||||
|
| `/batch` | Batch processing | -- |
|
||||||
|
| `/stuck` | Help when stuck | -- |
|
||||||
|
| `/skillify` | Create a new Skill | -- |
|
||||||
|
| `/keybindings` | Custom keyboard shortcuts | -- |
|
||||||
|
| `/loop` | Timed loop tasks | AGENT_TRIGGERS feature gate |
|
||||||
|
| `/schedule` | Remote agent scheduling | AGENT_TRIGGERS_REMOTE feature gate |
|
||||||
|
| `/claude-api` | Claude API integration | BUILDING_CLAUDE_APPS feature gate |
|
||||||
|
| `/dream` | Automatic memory organization | KAIROS feature gate |
|
||||||
|
|
||||||
|
### 2. Managed (Policy-Managed Skills)
|
||||||
|
|
||||||
|
Controlled by organizational policies, stored in `<managed-path>/.claude/skills/`. Suitable for enterprise deployments.
|
||||||
|
|
||||||
|
### 3. User (User Skills)
|
||||||
|
|
||||||
|
Defined by individual users, stored in `~/.claude/skills/`.
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.claude/skills/
|
||||||
|
├── my-review/
|
||||||
|
│ └── SKILL.md ← Main Skill file
|
||||||
|
├── deploy-check/
|
||||||
|
│ └── SKILL.md
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Project (Project Skills)
|
||||||
|
|
||||||
|
Defined at the project level, stored in `.claude/skills/`. Can be committed to version control.
|
||||||
|
|
||||||
|
```
|
||||||
|
your-project/
|
||||||
|
└── .claude/
|
||||||
|
└── skills/
|
||||||
|
├── lint-fix/
|
||||||
|
│ └── SKILL.md
|
||||||
|
└── test-runner/
|
||||||
|
└── SKILL.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Plugin (Plugin Skills)
|
||||||
|
|
||||||
|
Provided by installed plugins. Plugins declare Skills directories via `skillsPath` / `skillsPaths` in their manifest.
|
||||||
|
|
||||||
|
Naming format: `{pluginName}:{skillName}`
|
||||||
|
|
||||||
|
```
|
||||||
|
Examples: superpowers:code-reviewer
|
||||||
|
superpowers:brainstorming
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. MCP (MCP Server Skills)
|
||||||
|
|
||||||
|
Provided by connected MCP servers, naming format: `mcp__server-name__prompt-name`.
|
||||||
|
|
||||||
|
**Security restriction**: MCP Skills are from remote untrusted sources and are **prohibited from executing** `!`...`` inline shell commands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Skill Definition Format
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
|
||||||
|
Each Skill is a directory containing a `SKILL.md` file:
|
||||||
|
|
||||||
|
```
|
||||||
|
skill-name/
|
||||||
|
└── SKILL.md ← Filename must be SKILL.md (case-insensitive)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complete Frontmatter Fields
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
name: My Skill # Display name (optional, defaults to directory name)
|
||||||
|
description: What this skill does # Description (required; auto-extracted from content if missing)
|
||||||
|
when_to_use: When to use this skill # Usage scenario description (optional)
|
||||||
|
version: 1.0.0 # Version number (optional)
|
||||||
|
|
||||||
|
# ── Invocation Control ──
|
||||||
|
user-invocable: true # Whether user can invoke via /skill-name (default: true)
|
||||||
|
disable-model-invocation: false # Prevent model from invoking via Skill tool (optional)
|
||||||
|
argument-hint: "<file path>" # Argument hint (optional)
|
||||||
|
|
||||||
|
# ── Execution Configuration ──
|
||||||
|
context: inline # Execution context: inline (default) or fork (sub-agent)
|
||||||
|
agent: general-purpose # Agent type when forked (optional)
|
||||||
|
model: sonnet # Model override: haiku / sonnet / opus / inherit (optional)
|
||||||
|
effort: high # Thinking effort: low / medium / high / max (optional)
|
||||||
|
allowed-tools: "Bash, Read" # Allowed tools (comma-separated or YAML list)
|
||||||
|
shell: bash # Shell type: bash (default) or powershell
|
||||||
|
|
||||||
|
# ── Conditional Activation ──
|
||||||
|
paths: "src/**/*.ts, test/**/*.ts" # Glob patterns; activate only when matching files are operated on
|
||||||
|
|
||||||
|
# ── Lifecycle Hooks ──
|
||||||
|
hooks:
|
||||||
|
PreToolUse:
|
||||||
|
- matcher: "Bash"
|
||||||
|
hooks:
|
||||||
|
- command: "echo 'Before bash'"
|
||||||
|
once: true # Execute only once
|
||||||
|
---
|
||||||
|
|
||||||
|
# Skill Body Content
|
||||||
|
|
||||||
|
This is the Markdown-formatted prompt that Claude sees when this Skill is invoked.
|
||||||
|
|
||||||
|
Supported special syntax:
|
||||||
|
- `${CLAUDE_SKILL_DIR}` — Expands to the Skill's directory
|
||||||
|
- `${CLAUDE_SESSION_ID}` — Expands to the current session ID
|
||||||
|
- `$ARGUMENTS` / `${ARG1}` — Argument substitution
|
||||||
|
- !`shell command` — Inline shell command execution
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontmatter Field Reference
|
||||||
|
|
||||||
|
| Field | Type | Default | Description |
|
||||||
|
|-------|------|---------|-------------|
|
||||||
|
| `name` | string | Directory name | Display name override |
|
||||||
|
| `description` | string | Auto-extracted | Brief Skill description |
|
||||||
|
| `when_to_use` | string | -- | Usage scenario description |
|
||||||
|
| `user-invocable` | boolean | true | Whether user can invoke via /name |
|
||||||
|
| `disable-model-invocation` | boolean | false | Prevent model invocation |
|
||||||
|
| `context` | `inline` \| `fork` | inline | Execution context |
|
||||||
|
| `agent` | string | general-purpose | Agent type when forked |
|
||||||
|
| `model` | string | Inherited | Model override (haiku/sonnet/opus) |
|
||||||
|
| `effort` | string \| int | -- | Thinking effort level |
|
||||||
|
| `allowed-tools` | string \| list | All | Allowed tools whitelist |
|
||||||
|
| `paths` | string \| list | -- | Conditional activation glob patterns |
|
||||||
|
| `shell` | `bash` \| `powershell` | bash | Shell command type |
|
||||||
|
| `hooks` | object | -- | Lifecycle hook configuration |
|
||||||
|
| `argument-hint` | string | -- | Argument hint text |
|
||||||
|
| `version` | string | -- | Version number |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Invocation Methods
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Method 1: User Slash Commands
|
||||||
|
|
||||||
|
Type `/skill-name` directly in the terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
> /commit
|
||||||
|
> /review-pr 123
|
||||||
|
> /verify
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prerequisite**: The Skill's `user-invocable` must be `true`.
|
||||||
|
|
||||||
|
### Method 2: Automatic Model Invocation
|
||||||
|
|
||||||
|
When Claude identifies a suitable Skill during conversation, it automatically invokes it via SkillTool:
|
||||||
|
|
||||||
|
```
|
||||||
|
User: Please review this code for me
|
||||||
|
Claude: [Invokes superpowers:code-reviewer via SkillTool]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prerequisite**: The Skill's `disable-model-invocation` must not be `true`.
|
||||||
|
|
||||||
|
### Method 3: Nested Invocation
|
||||||
|
|
||||||
|
One Skill can trigger another during execution:
|
||||||
|
|
||||||
|
```
|
||||||
|
/verify → internally invokes → /simplify
|
||||||
|
```
|
||||||
|
|
||||||
|
Tracked in telemetry via `invocation_trigger: 'nested-skill'`.
|
||||||
|
|
||||||
|
### Invocation Priority
|
||||||
|
|
||||||
|
When Skills with the same name exist in multiple sources, they are resolved in the following order (first match wins):
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Bundled (built-in) ← Highest priority
|
||||||
|
2. Built-in Plugin
|
||||||
|
3. Skill Dirs (user/project directories)
|
||||||
|
4. Workflow Commands
|
||||||
|
5. Plugin Commands
|
||||||
|
6. Plugin Skills
|
||||||
|
7. Built-in Commands ← Lowest priority
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Execution Context
|
||||||
|
|
||||||
|
### Inline Mode (Default)
|
||||||
|
|
||||||
|
Skill content is **expanded into the current conversation**. Claude directly sees the prompt and executes within the same context.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
context: inline # Default value, can be omitted
|
||||||
|
```
|
||||||
|
|
||||||
|
**Characteristics:**
|
||||||
|
- Shares the parent conversation's token budget
|
||||||
|
- Can access conversation history context
|
||||||
|
- `allowedTools` restricts available tools for the current turn
|
||||||
|
- `model` overrides the model used for the current turn
|
||||||
|
|
||||||
|
### Fork Mode (Sub-Agent)
|
||||||
|
|
||||||
|
The Skill runs in an **isolated sub-agent** with its own independent token budget and context.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
context: fork
|
||||||
|
agent: general-purpose # Optional, specifies agent type
|
||||||
|
```
|
||||||
|
|
||||||
|
**Characteristics:**
|
||||||
|
- Independent token budget; does not consume the parent conversation's quota
|
||||||
|
- Isolated conversation context
|
||||||
|
- Can specify a different agent type (e.g., `Bash`, `general-purpose`)
|
||||||
|
- Results are extracted and returned to the parent conversation upon completion
|
||||||
|
- Supports progress reporting (`onProgress` callback)
|
||||||
|
|
||||||
|
### Comparison of Both Modes
|
||||||
|
|
||||||
|
| Feature | Inline | Fork |
|
||||||
|
|---------|--------|------|
|
||||||
|
| Token Budget | Shared with parent | Independent budget |
|
||||||
|
| Context Access | Full conversation history | Skill prompt only |
|
||||||
|
| Result Return | Directly in conversation | Text extracted into tool_result |
|
||||||
|
| Use Cases | Brief guidance, extended context | Long tasks, independent computation |
|
||||||
|
| Tool Restrictions | contextModifier modification | modifiedGetAppState |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Conditional Activation
|
||||||
|
|
||||||
|
Skills can use the `paths` frontmatter to implement **on-demand activation**, becoming visible to the model only when matching files are operated on.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
name: TypeScript Fix
|
||||||
|
description: Fix TypeScript type errors
|
||||||
|
paths: "src/**/*.ts, test/**/*.ts"
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
```
|
||||||
|
1. All Skills are loaded at startup
|
||||||
|
2. Skills with paths are stored in the conditionalSkills Map (not exposed to the model)
|
||||||
|
3. When the user operates on a file (Read/Write/Edit)
|
||||||
|
4. activateConditionalSkillsForPaths() matches using the ignore library
|
||||||
|
5. On match → moved to the dynamicSkills Map → visible to model
|
||||||
|
6. Once activated, remains active for the entire session
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dynamic Discovery
|
||||||
|
|
||||||
|
In addition to conditional activation, Skills also support **runtime discovery**:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. User operates on a file in a deeply nested directory
|
||||||
|
2. discoverSkillDirsForPaths() traverses upward from the file path
|
||||||
|
3. Looks for .claude/skills/ directories (not beyond cwd)
|
||||||
|
4. Skips directories ignored by .gitignore
|
||||||
|
5. New directory found → addSkillDirectories() → load and register
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Permission Control
|
||||||
|
|
||||||
|
### Auto-Allow
|
||||||
|
|
||||||
|
If a Skill contains only "safe properties" (no `allowedTools`, no `hooks`, no `fork`), it is **automatically approved** for execution without user confirmation.
|
||||||
|
|
||||||
|
### Manual Confirmation
|
||||||
|
|
||||||
|
Skills with tool restrictions, hooks, or fork execution will prompt the user on first invocation:
|
||||||
|
|
||||||
|
```
|
||||||
|
Execute skill: my-custom-skill
|
||||||
|
Allow? (y)es / (n)o / (a)lways allow / (d)eny
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission Rules
|
||||||
|
|
||||||
|
| Rule Type | Format | Description |
|
||||||
|
|-----------|--------|-------------|
|
||||||
|
| Exact Allow | `Skill:commit` | Allow execution of the commit Skill |
|
||||||
|
| Prefix Allow | `Skill:review:*` | Allow all Skills with the review: prefix |
|
||||||
|
| Exact Deny | `Skill:dangerous` set to deny | Deny execution |
|
||||||
|
| Prefix Deny | `Skill:untrusted:*` set to deny | Deny all Skills with the untrusted: prefix |
|
||||||
|
|
||||||
|
**Processing order:** Deny rules → Allow rules → Safe property check → Ask user
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Quick Reference
|
||||||
|
|
||||||
|
### Creating a Skill
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Create directory
|
||||||
|
mkdir -p ~/.claude/skills/my-skill
|
||||||
|
|
||||||
|
# 2. Create SKILL.md
|
||||||
|
cat > ~/.claude/skills/my-skill/SKILL.md << 'EOF'
|
||||||
|
---
|
||||||
|
name: My Skill
|
||||||
|
description: An example Skill
|
||||||
|
user-invocable: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Skill Content
|
||||||
|
|
||||||
|
Hello, this is my custom Skill.
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Operations
|
||||||
|
|
||||||
|
| Operation | Method |
|
||||||
|
|-----------|--------|
|
||||||
|
| Create a Skill | `~/.claude/skills/<name>/SKILL.md` |
|
||||||
|
| Project-level Skill | `.claude/skills/<name>/SKILL.md` |
|
||||||
|
| Invoke a Skill | Type `/skill-name` in terminal |
|
||||||
|
| View available Skills | Type `/skills` in terminal |
|
||||||
|
| Create Skill with AI | `/skillify` |
|
||||||
|
| Restrict tools | Add `allowed-tools` in frontmatter |
|
||||||
|
| Fork execution | Add `context: fork` in frontmatter |
|
||||||
|
| Conditional activation | Add `paths: "src/**"` in frontmatter |
|
||||||
|
|
||||||
|
### Skill Availability Matrix
|
||||||
|
|
||||||
|
| Source | User Invocable | Model Invocable | Supports Fork | Supports Hooks |
|
||||||
|
|--------|---------------|-----------------|---------------|----------------|
|
||||||
|
| Bundled | Per definition | Per definition | Yes | Yes |
|
||||||
|
| Managed | Yes | Yes | Yes | Yes |
|
||||||
|
| User | Yes (default) | Yes | Yes | Yes |
|
||||||
|
| Project | Yes (default) | Yes | Yes | Yes |
|
||||||
|
| Plugin | Per config | Per config | Yes | Yes |
|
||||||
|
| MCP | Per config | Per config | Yes | No (security restriction) |
|
||||||
@@ -0,0 +1,897 @@
|
|||||||
|
# Claude Code Skills System -- Implementation Details
|
||||||
|
|
||||||
|
> A deep dive into how Skills are discovered, loaded, injected, executed, and managed.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#1-overall-architecture">Architecture</a> · <a href="#2-skill-discovery-and-loading">Discovery & Loading</a> · <a href="#3-frontmatter-parsing">Frontmatter Parsing</a> · <a href="#4-skill-injection-into-conversations">Injection</a> · <a href="#5-skilltool-execution-engine">Execution Engine</a> · <a href="#6-fork-sub-agent-execution">Fork Execution</a> · <a href="#7-conditional-activation-and-dynamic-discovery">Conditional Activation</a> · <a href="#8-hook-integration">Hook Integration</a> · <a href="#9-permission-system">Permission System</a> · <a href="#10-complete-lifecycle">Complete Lifecycle</a> · <a href="#11-source-code-index">Source Index</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Overall Architecture
|
||||||
|
|
||||||
|
The Skills system consists of 5 core modules working together:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ Skills System │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────┐ ┌──────────────┐ ┌────────────┐ │
|
||||||
|
│ │ Discovery │ │ Prompt │ │ SkillTool │ │
|
||||||
|
│ │ & Loading │→│ Injection │→│ Execution │ │
|
||||||
|
│ └─────────────┘ └──────────────┘ └────────────┘ │
|
||||||
|
│ ↑ ↓ │
|
||||||
|
│ ┌─────────────┐ ┌────────────┐ │
|
||||||
|
│ │ Activation │ │ Context │ │
|
||||||
|
│ │ Conditional │←────────────────────│ Modifier │ │
|
||||||
|
│ └─────────────┘ └────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Module Responsibilities
|
||||||
|
|
||||||
|
| Module | Core File | Responsibility |
|
||||||
|
|--------|-----------|----------------|
|
||||||
|
| Discovery | `loadSkillsDir.ts` | Discover and load Skills from 6 sources |
|
||||||
|
| Prompt | `prompt.ts` + `attachments.ts` | Inject Skill listing into system-reminder |
|
||||||
|
| SkillTool | `SkillTool.ts` | Validation, permission checks, Skill execution |
|
||||||
|
| Activation | `loadSkillsDir.ts` (second half) | Conditional activation and dynamic discovery |
|
||||||
|
| Context | `forkedAgent.ts` | Context preparation and modification |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Skill Discovery and Loading
|
||||||
|
|
||||||
|
### Loading Entry Point
|
||||||
|
|
||||||
|
Skills loading begins with the `getSkills()` function in `commands.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/commands.ts:351-396
|
||||||
|
async function getSkills(cwd: string) {
|
||||||
|
const [skillDirCommands, pluginSkills] = await Promise.all([
|
||||||
|
getSkillDirCommands(cwd), // Directory Skills (managed/user/project)
|
||||||
|
getPluginSkills(), // Plugin Skills
|
||||||
|
])
|
||||||
|
const bundledSkills = getBundledSkills() // Built-in Skills
|
||||||
|
const builtinPluginSkills = getBuiltinPluginSkillCommands() // Built-in plugin Skills
|
||||||
|
return { skillDirCommands, pluginSkills, bundledSkills, builtinPluginSkills }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Aggregation and Ordering
|
||||||
|
|
||||||
|
All Skills from all sources are aggregated in `loadAllCommands()`, ordered by priority:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/commands.ts:447-467
|
||||||
|
const loadAllCommands = memoize(async (cwd: string): Promise<Command[]> => {
|
||||||
|
return [
|
||||||
|
...bundledSkills, // 1. Built-in Skills (highest priority)
|
||||||
|
...builtinPluginSkills, // 2. Built-in plugin Skills
|
||||||
|
...skillDirCommands, // 3. Directory Skills (managed → user → project)
|
||||||
|
...workflowCommands, // 4. Workflow commands
|
||||||
|
...pluginCommands, // 5. Plugin commands
|
||||||
|
...pluginSkills, // 6. Plugin Skills
|
||||||
|
...COMMANDS(), // 7. Built-in commands (lowest priority)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key feature:** Uses `memoize` for caching to avoid redundant disk I/O.
|
||||||
|
|
||||||
|
### Directory Skill Loading Flow
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
`getSkillDirCommands()` is the core loading function for directory Skills:
|
||||||
|
|
||||||
|
```
|
||||||
|
getSkillDirCommands(cwd)
|
||||||
|
├─ Determine loading paths
|
||||||
|
│ ├─ managed: ${MANAGED_PATH}/.claude/skills/
|
||||||
|
│ ├─ user: ~/.claude/skills/
|
||||||
|
│ ├─ project: .claude/skills/ (traverse upward to HOME)
|
||||||
|
│ └─ additional: paths specified via --add-dir
|
||||||
|
│
|
||||||
|
├─ Parallel loading (Promise.all)
|
||||||
|
│ ├─ loadSkillsFromSkillsDir(managedDir, 'policySettings')
|
||||||
|
│ ├─ loadSkillsFromSkillsDir(userDir, 'userSettings')
|
||||||
|
│ ├─ loadSkillsFromSkillsDir(projectDirs, 'projectSettings')
|
||||||
|
│ ├─ loadSkillsFromSkillsDir(additionalDirs, 'projectSettings')
|
||||||
|
│ └─ loadSkillsFromCommandsDir(cwd) ← Legacy /commands/ format compatibility
|
||||||
|
│
|
||||||
|
├─ Deduplication (by realpath)
|
||||||
|
│ └─ getFileIdentity(filePath) → realpath resolves symlinks
|
||||||
|
│ └─ seenFileIds Map, first occurrence wins
|
||||||
|
│
|
||||||
|
└─ Separate conditional Skills
|
||||||
|
├─ No paths → unconditionalSkills (immediately available)
|
||||||
|
└─ Has paths → conditionalSkills Map (awaiting activation)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deduplication Mechanism
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/skills/loadSkillsDir.ts:725-763
|
||||||
|
const fileIds = await Promise.all(
|
||||||
|
allSkillsWithPaths.map(({ skill, filePath }) =>
|
||||||
|
skill.type === 'prompt'
|
||||||
|
? getFileIdentity(filePath) // realpath() resolves symlinks
|
||||||
|
: Promise.resolve(null),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const seenFileIds = new Map<string, SettingSource>()
|
||||||
|
for (const entry of allSkillsWithPaths) {
|
||||||
|
const fileId = fileIds[i]
|
||||||
|
const existingSource = seenFileIds.get(fileId)
|
||||||
|
if (existingSource !== undefined) continue // Skip duplicates
|
||||||
|
seenFileIds.set(fileId, skill.source)
|
||||||
|
deduplicatedSkills.push(skill)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bundled Skill Registration
|
||||||
|
|
||||||
|
Built-in Skills use an entirely different registration path:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/skills/bundledSkills.ts:53-100
|
||||||
|
export function registerBundledSkill(definition: BundledSkillDefinition): void {
|
||||||
|
// If files are present, create extraction directory and lazy extraction logic
|
||||||
|
if (files && Object.keys(files).length > 0) {
|
||||||
|
skillRoot = getBundledSkillExtractDir(definition.name)
|
||||||
|
// Extract files to disk on first invocation
|
||||||
|
getPromptForCommand = async (args, ctx) => {
|
||||||
|
extractionPromise ??= extractBundledSkillFiles(name, files)
|
||||||
|
const extractedDir = await extractionPromise
|
||||||
|
const blocks = await inner(args, ctx)
|
||||||
|
return prependBaseDir(blocks, extractedDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const command: Command = {
|
||||||
|
type: 'prompt',
|
||||||
|
source: 'bundled',
|
||||||
|
loadedFrom: 'bundled',
|
||||||
|
// ...other fields
|
||||||
|
}
|
||||||
|
bundledSkills.push(command)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**File extraction:** Bundled Skills can include a `files: Record<string, string>` field. On first invocation, these files are extracted to disk (`getBundledSkillExtractDir()`), allowing the model to access them via Read/Grep.
|
||||||
|
|
||||||
|
### Startup Registration
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/skills/bundled/index.ts:13-58
|
||||||
|
export function initBundledSkills(): void {
|
||||||
|
require('./verify.js').registerVerifySkill()
|
||||||
|
require('./debug.js').registerDebugSkill()
|
||||||
|
require('./remember.js').registerRememberSkill()
|
||||||
|
// ...
|
||||||
|
if (feature('AGENT_TRIGGERS')) {
|
||||||
|
require('./loop.js').registerLoopSkill() // Feature-gated
|
||||||
|
}
|
||||||
|
if (feature('KAIROS')) {
|
||||||
|
require('./dream.js').registerDreamSkill() // Feature-gated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plugin Skill Loading
|
||||||
|
|
||||||
|
```
|
||||||
|
Plugin System
|
||||||
|
├─ loadAllPluginsCacheOnly()
|
||||||
|
│ └─ Get all enabled plugins
|
||||||
|
│
|
||||||
|
├─ For each plugin:
|
||||||
|
│ ├─ Read manifest.skillsPath → default Skills directory
|
||||||
|
│ ├─ Read manifest.skillsPaths[] → additional Skills directories
|
||||||
|
│ └─ loadSkillsFromDirectory() to load SKILL.md
|
||||||
|
│
|
||||||
|
├─ Namespacing:
|
||||||
|
│ └─ {pluginName}:{namespace}:{skillName}
|
||||||
|
│ e.g.: superpowers:code-reviewer
|
||||||
|
│
|
||||||
|
└─ Variable substitution:
|
||||||
|
├─ ${CLAUDE_PLUGIN_ROOT} → plugin root directory
|
||||||
|
├─ ${CLAUDE_PLUGIN_DATA} → plugin data directory
|
||||||
|
├─ ${CLAUDE_SKILL_DIR} → skill directory
|
||||||
|
└─ ${user_config.X} → user configuration values
|
||||||
|
```
|
||||||
|
|
||||||
|
### MCP Skill Loading
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/services/mcp/client.ts:2030-2102
|
||||||
|
// MCP prompts are converted to Command objects
|
||||||
|
async function fetchCommandsForClient(client) {
|
||||||
|
const prompts = await client.listPrompts()
|
||||||
|
return prompts.map(prompt => ({
|
||||||
|
type: 'prompt',
|
||||||
|
name: `mcp__${normalizeNameForMCP(serverName)}__${prompt.name}`,
|
||||||
|
source: 'mcp',
|
||||||
|
loadedFrom: 'mcp',
|
||||||
|
// getPromptForCommand calls the MCP server to fetch content
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Feature gate:** `feature('MCP_SKILLS')` controls whether MCP Skills are available.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Frontmatter Parsing
|
||||||
|
|
||||||
|
### Parsing Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
SKILL.md file
|
||||||
|
↓
|
||||||
|
parseFrontmatter() ← frontmatterParser.ts
|
||||||
|
├─ Separate YAML frontmatter from Markdown content
|
||||||
|
├─ quoteProblematicValues() ← Handle special characters (glob patterns, etc.)
|
||||||
|
└─ parseYaml() ← Parse YAML
|
||||||
|
↓
|
||||||
|
parseSkillFrontmatterFields() ← loadSkillsDir.ts:185-265
|
||||||
|
├─ description extraction priority:
|
||||||
|
│ 1. frontmatter.description field
|
||||||
|
│ 2. First # heading in Markdown
|
||||||
|
│ 3. Skill name as fallback
|
||||||
|
├─ parseUserSpecifiedModel() ← Model alias resolution
|
||||||
|
├─ parseEffortValue() ← Effort level parsing
|
||||||
|
├─ parseHooksFromFrontmatter() ← Hook configuration validation
|
||||||
|
├─ parseBooleanFrontmatter() ← Boolean field parsing
|
||||||
|
└─ parseSlashCommandToolsFromFrontmatter() ← Tool list parsing
|
||||||
|
↓
|
||||||
|
createSkillCommand() ← loadSkillsDir.ts:270-401
|
||||||
|
└─ Generate Command object (with getPromptForCommand closure)
|
||||||
|
```
|
||||||
|
|
||||||
|
### FrontmatterData Type Definition
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/frontmatterParser.ts:10-59
|
||||||
|
export type FrontmatterData = {
|
||||||
|
'allowed-tools'?: string | string[] | null
|
||||||
|
description?: string | null
|
||||||
|
'argument-hint'?: string | null
|
||||||
|
when_to_use?: string | null
|
||||||
|
version?: string | null
|
||||||
|
model?: string | null // haiku, sonnet, opus, inherit
|
||||||
|
'user-invocable'?: string | null
|
||||||
|
'disable-model-invocation'?: string | null
|
||||||
|
hooks?: HooksSettings | null
|
||||||
|
effort?: string | null // low, medium, high, max, or numeric
|
||||||
|
context?: 'inline' | 'fork' | null
|
||||||
|
agent?: string | null
|
||||||
|
paths?: string | string[] | null
|
||||||
|
shell?: string | null // bash, powershell
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### The getPromptForCommand Closure
|
||||||
|
|
||||||
|
Each Command object contains a `getPromptForCommand` closure that executes upon invocation:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/skills/loadSkillsDir.ts:344-399
|
||||||
|
async getPromptForCommand(args, toolUseContext) {
|
||||||
|
// 1. Add base directory header
|
||||||
|
let finalContent = baseDir
|
||||||
|
? `Base directory for this skill: ${baseDir}\n\n${markdownContent}`
|
||||||
|
: markdownContent
|
||||||
|
|
||||||
|
// 2. Argument substitution
|
||||||
|
finalContent = substituteArguments(finalContent, args, true, argumentNames)
|
||||||
|
|
||||||
|
// 3. Skill directory variable substitution
|
||||||
|
if (baseDir) {
|
||||||
|
finalContent = finalContent.replace(/\$\{CLAUDE_SKILL_DIR\}/g, skillDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Session ID substitution
|
||||||
|
finalContent = finalContent.replace(/\$\{CLAUDE_SESSION_ID\}/g, getSessionId())
|
||||||
|
|
||||||
|
// 5. Execute inline shell commands (skipped for MCP Skills — security restriction)
|
||||||
|
if (loadedFrom !== 'mcp') {
|
||||||
|
finalContent = await executeShellCommandsInPrompt(finalContent, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
return [{ type: 'text', text: finalContent }]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Skill Injection into Conversations
|
||||||
|
|
||||||
|
### Injection Flow
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Skills are injected into conversations via `system-reminder` messages:
|
||||||
|
|
||||||
|
```
|
||||||
|
Start of each conversation turn
|
||||||
|
↓
|
||||||
|
getSkillListingAttachments() ← attachments.ts:2600-2747
|
||||||
|
├─ getSkillToolCommands(cwd) ← Get all model-invocable Skills
|
||||||
|
├─ getMcpSkillCommands() ← Get MCP Skills
|
||||||
|
├─ sentSkillNames Map tracking ← Avoid duplicate sends (per-agent)
|
||||||
|
└─ formatCommandsWithinBudget() ← Truncate to context budget
|
||||||
|
↓
|
||||||
|
Returns Attachment:
|
||||||
|
{ type: 'skill_listing', content, skillCount, isInitial }
|
||||||
|
↓
|
||||||
|
normalizeAttachmentForAPI() ← messages.ts:3732-3737
|
||||||
|
↓
|
||||||
|
Wrapped as <system-reminder> user message:
|
||||||
|
"The following skills are available for use with the Skill tool:
|
||||||
|
- commit: Create a git commit...
|
||||||
|
- review-pr: Review a pull request...
|
||||||
|
- superpowers:code-reviewer: Expert code review..."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Context Budget Control
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/tools/SkillTool/prompt.ts:21-29
|
||||||
|
export const SKILL_BUDGET_CONTEXT_PERCENT = 0.01 // 1% of context window
|
||||||
|
export const CHARS_PER_TOKEN = 4
|
||||||
|
export const DEFAULT_CHAR_BUDGET = 8_000 // 200k × 4 × 1% fallback
|
||||||
|
export const MAX_LISTING_DESC_CHARS = 250 // Max characters per description
|
||||||
|
```
|
||||||
|
|
||||||
|
**Truncation strategy:**
|
||||||
|
|
||||||
|
```
|
||||||
|
formatCommandsWithinBudget(commands, contextWindowTokens)
|
||||||
|
├─ Calculate total budget = contextWindowTokens × 4 × 1%
|
||||||
|
├─ Try full descriptions
|
||||||
|
│ └─ Total chars ≤ budget → output all
|
||||||
|
│
|
||||||
|
├─ Partition: Bundled (never truncated) + rest
|
||||||
|
│ ├─ Bundled Skills always retain full descriptions
|
||||||
|
│ └─ Remaining Skills split the leftover budget evenly
|
||||||
|
│
|
||||||
|
├─ Truncate descriptions → maxDescLen characters
|
||||||
|
│ └─ maxDescLen < 20 → extreme case: non-Bundled show name only
|
||||||
|
│
|
||||||
|
└─ Output format: "- skill-name: description..."
|
||||||
|
```
|
||||||
|
|
||||||
|
### SkillTool Prompt
|
||||||
|
|
||||||
|
The tool prompt definition seen by the model:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/tools/SkillTool/prompt.ts:173-196
|
||||||
|
export const getPrompt = memoize(async (_cwd: string): Promise<string> => {
|
||||||
|
return `Execute a skill within the main conversation
|
||||||
|
|
||||||
|
When users ask you to perform tasks, check if any of the available skills match.
|
||||||
|
|
||||||
|
How to invoke:
|
||||||
|
- Use this tool with the skill name and optional arguments
|
||||||
|
- Examples:
|
||||||
|
- skill: "pdf"
|
||||||
|
- skill: "commit", args: "-m 'Fix bug'"
|
||||||
|
|
||||||
|
Important:
|
||||||
|
- Available skills are listed in system-reminder messages
|
||||||
|
- When a skill matches, invoke BEFORE generating any other response
|
||||||
|
- NEVER mention a skill without calling this tool
|
||||||
|
- Do not invoke a skill that is already running
|
||||||
|
`
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. SkillTool Execution Engine
|
||||||
|
|
||||||
|
### Tool Definition
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/tools/SkillTool/SkillTool.ts:331-340
|
||||||
|
export const SkillTool = buildTool({
|
||||||
|
name: 'Skill',
|
||||||
|
inputSchema: z.object({
|
||||||
|
skill: z.string(), // Skill name
|
||||||
|
args: z.string().optional(), // Optional arguments
|
||||||
|
}),
|
||||||
|
outputSchema: z.union([inlineOutputSchema, forkedOutputSchema]),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Execution Flow
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
SkillTool.call({ skill, args })
|
||||||
|
│
|
||||||
|
├─ 1. Normalize input
|
||||||
|
│ └─ Strip leading /, trim whitespace
|
||||||
|
│
|
||||||
|
├─ 2. Remote Skill check (experimental)
|
||||||
|
│ └─ _canonical_<slug> prefix → executeRemoteSkill()
|
||||||
|
│
|
||||||
|
├─ 3. Find Command object
|
||||||
|
│ └─ getAllCommands(context) → findCommand(name, commands)
|
||||||
|
│
|
||||||
|
├─ 4. Record usage frequency
|
||||||
|
│ └─ recordSkillUsage(commandName) → influences sorting recommendations
|
||||||
|
│
|
||||||
|
├─ 5. Determine execution path
|
||||||
|
│ ├─ command.context === 'fork'
|
||||||
|
│ │ └─ → executeForkedSkill() [see Section 6]
|
||||||
|
│ │
|
||||||
|
│ └─ Default: inline
|
||||||
|
│ ├─ processPromptSlashCommand()
|
||||||
|
│ │ └─ getMessagesForPromptSlashCommand()
|
||||||
|
│ │ ├─ command.getPromptForCommand(args, context)
|
||||||
|
│ │ ├─ registerSkillHooks() ← Register hooks
|
||||||
|
│ │ ├─ addInvokedSkill() ← Record (restored after compression)
|
||||||
|
│ │ ├─ formatCommandLoadingMetadata()
|
||||||
|
│ │ │ └─ <command-name>/skillName</command-name>
|
||||||
|
│ │ └─ Extract attachments → create messages
|
||||||
|
│ │
|
||||||
|
│ ├─ Extract metadata: allowedTools, model, effort
|
||||||
|
│ ├─ tagMessagesWithToolUseID() ← Associate with tool_use
|
||||||
|
│ └─ Return { newMessages, contextModifier }
|
||||||
|
│
|
||||||
|
└─ 6. contextModifier() closure
|
||||||
|
├─ Update allowedTools
|
||||||
|
│ └─ appState.toolPermissionContext.alwaysAllowRules.command
|
||||||
|
├─ Update model
|
||||||
|
│ └─ resolveSkillModelOverride() preserves [1m] suffix
|
||||||
|
└─ Update effort
|
||||||
|
└─ appState.effortValue
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validation Logic
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/tools/SkillTool/SkillTool.ts:354-430
|
||||||
|
async validateInput({ skill }, context) {
|
||||||
|
// 1. Format check — non-empty
|
||||||
|
// 2. Normalize — strip leading /
|
||||||
|
// 3. Remote Skill check — _canonical_ prefix
|
||||||
|
// 4. Lookup — findCommand() within getAllCommands()
|
||||||
|
// 5. Disabled check — disableModelInvocation
|
||||||
|
// 6. Type check — must be 'prompt' type
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error code definitions:**
|
||||||
|
|
||||||
|
| errorCode | Meaning |
|
||||||
|
|-----------|---------|
|
||||||
|
| 1 | Invalid format (empty skill name) |
|
||||||
|
| 2 | Unknown skill |
|
||||||
|
| 4 | Model invocation disabled |
|
||||||
|
| 5 | Not a prompt type |
|
||||||
|
| 6 | Remote skill not found |
|
||||||
|
|
||||||
|
### getAllCommands -- MCP Skill Merging
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/tools/SkillTool/SkillTool.ts:81-94
|
||||||
|
async function getAllCommands(context: ToolUseContext): Promise<Command[]> {
|
||||||
|
// Get MCP Skills from AppState (loadedFrom === 'mcp')
|
||||||
|
const mcpSkills = context.getAppState()
|
||||||
|
.mcp.commands.filter(
|
||||||
|
cmd => cmd.type === 'prompt' && cmd.loadedFrom === 'mcp',
|
||||||
|
)
|
||||||
|
if (mcpSkills.length === 0) return getCommands(getProjectRoot())
|
||||||
|
const localCommands = await getCommands(getProjectRoot())
|
||||||
|
return uniqBy([...localCommands, ...mcpSkills], 'name')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Fork Sub-Agent Execution
|
||||||
|
|
||||||
|
### Execution Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
executeForkedSkill(command, commandName, args, context, ...)
|
||||||
|
│
|
||||||
|
├─ 1. Create sub-agent ID
|
||||||
|
│ └─ agentId = createAgentId()
|
||||||
|
│
|
||||||
|
├─ 2. Telemetry recording
|
||||||
|
│ └─ logEvent('tengu_skill_tool_invocation', { execution_context: 'fork' })
|
||||||
|
│
|
||||||
|
├─ 3. Prepare fork context
|
||||||
|
│ └─ prepareForkedCommandContext(command, args, context)
|
||||||
|
│ ├─ command.getPromptForCommand(args, context) ← Get skill content
|
||||||
|
│ ├─ parseToolListFromCLI(allowedTools) ← Parse tool whitelist
|
||||||
|
│ ├─ createGetAppStateWithAllowedTools() ← Modify AppState
|
||||||
|
│ ├─ Select agent: command.agent ?? 'general-purpose'
|
||||||
|
│ └─ promptMessages = [createUserMessage(skillContent)]
|
||||||
|
│
|
||||||
|
├─ 4. Merge effort
|
||||||
|
│ └─ command.effort → inject into agentDefinition
|
||||||
|
│
|
||||||
|
├─ 5. Run sub-agent
|
||||||
|
│ └─ for await (message of runAgent({
|
||||||
|
│ agentDefinition,
|
||||||
|
│ promptMessages,
|
||||||
|
│ toolUseContext: { ...context, getAppState: modifiedGetAppState },
|
||||||
|
│ model: command.model,
|
||||||
|
│ override: { agentId },
|
||||||
|
│ }))
|
||||||
|
│ └─ Collect messages + report progress (onProgress)
|
||||||
|
│
|
||||||
|
├─ 6. Extract results
|
||||||
|
│ └─ extractResultText(agentMessages)
|
||||||
|
│ └─ Get text from the last assistant message
|
||||||
|
│
|
||||||
|
└─ 7. Cleanup
|
||||||
|
└─ clearInvokedSkillsForAgent(agentId)
|
||||||
|
```
|
||||||
|
|
||||||
|
### prepareForkedCommandContext
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/forkedAgent.ts:191-232
|
||||||
|
export async function prepareForkedCommandContext(
|
||||||
|
command: PromptCommand,
|
||||||
|
args: string,
|
||||||
|
context: ToolUseContext,
|
||||||
|
): Promise<PreparedForkedContext> {
|
||||||
|
// Get skill content (with argument substitution and shell execution)
|
||||||
|
const skillPrompt = await command.getPromptForCommand(args, context)
|
||||||
|
const skillContent = skillPrompt.map(b => b.type === 'text' ? b.text : '').join('\n')
|
||||||
|
|
||||||
|
// Build tool whitelist
|
||||||
|
const allowedTools = parseToolListFromCLI(command.allowedTools ?? [])
|
||||||
|
const modifiedGetAppState = createGetAppStateWithAllowedTools(
|
||||||
|
context.getAppState, allowedTools,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Select agent type
|
||||||
|
const agentTypeName = command.agent ?? 'general-purpose'
|
||||||
|
const baseAgent = agents.find(a => a.agentType === agentTypeName)
|
||||||
|
|
||||||
|
// Build prompt messages
|
||||||
|
const promptMessages = [createUserMessage({ content: skillContent })]
|
||||||
|
|
||||||
|
return { skillContent, modifiedGetAppState, baseAgent, promptMessages }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Inline vs Fork Return Differences
|
||||||
|
|
||||||
|
**Inline return:**
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
success: true,
|
||||||
|
commandName: 'commit',
|
||||||
|
allowedTools: ['Bash', 'Read'],
|
||||||
|
model: 'sonnet',
|
||||||
|
status: 'inline',
|
||||||
|
},
|
||||||
|
newMessages: [...], // Injected into conversation
|
||||||
|
contextModifier: (ctx) => { ... }, // Modifies context
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fork return:**
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
success: true,
|
||||||
|
commandName: 'verify',
|
||||||
|
status: 'forked',
|
||||||
|
agentId: 'agent_abc123',
|
||||||
|
result: 'Verification passed, all tests have run...',
|
||||||
|
},
|
||||||
|
// No newMessages — result is embedded in the tool_result block
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Conditional Activation and Dynamic Discovery
|
||||||
|
|
||||||
|
### Conditional Skills
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Skills with `paths` frontmatter are not immediately exposed to the model:
|
||||||
|
|
||||||
|
```
|
||||||
|
At startup
|
||||||
|
├─ Load all Skills
|
||||||
|
├─ Those with paths → conditionalSkills Map
|
||||||
|
└─ Those without paths → immediately available
|
||||||
|
|
||||||
|
At runtime (triggered by file operations)
|
||||||
|
├─ activateConditionalSkillsForPaths(filePaths, cwd)
|
||||||
|
│ ├─ Iterate over conditionalSkills Map
|
||||||
|
│ ├─ Match paths patterns using the ignore library
|
||||||
|
│ │ └─ filePath converted to cwd-relative path before matching
|
||||||
|
│ ├─ On match:
|
||||||
|
│ │ ├─ Move to dynamicSkills Map
|
||||||
|
│ │ ├─ Remove from conditionalSkills
|
||||||
|
│ │ ├─ Add to activatedConditionalSkillNames Set
|
||||||
|
│ │ └─ Log telemetry: tengu_dynamic_skills_changed
|
||||||
|
│ └─ Once activated, remains active for the session
|
||||||
|
└─ Notify cache invalidation → skillsLoaded.emit()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dynamic Discovery
|
||||||
|
|
||||||
|
When operating on files in deeply nested directories, the system automatically discovers new Skills:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/skills/loadSkillsDir.ts:861-915
|
||||||
|
export async function discoverSkillDirsForPaths(
|
||||||
|
filePaths: string[],
|
||||||
|
cwd: string,
|
||||||
|
): Promise<string[]> {
|
||||||
|
for (const filePath of filePaths) {
|
||||||
|
let currentDir = dirname(filePath)
|
||||||
|
// Traverse upward from the file's directory to cwd (excluding cwd itself)
|
||||||
|
while (currentDir.startsWith(resolvedCwd + pathSep)) {
|
||||||
|
const skillDir = join(currentDir, '.claude', 'skills')
|
||||||
|
if (!dynamicSkillDirs.has(skillDir)) {
|
||||||
|
dynamicSkillDirs.add(skillDir)
|
||||||
|
await fs.stat(skillDir) // Check if it exists
|
||||||
|
// Check if ignored by .gitignore
|
||||||
|
if (await isPathGitignored(currentDir, resolvedCwd)) continue
|
||||||
|
newDirs.push(skillDir)
|
||||||
|
}
|
||||||
|
currentDir = dirname(currentDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort by depth (deepest first), ensuring nearest Skills have higher priority
|
||||||
|
return newDirs.sort((a, b) => b.split(pathSep).length - a.split(pathSep).length)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cache Invalidation Chain
|
||||||
|
|
||||||
|
```
|
||||||
|
Dynamic Skill change
|
||||||
|
↓
|
||||||
|
skillsLoaded.emit()
|
||||||
|
↓
|
||||||
|
clearCommandMemoizationCaches()
|
||||||
|
├─ loadAllCommands.cache.clear()
|
||||||
|
├─ getSkillToolCommands.cache.clear()
|
||||||
|
├─ getSlashCommandToolSkills.cache.clear()
|
||||||
|
└─ clearSkillIndexCache()
|
||||||
|
↓
|
||||||
|
Next conversation turn loads the updated Skill list
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Hook Integration
|
||||||
|
|
||||||
|
### Hook Registration
|
||||||
|
|
||||||
|
Skills can declare hooks via frontmatter, which are automatically registered as session-level hooks upon invocation:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/utils/hooks/registerSkillHooks.ts:20-64
|
||||||
|
export function registerSkillHooks(
|
||||||
|
setAppState, sessionId, hooks, skillName, skillRoot?,
|
||||||
|
): void {
|
||||||
|
for (const eventName of HOOK_EVENTS) {
|
||||||
|
const matchers = hooks[eventName]
|
||||||
|
if (!matchers) continue
|
||||||
|
for (const matcher of matchers) {
|
||||||
|
for (const hook of matcher.hooks) {
|
||||||
|
// once: true → automatically removed after one execution
|
||||||
|
const onHookSuccess = hook.once
|
||||||
|
? () => removeSessionHook(setAppState, sessionId, eventName, hook)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
addSessionHook(
|
||||||
|
setAppState, sessionId, eventName,
|
||||||
|
matcher.matcher || '',
|
||||||
|
hook, onHookSuccess, skillRoot,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hook Lifecycle
|
||||||
|
|
||||||
|
```
|
||||||
|
Skill invocation
|
||||||
|
↓
|
||||||
|
processPromptSlashCommand()
|
||||||
|
├─ Check command.hooks
|
||||||
|
└─ registerSkillHooks(setAppState, sessionId, hooks, skillName, skillRoot)
|
||||||
|
├─ Iterate over HOOK_EVENTS (PreToolUse, PostToolUse, Stop, ...)
|
||||||
|
├─ Register addSessionHook() for each matcher
|
||||||
|
├─ skillRoot → CLAUDE_PLUGIN_ROOT environment variable
|
||||||
|
└─ once: true → removeSessionHook() after first execution
|
||||||
|
↓
|
||||||
|
During session
|
||||||
|
├─ Tool calls trigger PreToolUse/PostToolUse
|
||||||
|
├─ Matcher matches → execute hook command
|
||||||
|
└─ Hooks with once: true are automatically removed after first execution
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Permission System
|
||||||
|
|
||||||
|
### Check Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
checkPermissions({ skill, args }, context)
|
||||||
|
│
|
||||||
|
├─ 1. Deny rule check (highest priority)
|
||||||
|
│ └─ getRuleByContentsForTool(context, SkillTool, 'deny')
|
||||||
|
│ ├─ Exact match: "commit" === commandName
|
||||||
|
│ └─ Prefix match: "review:*" → commandName.startsWith("review")
|
||||||
|
│
|
||||||
|
├─ 2. Remote Skill auto-allow
|
||||||
|
│ └─ _canonical_<slug> → auto-allow (Ant-specific experimental)
|
||||||
|
│
|
||||||
|
├─ 3. Allow rule check
|
||||||
|
│ └─ getRuleByContentsForTool(context, SkillTool, 'allow')
|
||||||
|
│
|
||||||
|
├─ 4. Safe property auto-allow
|
||||||
|
│ └─ skillHasOnlySafeProperties(command)
|
||||||
|
│ └─ SAFE_SKILL_PROPERTIES whitelist check
|
||||||
|
│
|
||||||
|
└─ 5. Default: ask user
|
||||||
|
└─ Provide suggestions: exact allow + prefix allow
|
||||||
|
```
|
||||||
|
|
||||||
|
### Safe Property Whitelist
|
||||||
|
|
||||||
|
If a Skill contains only the following properties (no hooks, no allowedTools, no fork), it is automatically allowed:
|
||||||
|
|
||||||
|
```
|
||||||
|
SAFE_SKILL_PROPERTIES = {
|
||||||
|
type, name, description, contentLength, source,
|
||||||
|
loadedFrom, progressMessage, userInvocable,
|
||||||
|
disableModelInvocation, hasUserSpecifiedDescription,
|
||||||
|
getPromptForCommand, userFacingName, ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Core principle:** Newly added frontmatter fields require permission by default, unless explicitly added to the whitelist.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Complete Lifecycle
|
||||||
|
|
||||||
|
### Data Flow Overview
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1: Discovery and Registration
|
||||||
|
──────────────────────────────────
|
||||||
|
CLI startup
|
||||||
|
├─ initBundledSkills() → bundledSkills[]
|
||||||
|
├─ getPluginSkills() → pluginSkills[]
|
||||||
|
├─ getSkillDirCommands(cwd) → skillDirCommands[]
|
||||||
|
│ └─ Conditional Skills → conditionalSkills Map
|
||||||
|
└─ loadAllCommands() → aggregate & memoize
|
||||||
|
|
||||||
|
Phase 2: Injection into Conversation
|
||||||
|
────────────────────────────────────
|
||||||
|
Each conversation turn
|
||||||
|
├─ getSkillListingAttachments()
|
||||||
|
│ ├─ getSkillToolCommands() → filter model-invocable Skills
|
||||||
|
│ ├─ getMcpSkillCommands() → MCP Skills
|
||||||
|
│ └─ formatCommandsWithinBudget() → truncate to budget
|
||||||
|
└─ Wrap as <system-reminder> message and inject
|
||||||
|
|
||||||
|
Phase 3: Invocation and Execution
|
||||||
|
─────────────────────────────────
|
||||||
|
Model/user trigger
|
||||||
|
├─ SkillTool.validateInput() → validation
|
||||||
|
├─ SkillTool.checkPermissions() → permissions
|
||||||
|
└─ SkillTool.call()
|
||||||
|
├─ Inline → processPromptSlashCommand()
|
||||||
|
│ ├─ getPromptForCommand() → content expansion
|
||||||
|
│ ├─ registerSkillHooks() → hook registration
|
||||||
|
│ ├─ addInvokedSkill() → record (restored after compression)
|
||||||
|
│ └─ contextModifier() → update tools/model/effort
|
||||||
|
└─ Fork → executeForkedSkill()
|
||||||
|
├─ prepareForkedCommandContext()
|
||||||
|
├─ runAgent() → sub-agent execution
|
||||||
|
└─ extractResultText() → extract results
|
||||||
|
|
||||||
|
Phase 4: Runtime Discovery
|
||||||
|
─────────────────────────
|
||||||
|
File operation trigger
|
||||||
|
├─ discoverSkillDirsForPaths() → new Skills directories
|
||||||
|
├─ addSkillDirectories() → load & register
|
||||||
|
├─ activateConditionalSkillsForPaths() → conditional activation
|
||||||
|
└─ clearCommandMemoizationCaches() → cache invalidation
|
||||||
|
```
|
||||||
|
|
||||||
|
### Skill Content Persistence
|
||||||
|
|
||||||
|
Inline Skill content is recorded to session state via `addInvokedSkill()`, ensuring it can be restored after context compression:
|
||||||
|
|
||||||
|
```
|
||||||
|
addInvokedSkill(name, path, content, agentId)
|
||||||
|
↓
|
||||||
|
Stored in session state
|
||||||
|
↓
|
||||||
|
On compression → buildPostCompactMessages()
|
||||||
|
↓
|
||||||
|
Restored scoped by agentId (prevents cross-agent leakage)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Source Code Index
|
||||||
|
|
||||||
|
### Core Files
|
||||||
|
|
||||||
|
| File | Responsibility |
|
||||||
|
|------|----------------|
|
||||||
|
| `src/tools/SkillTool/SkillTool.ts` | SkillTool definition, validation, permissions, execution |
|
||||||
|
| `src/tools/SkillTool/prompt.ts` | Tool prompt, Skill listing formatting, budget control |
|
||||||
|
| `src/skills/loadSkillsDir.ts` | Directory Skill discovery, loading, deduplication, conditional activation |
|
||||||
|
| `src/skills/bundledSkills.ts` | Built-in Skill registration system |
|
||||||
|
| `src/skills/bundled/index.ts` | Built-in Skills initialization entry point |
|
||||||
|
| `src/commands.ts` | Command aggregation, ordering, filtering, cache management |
|
||||||
|
|
||||||
|
### Type Definitions
|
||||||
|
|
||||||
|
| File | Key Types |
|
||||||
|
|------|-----------|
|
||||||
|
| `src/types/command.ts` | `PromptCommand`, `Command`, `LocalCommandResult` |
|
||||||
|
| `src/utils/frontmatterParser.ts` | `FrontmatterData`, `ParsedMarkdown` |
|
||||||
|
| `src/skills/bundledSkills.ts` | `BundledSkillDefinition` |
|
||||||
|
|
||||||
|
### Utility Modules
|
||||||
|
|
||||||
|
| File | Responsibility |
|
||||||
|
|------|----------------|
|
||||||
|
| `src/utils/forkedAgent.ts` | Fork context preparation, result extraction |
|
||||||
|
| `src/utils/hooks/registerSkillHooks.ts` | Skill hook registration |
|
||||||
|
| `src/utils/argumentSubstitution.ts` | Argument substitution ($ARGUMENTS, ${ARG1}) |
|
||||||
|
| `src/utils/promptShellExecution.ts` | Inline shell command execution |
|
||||||
|
| `src/utils/attachments.ts` | Skill listing attachment generation |
|
||||||
|
| `src/utils/messages.ts` | system-reminder wrapping |
|
||||||
|
| `src/utils/plugins/loadPluginCommands.ts` | Plugin Skill loading |
|
||||||
|
| `src/services/mcp/client.ts` | MCP Skill conversion |
|
||||||
|
| `src/skills/mcpSkillBuilders.ts` | MCP Skill builder registration |
|
||||||
|
|
||||||
|
### Key Constants
|
||||||
|
|
||||||
|
| Constant | Value | Location |
|
||||||
|
|----------|-------|----------|
|
||||||
|
| `SKILL_BUDGET_CONTEXT_PERCENT` | 0.01 | prompt.ts:21 |
|
||||||
|
| `DEFAULT_CHAR_BUDGET` | 8,000 | prompt.ts:23 |
|
||||||
|
| `MAX_LISTING_DESC_CHARS` | 250 | prompt.ts:29 |
|
||||||
|
| `SKILL_TOOL_NAME` | `'Skill'` | constants.ts |
|
||||||
|
|
||||||
|
### Telemetry Events
|
||||||
|
|
||||||
|
| Event | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `tengu_skill_tool_invocation` | Skill invocation (includes execution_context, invocation_trigger) |
|
||||||
|
| `tengu_skill_tool_slash_prefix` | Model used the / prefix |
|
||||||
|
| `tengu_dynamic_skills_changed` | Dynamic Skills changed (conditional activation/directory discovery) |
|
||||||
|
| `tengu_skill_descriptions_truncated` | Skill descriptions were truncated |
|
||||||
|
After Width: | Height: | Size: 460 KiB |
|
After Width: | Height: | Size: 655 KiB |
|
After Width: | Height: | Size: 562 KiB |
|
After Width: | Height: | Size: 492 KiB |
|
After Width: | Height: | Size: 595 KiB |
|
After Width: | Height: | Size: 785 KiB |
|
After Width: | Height: | Size: 576 KiB |
|
After Width: | Height: | Size: 668 KiB |
|
After Width: | Height: | Size: 513 KiB |