mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 14:25:51 +00:00
docs: 文档大重组,对齐 README 入口
以 README 为单一事实来源,重构整个 docs/ 目录。 最终结构(3 大组、15 篇文档): - 开始: installation / quickstart / model-providers - 核心功能: pipes-and-lan、acp、channels、chrome-control、computer-use、 voice-mode、web-browser-tool、auto-dream、remote-control-self-hosting、 langfuse-monitoring - 内部机制: growthbook-adapter、sentry-setup 主要变更: - 删除 56 个 README 未提及的文档(architecture 全部 / guides 全部 / features 中未在 README 出现的 20 篇 / internals 中的 5 篇) - 合并 6 组重复文档(pipes-and-lan、chrome-control、acp、computer-use、 auto-dream、coordinator-mode 简化为入口) - features 子组从 5 → 4,ui/ 合并入 tools/ - 所有保留文档加上人性化 frontmatter(title/description/keywords) - docs.json navigation 简化为 3 大组,redirects 重新过滤为 7 条合并跳转 - 新增 docs.md 工作大纲与验证脚本(verify-docs / check-docs-orphans / dump-docs-outline) 总计 130 文件改动,从约 35000 行精简到约 2000 行。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win>
This commit is contained in:
@@ -1,207 +0,0 @@
|
||||
# acp-link — ACP 代理服务器
|
||||
|
||||
> 源码目录:`packages/acp-link/`
|
||||
> PR: #292
|
||||
> 新增时间:2026-04-18
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
`acp-link` 是一个 ACP (Agent Client Protocol) 代理服务器,将 WebSocket 客户端桥接到 ACP agent 的 stdio 接口。它让 ACP agent(如 Claude Code)可以通过 WebSocket 远程访问,而不仅限于本地 stdio。
|
||||
|
||||
### 核心特性
|
||||
|
||||
- **WebSocket → stdio 桥接**:将浏览器/远程客户端的 WebSocket 连接转换为 ACP agent 的 stdin/stdout NDJSON 流
|
||||
- **会话管理**:创建、加载、恢复、列出、关闭会话
|
||||
- **权限审批流程**:客户端可远程审批 agent 的工具权限请求
|
||||
- **RCS 集成**:可与 Remote Control Server (RCS) 连接,将 ACP agent 注册到 RCS 并通过 Web UI 交互
|
||||
- **HTTPS 支持**:内置自签名证书生成,支持安全连接
|
||||
- **Token 认证**:自动生成或通过环境变量配置认证 token
|
||||
|
||||
## 二、架构
|
||||
|
||||
### 独立模式
|
||||
|
||||
```
|
||||
┌──────────────────┐ WebSocket ┌──────────────────┐ stdio/NDJSON ┌──────────────┐
|
||||
│ 浏览器/客户端 │ ◄──────────────►│ acp-link │ ◄────────────────►│ ACP Agent │
|
||||
│ (WS Client) │ ws://host:port │ (Proxy Server) │ spawn subprocess │ (Claude等) │
|
||||
└──────────────────┘ └──────────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
### RCS 集成模式
|
||||
|
||||
```
|
||||
┌──────────────┐ WebSocket ┌──────────────────┐ stdio/NDJSON ┌──────────────┐
|
||||
│ RCS Web UI │ ◄──────────────►│ Remote Control │ ◄─────────────────►│ acp-link │
|
||||
│ (/code/*) │ ACP Relay WS │ Server (RCS) │ ACP events │ + Agent │
|
||||
└──────────────┘ └──────────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
### 文件结构
|
||||
|
||||
```
|
||||
packages/acp-link/
|
||||
├── src/
|
||||
│ ├── server.ts # 主服务器:WS 连接管理、会话管理、权限处理、消息桥接
|
||||
│ ├── rcs-upstream.ts # RCS 上游客户端:REST 注册 + WS identify 两步流程
|
||||
│ ├── cert.ts # TLS 证书生成(自签名)
|
||||
│ ├── logger.ts # 日志模块
|
||||
│ ├── types.ts # JSON-RPC 和 ACP 协议类型定义
|
||||
│ ├── cli/
|
||||
│ │ ├── bin.ts # CLI 入口
|
||||
│ │ ├── command.ts # 命令行参数解析
|
||||
│ │ ├── app.ts # 应用启动
|
||||
│ │ └── context.ts # 上下文配置
|
||||
│ └── __tests__/ # 测试(cert, server, types)
|
||||
├── package.json
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
## 三、安装与使用
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
# 直接运行(在 monorepo 中)
|
||||
# 注意:claude 本身不支持 ACP,需要用 ccb-bun --acp 启动 ACP agent
|
||||
bun packages/acp-link/src/cli/bin.ts ccb-bun -- --acp
|
||||
|
||||
# 指定端口和主机
|
||||
acp-link --port 9000 --host 0.0.0.0 ccb-bun -- --acp
|
||||
|
||||
# 启用 HTTPS(自签名证书)
|
||||
acp-link --https ccb-bun -- --acp
|
||||
|
||||
# 调试模式
|
||||
acp-link --debug ccb-bun -- --acp
|
||||
```
|
||||
|
||||
### CLI 参考
|
||||
|
||||
```
|
||||
USAGE
|
||||
acp-link [--port value] [--host value] [--debug] [--no-auth] [--https] <command>...
|
||||
acp-link --help
|
||||
acp-link --version
|
||||
|
||||
FLAGS
|
||||
[--port] Port to listen on [default = 9315]
|
||||
[--host] Host to bind to [default = localhost]
|
||||
[--debug] Enable debug logging to file
|
||||
[--no-auth] Disable authentication (dangerous)
|
||||
[--https] Enable HTTPS with self-signed cert
|
||||
-h --help Print help information and exit
|
||||
-v --version Print version information and exit
|
||||
|
||||
ARGUMENTS
|
||||
command... Agent command followed by its arguments (e.g. "ccb-bun -- --acp")
|
||||
```
|
||||
|
||||
## 四、认证
|
||||
|
||||
默认启动时自动生成随机 token。客户端连接时不要把 token 放在 URL 中:
|
||||
|
||||
```
|
||||
ws://localhost:9315/ws
|
||||
```
|
||||
|
||||
无法发送 `Authorization` header 的 WebSocket 客户端需要使用
|
||||
`rcs.auth.<base64url-token>` 子协议传递 token。
|
||||
|
||||
配置固定 token:
|
||||
|
||||
```bash
|
||||
ACP_AUTH_TOKEN=my-fixed-token acp-link ccb-bun -- --acp
|
||||
```
|
||||
|
||||
禁用认证(不推荐,仅用于开发):
|
||||
|
||||
```bash
|
||||
acp-link --no-auth ccb-bun -- --acp
|
||||
```
|
||||
|
||||
## 五、RCS 集成
|
||||
|
||||
acp-link 支持将 ACP agent 注册到 Remote Control Server,通过 Web UI 远程操控。
|
||||
|
||||
### 连接方式
|
||||
|
||||
```bash
|
||||
# 通过环境变量配置 RCS 连接
|
||||
ACP_RCS_URL=http://localhost:3000 \
|
||||
ACP_RCS_TOKEN=sk-rcs-your-key \
|
||||
acp-link ccb-bun -- --acp
|
||||
```
|
||||
|
||||
### 注册流程(两步)
|
||||
|
||||
1. **REST 注册**:通过 `POST /v1/environments/bridge` 向 RCS 注册环境
|
||||
2. **WS identify**:建立 WebSocket 连接后发送 `identify` 消息(携带 agentId),替代完整 `register`
|
||||
|
||||
RCS 的 ACP WebSocket 连接不接受 URL query token。acp-link 会通过
|
||||
`rcs.auth.<base64url-token>` WebSocket 子协议发送 `ACP_RCS_TOKEN`。
|
||||
|
||||
```
|
||||
acp-link RCS
|
||||
│ │
|
||||
│── POST /v1/environments/bridge ──►│ (REST 注册)
|
||||
│◄── { agentId, sessionId } ───────│
|
||||
│ │
|
||||
│── WS connect ─────────────────►│ (WebSocket)
|
||||
│── identify { agentId } ────────►│ (WS 标识)
|
||||
│◄── identified ─────────────────│
|
||||
│ │
|
||||
│── ACP events ─────────────────►│ (双向消息转发)
|
||||
│◄── user prompts/permissions ───│
|
||||
```
|
||||
|
||||
## 六、权限模式
|
||||
|
||||
### permissionMode 传递链
|
||||
|
||||
权限模式通过整条链路传递:Web UI → RCS → acp-link → ACP agent。
|
||||
|
||||
支持的权限模式:
|
||||
- `default` — 每次请求权限确认
|
||||
- `auto` — 自动判断
|
||||
- `acceptEdits` — 自动接受编辑
|
||||
- `plan` — 规划模式
|
||||
- `dontAsk` — 不询问
|
||||
- `bypassPermissions` — 绕过权限(需 sandbox 环境)
|
||||
|
||||
### fallback 链
|
||||
|
||||
当客户端未显式传递 permissionMode 时,使用以下 fallback 链:
|
||||
|
||||
```
|
||||
客户端传值 > config.permissionMode > ACP_PERMISSION_MODE 环境变量
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
ACP_PERMISSION_MODE=auto acp-link ccb-bun -- --acp
|
||||
```
|
||||
|
||||
## 七、权限管道(2026-04-18 改进)
|
||||
|
||||
### 模式同步
|
||||
|
||||
`applySessionMode` 在 agent 切换权限模式时同步 `appState.toolPermissionContext.mode`,确保内部权限上下文与 ACP 客户端状态一致。
|
||||
|
||||
### 统一权限流水线
|
||||
|
||||
`createAcpCanUseTool` 接入 `hasPermissionsToUseTool` 统一权限流水线,替代原来分散的处理逻辑。支持 `onModeChange` 回调,模式变更时实时同步。
|
||||
|
||||
### bypass 检测
|
||||
|
||||
`bypassPermissions` 模式增加可用性检测 — 仅在非 root 或 sandbox 环境中允许启用,防止权限绕过的安全风险。
|
||||
|
||||
## 八、环境变量
|
||||
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| `ACP_AUTH_TOKEN` | 固定认证 token(默认自动生成) |
|
||||
| `ACP_PERMISSION_MODE` | 默认权限模式 fallback |
|
||||
| `ACP_RCS_URL` | RCS 服务器地址(启用 RCS 集成) |
|
||||
| `ACP_RCS_TOKEN` | RCS API token |
|
||||
@@ -1,189 +0,0 @@
|
||||
# ACP (Agent Client Protocol) — Zed / IDE 集成
|
||||
|
||||
> Feature Flag: `FEATURE_ACP=1`(build 和 dev 模式默认启用)
|
||||
> 实现状态:可用(支持 Zed、Cursor 等 ACP 客户端)
|
||||
> 源码目录:`src/services/acp/`
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
ACP (Agent Client Protocol) 是一种标准化的 stdio 协议,允许 IDE 和编辑器通过 stdin/stdout 的 NDJSON 流驱动 AI Agent。CCB 实现了完整的 ACP agent 端,可以被 Zed、Cursor 等支持 ACP 的客户端直接调用。
|
||||
|
||||
### 核心特性
|
||||
|
||||
- **会话管理**:新建 / 恢复 / 加载 / 分叉 / 关闭会话
|
||||
- **历史回放**:恢复会话时自动加载并回放对话历史
|
||||
- **权限桥接**:ACP 客户端的权限决策映射到 CCB 的工具权限系统
|
||||
- **斜杠命令 & Skills**:加载真实命令列表,支持 `/commit`、`/review` 等 prompt 型 skill
|
||||
- **Context Window 跟踪**:精确的 usage_update,含 model prefix matching
|
||||
- **Prompt 排队**:支持连续发送多条 prompt,自动排队处理
|
||||
- **模式切换**:auto / default / acceptEdits / plan / dontAsk / bypassPermissions
|
||||
- **模型切换**:运行时切换 AI 模型
|
||||
|
||||
## 二、架构
|
||||
|
||||
```
|
||||
┌──────────────┐ NDJSON/stdio ┌──────────────────┐
|
||||
│ Zed / IDE │ ◄────────────────► │ CCB ACP Agent │
|
||||
│ (Client) │ stdin / stdout │ (Agent) │
|
||||
└──────────────┘ │ │
|
||||
│ entry.ts │ ← stdio → NDJSON stream
|
||||
│ agent.ts │ ← ACP protocol handler
|
||||
│ bridge.ts │ ← SDKMessage → ACP SessionUpdate
|
||||
│ permissions.ts │ ← 权限桥接
|
||||
│ utils.ts │ ← 通用工具
|
||||
│ │
|
||||
│ QueryEngine │ ← 内部查询引擎
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
### 文件职责
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `entry.ts` | 入口,创建 stdio → NDJSON stream,启动 `AgentSideConnection` |
|
||||
| `agent.ts` | 实现 ACP `Agent` 接口:会话 CRUD、prompt、cancel、模式/模型切换 |
|
||||
| `bridge.ts` | `SDKMessage` → ACP `SessionUpdate` 转换:文本/思考/工具/用量/编辑 diff |
|
||||
| `permissions.ts` | ACP `requestPermission()` → CCB `CanUseToolFn` 桥接 |
|
||||
| `utils.ts` | Pushable、流转换、权限模式解析、session fingerprint、路径显示 |
|
||||
|
||||
## 三、配置 Zed 编辑器
|
||||
|
||||
### 3.1 Zed settings.json 配置
|
||||
|
||||
打开 Zed 的 `settings.json`(`Cmd+,` → Open Settings),添加 `agent_servers` 配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"ccb": {
|
||||
"type": "custom",
|
||||
"command": "ccb",
|
||||
"args": ["--acp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 API 认证配置
|
||||
|
||||
CCB 的 ACP agent 在启动时会自动加载 `settings.json` 中的环境变量(`ANTHROPIC_BASE_URL`、`ANTHROPIC_AUTH_TOKEN` 等)。确保已通过 `/login` 配置好 API 供应商。
|
||||
|
||||
也可通过环境变量传入:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"claude-code": {
|
||||
"command": "ccb",
|
||||
"args": ["--acp"],
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://api.example.com/v1",
|
||||
"ANTHROPIC_AUTH_TOKEN": "sk-xxx"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 在 Zed 中使用
|
||||
|
||||
1. 配置完成后重启 Zed
|
||||
2. 打开任意项目目录
|
||||
3. 按 `Cmd+'`(macOS)或 `Ctrl+'`(Linux)打开 Agent Panel
|
||||
4. 在 Agent Panel 顶部的下拉菜单中选择 **claude-code**
|
||||
5. 开始对话
|
||||
|
||||
### 3.5 功能说明
|
||||
|
||||
| 功能 | 操作 |
|
||||
|------|------|
|
||||
| 对话 | 在 Agent Panel 中直接输入消息 |
|
||||
| 斜杠命令 | 输入 `/` 查看可用 skills 列表(如 `/commit`、`/review`) |
|
||||
| 工具权限 | 弹出权限请求时选择 Allow / Reject / Always Allow |
|
||||
| 模式切换 | 通过 Agent Panel 的设置菜单切换 auto/default/plan 等模式 |
|
||||
| 模型切换 | 通过 Agent Panel 的设置菜单切换 AI 模型 |
|
||||
| 会话恢复 | 关闭重开 Zed 后,之前的会话可自动恢复(含历史消息) |
|
||||
|
||||
## 四、配置其他 ACP 客户端
|
||||
|
||||
ACP 是开放协议,任何支持 ACP 的客户端都可以连接 CCB。通用配置模式:
|
||||
|
||||
```
|
||||
命令: ccb --acp
|
||||
参数: ["--acp"]
|
||||
通信: stdin/stdout NDJSON
|
||||
协议版本: ACP v1
|
||||
```
|
||||
|
||||
### 4.1 Cursor
|
||||
|
||||
在 Cursor 的设置中配置 MCP / Agent Server,使用同样的 `ccb --acp` 命令。
|
||||
|
||||
### 4.2 自定义客户端
|
||||
|
||||
使用 `@agentclientprotocol/sdk` 可以快速构建 ACP 客户端:
|
||||
|
||||
```typescript
|
||||
import { ClientSideConnection, ndJsonStream } from '@agentclientprotocol/sdk'
|
||||
|
||||
// 创建连接(将 ccb --acp 作为子进程启动)
|
||||
const child = spawn('ccb', ['--acp'])
|
||||
const stream = ndJsonStream(
|
||||
Writable.toWeb(child.stdin),
|
||||
Readable.toWeb(child.stdout),
|
||||
)
|
||||
|
||||
const client = new ClientSideConnection(stream)
|
||||
|
||||
// 初始化
|
||||
await client.initialize({ clientCapabilities: {} })
|
||||
|
||||
// 创建会话
|
||||
const { sessionId } = await client.newSession({
|
||||
cwd: '/path/to/project',
|
||||
})
|
||||
|
||||
// 发送 prompt
|
||||
const response = await client.prompt({
|
||||
sessionId,
|
||||
prompt: [{ type: 'text', text: 'Hello, explain this project' }],
|
||||
})
|
||||
|
||||
// 监听 session 更新
|
||||
client.on('sessionUpdate', (update) => {
|
||||
console.log('Update:', update)
|
||||
})
|
||||
```
|
||||
|
||||
## 五、ACP 协议支持矩阵
|
||||
|
||||
| 方法 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| `initialize` | ✅ | 返回 agent 信息和能力 |
|
||||
| `authenticate` | ✅ | 无需认证(自托管) |
|
||||
| `newSession` | ✅ | 创建新会话 |
|
||||
| `resumeSession` | ✅ | 恢复已有会话(含历史回放) |
|
||||
| `loadSession` | ✅ | 加载指定会话(含历史回放) |
|
||||
| `listSessions` | ✅ | 列出可用会话 |
|
||||
| `forkSession` | ✅ | 分叉会话 |
|
||||
| `closeSession` | ✅ | 关闭会话 |
|
||||
| `prompt` | ✅ | 发送消息,支持排队 |
|
||||
| `cancel` | ✅ | 取消当前/排队的 prompt |
|
||||
| `setSessionMode` | ✅ | 切换权限模式 |
|
||||
| `setSessionModel` | ✅ | 切换 AI 模型 |
|
||||
| `setSessionConfigOption` | ✅ | 动态修改配置 |
|
||||
|
||||
### SessionUpdate 类型
|
||||
|
||||
| 类型 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| `agent_message_chunk` | ✅ | 助手文本消息 |
|
||||
| `agent_thought_chunk` | ✅ | 思考/推理内容 |
|
||||
| `user_message_chunk` | ✅ | 用户消息(历史回放) |
|
||||
| `tool_call` | ✅ | 工具调用开始 |
|
||||
| `tool_call_update` | ✅ | 工具调用结果/状态更新 |
|
||||
| `usage_update` | ✅ | token 用量 + context window |
|
||||
| `plan` | ✅ | TodoWrite → plan entries |
|
||||
| `available_commands_update` | ✅ | 斜杠命令 & skills 列表 |
|
||||
| `current_mode_update` | ✅ | 模式切换通知 |
|
||||
| `config_option_update` | ✅ | 配置更新通知 |
|
||||
389
docs/features/agents/acp.md
Normal file
389
docs/features/agents/acp.md
Normal file
@@ -0,0 +1,389 @@
|
||||
---
|
||||
title: "ACP 协议:接入 Zed / Cursor 等 IDE"
|
||||
description: "通过 ACP(Agent Client Protocol)把 CCB 接入支持 ACP 的 IDE。本文包含 acp-link CLI 用法、权限桥接、以及 Zed 集成案例。"
|
||||
keywords: ["ACP 协议", "Zed 编辑器", "acp-link", "权限桥接", "IDE 集成"]
|
||||
---
|
||||
|
||||
# ACP 协议:接入 Zed / Cursor 等 IDE
|
||||
|
||||
## 概述
|
||||
|
||||
ACP (Agent Client Protocol) 是一种标准化的 stdio 协议,允许 IDE 和编辑器通过 stdin/stdout 的 NDJSON 流驱动 AI Agent。CCB 实现了完整的 ACP agent 端,可以被 Zed、Cursor 等支持 ACP 的客户端直接调用。
|
||||
|
||||
CCB 在 ACP 体系下提供两层能力:
|
||||
|
||||
- **ACP Agent**(源码目录 `src/services/acp/`):CCB 自身作为 ACP agent,通过 `ccb --acp` 暴露 stdio 接口,由 IDE 直接调用。
|
||||
- **acp-link 代理服务器**(源码目录 `packages/acp-link/`):将 WebSocket 客户端桥接到 ACP agent 的 stdio 接口,让 ACP agent 可以通过 WebSocket 远程访问,而不仅限于本地 stdio。
|
||||
|
||||
### 核心特性
|
||||
|
||||
ACP Agent:
|
||||
|
||||
- **会话管理**:新建 / 恢复 / 加载 / 分叉 / 关闭会话
|
||||
- **历史回放**:恢复会话时自动加载并回放对话历史
|
||||
- **权限桥接**:ACP 客户端的权限决策映射到 CCB 的工具权限系统
|
||||
- **斜杠命令 & Skills**:加载真实命令列表,支持 `/commit`、`/review` 等 prompt 型 skill
|
||||
- **Context Window 跟踪**:精确的 usage_update,含 model prefix matching
|
||||
- **Prompt 排队**:支持连续发送多条 prompt,自动排队处理
|
||||
- **模式切换**:auto / default / acceptEdits / plan / dontAsk / bypassPermissions
|
||||
- **模型切换**:运行时切换 AI 模型
|
||||
|
||||
acp-link:
|
||||
|
||||
- **WebSocket → stdio 桥接**:将浏览器/远程客户端的 WebSocket 连接转换为 ACP agent 的 stdin/stdout NDJSON 流
|
||||
- **会话管理**:创建、加载、恢复、列出、关闭会话
|
||||
- **权限审批流程**:客户端可远程审批 agent 的工具权限请求
|
||||
- **RCS 集成**:可与 Remote Control Server (RCS) 连接,将 ACP agent 注册到 RCS 并通过 Web UI 交互
|
||||
- **HTTPS 支持**:内置自签名证书生成,支持安全连接
|
||||
- **Token 认证**:自动生成或通过环境变量配置认证 token
|
||||
|
||||
## 快速上手
|
||||
|
||||
### 在 Zed 中接入 CCB
|
||||
|
||||
1. 打开 Zed 的 `settings.json`(`Cmd+,` → Open Settings),添加 `agent_servers` 配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"ccb": {
|
||||
"type": "custom",
|
||||
"command": "ccb",
|
||||
"args": ["--acp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. API 认证:CCB 的 ACP agent 在启动时会自动加载 `settings.json` 中的环境变量(`ANTHROPIC_BASE_URL`、`ANTHROPIC_AUTH_TOKEN` 等)。确保已通过 `/login` 配置好 API 供应商;也可在 `agent_servers` 中显式传入 `env`:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"claude-code": {
|
||||
"command": "ccb",
|
||||
"args": ["--acp"],
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://api.example.com/v1",
|
||||
"ANTHROPIC_AUTH_TOKEN": "sk-xxx"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. 重启 Zed,打开任意项目目录。
|
||||
4. 按 `Cmd+'`(macOS)或 `Ctrl+'`(Linux)打开 Agent Panel。
|
||||
5. 在 Agent Panel 顶部的下拉菜单中选择 **claude-code**。
|
||||
6. 开始对话。
|
||||
|
||||
### Zed 中的功能操作
|
||||
|
||||
| 功能 | 操作 |
|
||||
|------|------|
|
||||
| 对话 | 在 Agent Panel 中直接输入消息 |
|
||||
| 斜杠命令 | 输入 `/` 查看可用 skills 列表(如 `/commit`、`/review`) |
|
||||
| 工具权限 | 弹出权限请求时选择 Allow / Reject / Always Allow |
|
||||
| 模式切换 | 通过 Agent Panel 的设置菜单切换 auto/default/plan 等模式 |
|
||||
| 模型切换 | 通过 Agent Panel 的设置菜单切换 AI 模型 |
|
||||
| 会话恢复 | 关闭重开 Zed 后,之前的会话可自动恢复(含历史消息) |
|
||||
|
||||
### 通过 acp-link 暴露到网络
|
||||
|
||||
```bash
|
||||
# 直接运行(在 monorepo 中)
|
||||
# 注意:claude 本身不支持 ACP,需要用 ccb-bun --acp 启动 ACP agent
|
||||
bun packages/acp-link/src/cli/bin.ts ccb-bun -- --acp
|
||||
|
||||
# 指定端口和主机
|
||||
acp-link --port 9000 --host 0.0.0.0 ccb-bun -- --acp
|
||||
|
||||
# 启用 HTTPS(自签名证书)
|
||||
acp-link --https ccb-bun -- --acp
|
||||
|
||||
# 调试模式
|
||||
acp-link --debug ccb-bun -- --acp
|
||||
```
|
||||
|
||||
## 详细说明
|
||||
|
||||
### ACP Agent 架构
|
||||
|
||||
```
|
||||
┌──────────────┐ NDJSON/stdio ┌──────────────────┐
|
||||
│ Zed / IDE │ ◄────────────────► │ CCB ACP Agent │
|
||||
│ (Client) │ stdin / stdout │ (Agent) │
|
||||
└──────────────┘ │ │
|
||||
│ entry.ts │ ← stdio → NDJSON stream
|
||||
│ agent.ts │ ← ACP protocol handler
|
||||
│ bridge.ts │ ← SDKMessage → ACP SessionUpdate
|
||||
│ permissions.ts │ ← 权限桥接
|
||||
│ utils.ts │ ← 通用工具
|
||||
│ │
|
||||
│ QueryEngine │ ← 内部查询引擎
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `entry.ts` | 入口,创建 stdio → NDJSON stream,启动 `AgentSideConnection` |
|
||||
| `agent.ts` | 实现 ACP `Agent` 接口:会话 CRUD、prompt、cancel、模式/模型切换 |
|
||||
| `bridge.ts` | `SDKMessage` → ACP `SessionUpdate` 转换:文本/思考/工具/用量/编辑 diff |
|
||||
| `permissions.ts` | ACP `requestPermission()` → CCB `CanUseToolFn` 桥接 |
|
||||
| `utils.ts` | Pushable、流转换、权限模式解析、session fingerprint、路径显示 |
|
||||
|
||||
### acp-link 架构
|
||||
|
||||
#### 独立模式
|
||||
|
||||
```
|
||||
┌──────────────────┐ WebSocket ┌──────────────────┐ stdio/NDJSON ┌──────────────┐
|
||||
│ 浏览器/客户端 │ ◄──────────────►│ acp-link │ ◄────────────────►│ ACP Agent │
|
||||
│ (WS Client) │ ws://host:port │ (Proxy Server) │ spawn subprocess │ (Claude等) │
|
||||
└──────────────────┘ └──────────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
#### RCS 集成模式
|
||||
|
||||
```
|
||||
┌──────────────┐ WebSocket ┌──────────────────┐ stdio/NDJSON ┌──────────────┐
|
||||
│ RCS Web UI │ ◄──────────────►│ Remote Control │ ◄─────────────────►│ acp-link │
|
||||
│ (/code/*) │ ACP Relay WS │ Server (RCS) │ ACP events │ + Agent │
|
||||
└──────────────┘ └──────────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
#### 文件结构
|
||||
|
||||
```
|
||||
packages/acp-link/
|
||||
├── src/
|
||||
│ ├── server.ts # 主服务器:WS 连接管理、会话管理、权限处理、消息桥接
|
||||
│ ├── rcs-upstream.ts # RCS 上游客户端:REST 注册 + WS identify 两步流程
|
||||
│ ├── cert.ts # TLS 证书生成(自签名)
|
||||
│ ├── logger.ts # 日志模块
|
||||
│ ├── types.ts # JSON-RPC 和 ACP 协议类型定义
|
||||
│ ├── cli/
|
||||
│ │ ├── bin.ts # CLI 入口
|
||||
│ │ ├── command.ts # 命令行参数解析
|
||||
│ │ ├── app.ts # 应用启动
|
||||
│ │ └── context.ts # 上下文配置
|
||||
│ └── __tests__/ # 测试(cert, server, types)
|
||||
├── package.json
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
### acp-link CLI 参考
|
||||
|
||||
```
|
||||
USAGE
|
||||
acp-link [--port value] [--host value] [--debug] [--no-auth] [--https] <command>...
|
||||
acp-link --help
|
||||
acp-link --version
|
||||
|
||||
FLAGS
|
||||
[--port] Port to listen on [default = 9315]
|
||||
[--host] Host to bind to [default = localhost]
|
||||
[--debug] Enable debug logging to file
|
||||
[--no-auth] Disable authentication (dangerous)
|
||||
[--https] Enable HTTPS with self-signed cert
|
||||
-h --help Print help information and exit
|
||||
-v --version Print version information and exit
|
||||
|
||||
ARGUMENTS
|
||||
command... Agent command followed by its arguments (e.g. "ccb-bun -- --acp")
|
||||
```
|
||||
|
||||
### 接入其他 ACP 客户端
|
||||
|
||||
ACP 是开放协议,任何支持 ACP 的客户端都可以连接 CCB。通用配置模式:
|
||||
|
||||
```
|
||||
命令: ccb --acp
|
||||
参数: ["--acp"]
|
||||
通信: stdin/stdout NDJSON
|
||||
协议版本: ACP v1
|
||||
```
|
||||
|
||||
#### Cursor
|
||||
|
||||
在 Cursor 的设置中配置 MCP / Agent Server,使用同样的 `ccb --acp` 命令。
|
||||
|
||||
#### 自定义客户端
|
||||
|
||||
使用 `@agentclientprotocol/sdk` 可以快速构建 ACP 客户端:
|
||||
|
||||
```typescript
|
||||
import { ClientSideConnection, ndJsonStream } from '@agentclientprotocol/sdk'
|
||||
|
||||
// 创建连接(将 ccb --acp 作为子进程启动)
|
||||
const child = spawn('ccb', ['--acp'])
|
||||
const stream = ndJsonStream(
|
||||
Writable.toWeb(child.stdin),
|
||||
Readable.toWeb(child.stdout),
|
||||
)
|
||||
|
||||
const client = new ClientSideConnection(stream)
|
||||
|
||||
// 初始化
|
||||
await client.initialize({ clientCapabilities: {} })
|
||||
|
||||
// 创建会话
|
||||
const { sessionId } = await client.newSession({
|
||||
cwd: '/path/to/project',
|
||||
})
|
||||
|
||||
// 发送 prompt
|
||||
const response = await client.prompt({
|
||||
sessionId,
|
||||
prompt: [{ type: 'text', text: 'Hello, explain this project' }],
|
||||
})
|
||||
|
||||
// 监听 session 更新
|
||||
client.on('sessionUpdate', (update) => {
|
||||
console.log('Update:', update)
|
||||
})
|
||||
```
|
||||
|
||||
## 进阶与参考
|
||||
|
||||
### 认证
|
||||
|
||||
默认启动时 acp-link 自动生成随机 token。客户端连接时不要把 token 放在 URL 中:
|
||||
|
||||
```
|
||||
ws://localhost:9315/ws
|
||||
```
|
||||
|
||||
无法发送 `Authorization` header 的 WebSocket 客户端需要使用
|
||||
`rcs.auth.<base64url-token>` 子协议传递 token。
|
||||
|
||||
配置固定 token:
|
||||
|
||||
```bash
|
||||
ACP_AUTH_TOKEN=my-fixed-token acp-link ccb-bun -- --acp
|
||||
```
|
||||
|
||||
禁用认证(不推荐,仅用于开发):
|
||||
|
||||
```bash
|
||||
acp-link --no-auth ccb-bun -- --acp
|
||||
```
|
||||
|
||||
### RCS 集成
|
||||
|
||||
acp-link 支持将 ACP agent 注册到 Remote Control Server,通过 Web UI 远程操控。
|
||||
|
||||
```bash
|
||||
# 通过环境变量配置 RCS 连接
|
||||
ACP_RCS_URL=http://localhost:3000 \
|
||||
ACP_RCS_TOKEN=sk-rcs-your-key \
|
||||
acp-link ccb-bun -- --acp
|
||||
```
|
||||
|
||||
注册流程(两步):
|
||||
|
||||
1. **REST 注册**:通过 `POST /v1/environments/bridge` 向 RCS 注册环境
|
||||
2. **WS identify**:建立 WebSocket 连接后发送 `identify` 消息(携带 agentId),替代完整 `register`
|
||||
|
||||
RCS 的 ACP WebSocket 连接不接受 URL query token。acp-link 会通过
|
||||
`rcs.auth.<base64url-token>` WebSocket 子协议发送 `ACP_RCS_TOKEN`。
|
||||
|
||||
```
|
||||
acp-link RCS
|
||||
│ │
|
||||
│── POST /v1/environments/bridge ──►│ (REST 注册)
|
||||
│◄── { agentId, sessionId } ───────│
|
||||
│ │
|
||||
│── WS connect ─────────────────►│ (WebSocket)
|
||||
│── identify { agentId } ────────►│ (WS 标识)
|
||||
│◄── identified ─────────────────│
|
||||
│ │
|
||||
│── ACP events ─────────────────►│ (双向消息转发)
|
||||
│◄── user prompts/permissions ───│
|
||||
```
|
||||
|
||||
### 权限模式
|
||||
|
||||
#### permissionMode 传递链
|
||||
|
||||
权限模式通过整条链路传递:Web UI → RCS → acp-link → ACP agent。
|
||||
|
||||
支持的权限模式:
|
||||
|
||||
- `default` — 每次请求权限确认
|
||||
- `auto` — 自动判断
|
||||
- `acceptEdits` — 自动接受编辑
|
||||
- `plan` — 规划模式
|
||||
- `dontAsk` — 不询问
|
||||
- `bypassPermissions` — 绕过权限(需 sandbox 环境)
|
||||
|
||||
#### fallback 链
|
||||
|
||||
当客户端未显式传递 permissionMode 时,使用以下 fallback 链:
|
||||
|
||||
```
|
||||
客户端传值 > config.permissionMode > ACP_PERMISSION_MODE 环境变量
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
ACP_PERMISSION_MODE=auto acp-link ccb-bun -- --acp
|
||||
```
|
||||
|
||||
#### 权限管道改进
|
||||
|
||||
- **模式同步**:`applySessionMode` 在 agent 切换权限模式时同步 `appState.toolPermissionContext.mode`,确保内部权限上下文与 ACP 客户端状态一致。
|
||||
- **统一权限流水线**:`createAcpCanUseTool` 接入 `hasPermissionsToUseTool` 统一权限流水线,替代原来分散的处理逻辑。支持 `onModeChange` 回调,模式变更时实时同步。
|
||||
- **bypass 检测**:`bypassPermissions` 模式增加可用性检测 — 仅在非 root 或 sandbox 环境中允许启用,防止权限绕过的安全风险。
|
||||
|
||||
### ACP 协议支持矩阵
|
||||
|
||||
| 方法 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| `initialize` | 支持 | 返回 agent 信息和能力 |
|
||||
| `authenticate` | 支持 | 无需认证(自托管) |
|
||||
| `newSession` | 支持 | 创建新会话 |
|
||||
| `resumeSession` | 支持 | 恢复已有会话(含历史回放) |
|
||||
| `loadSession` | 支持 | 加载指定会话(含历史回放) |
|
||||
| `listSessions` | 支持 | 列出可用会话 |
|
||||
| `forkSession` | 支持 | 分叉会话 |
|
||||
| `closeSession` | 支持 | 关闭会话 |
|
||||
| `prompt` | 支持 | 发送消息,支持排队 |
|
||||
| `cancel` | 支持 | 取消当前/排队的 prompt |
|
||||
| `setSessionMode` | 支持 | 切换权限模式 |
|
||||
| `setSessionModel` | 支持 | 切换 AI 模型 |
|
||||
| `setSessionConfigOption` | 支持 | 动态修改配置 |
|
||||
|
||||
#### SessionUpdate 类型
|
||||
|
||||
| 类型 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| `agent_message_chunk` | 支持 | 助手文本消息 |
|
||||
| `agent_thought_chunk` | 支持 | 思考/推理内容 |
|
||||
| `user_message_chunk` | 支持 | 用户消息(历史回放) |
|
||||
| `tool_call` | 支持 | 工具调用开始 |
|
||||
| `tool_call_update` | 支持 | 工具调用结果/状态更新 |
|
||||
| `usage_update` | 支持 | token 用量 + context window |
|
||||
| `plan` | 支持 | TodoWrite → plan entries |
|
||||
| `available_commands_update` | 支持 | 斜杠命令 & skills 列表 |
|
||||
| `current_mode_update` | 支持 | 模式切换通知 |
|
||||
| `config_option_update` | 支持 | 配置更新通知 |
|
||||
|
||||
### 环境变量与功能开关
|
||||
|
||||
#### 环境变量
|
||||
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| `ACP_AUTH_TOKEN` | 固定认证 token(默认自动生成) |
|
||||
| `ACP_PERMISSION_MODE` | 默认权限模式 fallback |
|
||||
| `ACP_RCS_URL` | RCS 服务器地址(启用 RCS 集成) |
|
||||
| `ACP_RCS_TOKEN` | RCS API token |
|
||||
|
||||
#### 功能开关
|
||||
|
||||
ACP Agent 与 acp-link 受 `FEATURE_ACP` 控制,build 和 dev 模式默认启用。源码目录:
|
||||
|
||||
- ACP Agent:`src/services/acp/`
|
||||
- acp-link:`packages/acp-link/`(相关 PR:#292,新增时间:2026-04-18)
|
||||
420
docs/features/agents/pipes-and-lan.md
Normal file
420
docs/features/agents/pipes-and-lan.md
Normal file
@@ -0,0 +1,420 @@
|
||||
---
|
||||
title: "群控:本机 + 局域网多实例协作"
|
||||
description: "多台 CCB 实例零配置组网,同机用 UDS、跨机用 LAN,自动发现与消息路由。包含 /pipes 命令、心跳机制、消息路由详解。"
|
||||
keywords: ["群控", "局域网协作", "UDS", "多实例", "消息路由"]
|
||||
---
|
||||
|
||||
# 群控:本机 + 局域网多实例协作
|
||||
|
||||
## 概述
|
||||
|
||||
Pipes 系统提供 Claude Code CLI 实例之间的通讯能力,让你可以在一台机器(main)上操控其他实例(sub),发送 prompt、查看执行结果、审批权限请求——全程零配置。
|
||||
|
||||
系统分两层,使用同一套协议(NDJSON)和同一套命令(`/pipes`、`/attach`、`/send` 等),对用户完全透明:
|
||||
|
||||
1. **本机 Pipes(UDS)**:同一台机器上的多个 CLI 实例通过 Unix Domain Socket(Linux/macOS)或 Windows Named Pipe 协作
|
||||
2. **局域网 Pipes(LAN)**:不同机器上的 CLI 实例通过 TCP + UDP Multicast beacon 协作
|
||||
|
||||
> 严格区分:`/peers` 解决"找到其他会话并发消息"(通用消息投递),`/pipes` 解决"把一个 REPL 变成另一个 REPL 的受控 worker"(主从 REPL 协调平面)。两者职责不同,不要混淆。
|
||||
|
||||
### 两层职责拆解
|
||||
|
||||
| 层 | 面向 | 传输方式 | 对外入口 |
|
||||
|------|------|----------|----------|
|
||||
| UDS peer messaging | 任意 CCB 进程 | 本机 Unix socket / Named pipe | `/peers`、`SendMessageTool` 的 `uds:<socket-path>` |
|
||||
| pipes control plane | 交互式 REPL 会话间的主从协作 | 本机 socket + LAN TCP | `/pipes`、`/attach`、`/detach`、`/send`、`/pipe-status`、`/claim-main` |
|
||||
|
||||
两层都依赖本机 socket,但命名、角色模型、交互语义和 UI 集成都不同:peer 层按 socket 路径寻址,服务工具调用;pipes 层按 `cli-xxxxxxxx` 会话名和 `main/sub/master/slave` 角色工作,直接影响 REPL 提交路径和 PromptInput 页脚。
|
||||
|
||||
## 快速上手
|
||||
|
||||
### 场景一:本机多实例
|
||||
|
||||
```bash
|
||||
# 终端 1
|
||||
bun run dev
|
||||
# 启动后自动注册为 main
|
||||
|
||||
# 终端 2
|
||||
bun run dev
|
||||
# 自动注册为 sub-1,被 main 自动 attach
|
||||
```
|
||||
|
||||
在终端 1 中输入 `/pipes`,可以看到两个实例。选中 sub-1 后,输入的消息会自动转发到 sub-1 执行。
|
||||
|
||||
### 场景二:局域网多机器
|
||||
|
||||
前置条件:
|
||||
|
||||
- 两台或以上机器在同一局域网
|
||||
- 每台机器安装了 CCB 并能 `bun run dev`
|
||||
- 防火墙允许 UDP 7101 + TCP 动态端口(见下方配置)
|
||||
|
||||
```bash
|
||||
# 机器 A (192.168.50.22)
|
||||
bun run dev
|
||||
|
||||
# 机器 B (192.168.50.27)
|
||||
bun run dev
|
||||
```
|
||||
|
||||
两边启动后等 3-5 秒(beacon 广播间隔),LAN peers 会自动发现并 attach。输入 `/pipes` 可看到标记 `[LAN]` 的远端实例。
|
||||
|
||||
## 防火墙配置
|
||||
|
||||
**每台机器都需要执行。** 请先确认网络为局域网(非公共 WiFi),路由器未开启 AP 隔离,两台机器在同一子网(`ping` 能通)。
|
||||
|
||||
### Windows(管理员 PowerShell)
|
||||
|
||||
```powershell
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Beacon (UDP)" -Direction Inbound -Protocol UDP -LocalPort 7101 -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Pipes (TCP)" -Direction Inbound -Protocol TCP -LocalPort 1024-65535 -Program (Get-Command bun).Source -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Beacon Out (UDP)" -Direction Outbound -Protocol UDP -RemotePort 7101 -Action Allow -Profile Private
|
||||
# 确认网络为"专用":Get-NetConnectionProfile
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
首次运行时系统弹出"允许接受传入连接"对话框,点击"允许"即可。如果使用 pf 防火墙:
|
||||
|
||||
```bash
|
||||
echo "pass in proto udp from any to any port 7101" | sudo pfctl -ef -
|
||||
```
|
||||
|
||||
### Linux(firewalld / iptables)
|
||||
|
||||
```bash
|
||||
# firewalld
|
||||
sudo firewall-cmd --zone=trusted --add-port=7101/udp --permanent
|
||||
sudo firewall-cmd --zone=trusted --add-port=1024-65535/tcp --permanent
|
||||
sudo firewall-cmd --reload
|
||||
|
||||
# 或 iptables
|
||||
sudo iptables -A INPUT -p udp --dport 7101 -j ACCEPT
|
||||
sudo iptables -A INPUT -p tcp --dport 1024:65535 -m owner --uid-owner $(id -u) -j ACCEPT
|
||||
```
|
||||
|
||||
## 交互面板与快捷键
|
||||
|
||||
### 状态栏
|
||||
|
||||
执行 `/pipes` 后,输入框底部出现 pipe 状态栏(单行),始终可见(直到会话结束):
|
||||
|
||||
```
|
||||
pipe: cli-a91bad56 (main) 192.168.50.22 2/3 selected selected pipes only · ←/→ or m switch · Shift+↓ edit
|
||||
```
|
||||
|
||||
显示:当前 pipe 名、角色、IP、已选数/总数、路由模式。
|
||||
|
||||
### 展开选择面板
|
||||
|
||||
按 **Shift+↓**(Shift + 下箭头)展开选择面板:
|
||||
|
||||
```
|
||||
pipe: cli-a91bad56 (main) 192.168.50.22 ↑↓ move Space select ←/→ or m route Enter/Esc close Shift+↓ toggle
|
||||
当前普通 prompt 走 已选 sub;切换不会清空选择
|
||||
☑ cli-da029538 (sub-1 XC/192.168.50.22)
|
||||
☐ cli-04d67950 (main vmwin11/192.168.50.27)
|
||||
☑ cli-893747d3 [offline] (sub-2 vmwin11/192.168.50.27)
|
||||
```
|
||||
|
||||
### 面板快捷键
|
||||
|
||||
| 快捷键 | 场景 | 作用 |
|
||||
|--------|------|------|
|
||||
| **Shift+↓** | 状态栏可见时 | 展开/收起选择面板 |
|
||||
| **↑ / ↓** | 面板展开时 | 上下移动光标 |
|
||||
| **Space** | 面板展开时 | 切换当前光标所在 pipe 的选中状态(☑ ↔ ☐) |
|
||||
| **Enter** | 面板展开时 | 确认并关闭面板 |
|
||||
| **Esc** | 面板展开时 | 取消并关闭面板 |
|
||||
| **← / → 或 M** | 状态栏可见且有选中 pipe 时 | 切换路由模式(`selected pipes only` ↔ `local main`) |
|
||||
|
||||
### 完整操作流程示例
|
||||
|
||||
```
|
||||
1. 输入 /pipes → 状态栏出现,显示发现的实例
|
||||
2. 按 Shift+↓ → 展开选择面板
|
||||
3. 按 ↓ 移动到目标 pipe → 光标移到 cli-04d67950
|
||||
4. 按 Space → 选中 ☑ cli-04d67950
|
||||
5. 按 Enter → 确认,面板收起
|
||||
6. 输入 "帮我检查 git status" → prompt 自动发送到 cli-04d67950 执行
|
||||
7. 按 M → 切换到 local main 模式
|
||||
8. 输入 "本地做点什么" → 仅在本地执行
|
||||
9. 按 M → 切回 selected pipes only
|
||||
10. 输入 "继续远端任务" → 又发送到 cli-04d67950
|
||||
```
|
||||
|
||||
远端执行结果会流式回传到你的消息列表:
|
||||
|
||||
```
|
||||
[main vmwin11/192.168.50.27 / cli-04d67950] 正在检查 git status...
|
||||
[main vmwin11/192.168.50.27 / cli-04d67950] Completed
|
||||
```
|
||||
|
||||
## 消息路由
|
||||
|
||||
### 路由模式
|
||||
|
||||
通过 **M 键**(或 ← / →)切换,**无需展开面板**。切换路由模式**不会清空选择**——你可以在 `local main` 模式下保持选择,随时按 M 切回继续向远端发送。
|
||||
|
||||
| 模式 | 状态栏显示 | 行为 |
|
||||
|------|-----------|------|
|
||||
| `selected pipes only` | 绿色高亮 | 输入的 prompt **仅**发送到选中的 pipe,本地不执行 |
|
||||
| `local main` | 灰色 | 输入的 prompt 在**本地 main** 执行,不转发到任何 pipe |
|
||||
|
||||
### 选中 pipe 后的自动路由
|
||||
|
||||
1. 通过 `/pipes select` 或 Shift+↓ 面板选中一个或多个 pipe
|
||||
2. 在输入框中正常输入消息
|
||||
3. 消息自动发送到所有选中的**已连接** pipe
|
||||
4. 每个 pipe 独立执行,结果流式回传到 main 的消息列表
|
||||
|
||||
> 选中但未连接的 pipe 不会导致本地处理被错误跳过——只有已连接的 pipe 会收到广播。
|
||||
|
||||
## 命令参考
|
||||
|
||||
### /pipes
|
||||
|
||||
显示所有发现的实例,管理选择状态。再次执行 `/pipes` 切换面板展开/收起。
|
||||
|
||||
```
|
||||
/pipes — 显示所有实例 + 切换选择面板
|
||||
/pipes select <name> — 选中某实例(消息会广播到它)
|
||||
/pipes deselect <name> — 取消选中
|
||||
/pipes all — 全选
|
||||
/pipes none — 全部取消
|
||||
```
|
||||
|
||||
输出示例:
|
||||
|
||||
```
|
||||
Your pipe: cli-a91bad56
|
||||
Role: main
|
||||
Machine ID: 205d6c3a...
|
||||
IP: 192.168.50.22
|
||||
Host: XC
|
||||
|
||||
Main machine: 205d6c3a... (this machine)
|
||||
[main] cli-a91bad56 XC/192.168.50.22 [alive] (you)
|
||||
☑ [sub-1] cli-da029538 XC/192.168.50.22 [alive] [connected]
|
||||
|
||||
LAN Peers:
|
||||
☐ [main] cli-04d67950 vmwin11/192.168.50.27 tcp:192.168.50.27:58853 [LAN]
|
||||
|
||||
Selected: cli-da029538
|
||||
```
|
||||
|
||||
### 其他命令
|
||||
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
| `/attach <name>` | 手动 attach 到一个实例(自动识别 LAN peer 并通过 TCP 连接),使其成为 slave |
|
||||
| `/detach <name>` | 断开与某个 slave 的连接 |
|
||||
| `/send <name> <msg>` | 向指定 pipe 发送消息(不依赖选择状态,直接指定目标) |
|
||||
| `/send tcp:host:port <msg>` | 直接通过 TCP 地址发送 |
|
||||
| `/claim-main` | 强制声明当前机器为 main(用于 main 意外退出后的恢复) |
|
||||
| `/pipe-status` | 显示详细状态 |
|
||||
| `/peers` | 列出所有已发现的 peer |
|
||||
|
||||
通常不需要手动 attach——heartbeat 会自动发现并连接。attach 后对方变为 slave,你变为 master,可以向它发送 prompt。
|
||||
|
||||
示例:
|
||||
|
||||
```
|
||||
/attach cli-04d67950
|
||||
/send cli-04d67950 请帮我检查一下日志
|
||||
/send tcp:192.168.50.27:58853 hello
|
||||
```
|
||||
|
||||
## 权限转发
|
||||
|
||||
当远端 slave 执行需要权限的工具(如 BashTool)时:
|
||||
|
||||
1. slave 发送 `permission_request` 到 main
|
||||
2. main 弹出权限确认对话框,显示来源标记 `[role hostname/ip / pipeName]`
|
||||
3. 用户确认/拒绝
|
||||
4. 结果发回 slave,继续或中断
|
||||
|
||||
> AI 通过 `SendMessageTool` 发送 `tcp:` 消息时需用户显式确认。
|
||||
|
||||
## 架构详解
|
||||
|
||||
### 通信协议
|
||||
|
||||
所有通讯使用 NDJSON(Newline-Delimited JSON),每行一个消息:
|
||||
|
||||
```json
|
||||
{"type":"ping","from":"cli-abc","ts":"2026-04-11T00:00:00.000Z"}
|
||||
{"type":"prompt","data":"帮我查看 git status","from":"cli-abc","ts":"..."}
|
||||
{"type":"stream","data":"正在执行...","from":"cli-def","ts":"..."}
|
||||
{"type":"done","data":"","from":"cli-def","ts":"..."}
|
||||
```
|
||||
|
||||
### 消息类型
|
||||
|
||||
| 类型 | 方向 | 说明 |
|
||||
|------|------|------|
|
||||
| `ping`/`pong` | 双向 | 健康检查 |
|
||||
| `attach_request`/`accept`/`reject` | M→S/S→M | 连接控制 |
|
||||
| `detach` | M→S | 断开连接 |
|
||||
| `prompt` | M→S | 主向从发送 prompt |
|
||||
| `prompt_ack` | S→M | 从确认接收 |
|
||||
| `stream` | S→M | 从流式回传 AI 输出 |
|
||||
| `tool_start`/`tool_result` | S→M | 工具执行通知 |
|
||||
| `done` | S→M | 本轮完成 |
|
||||
| `error` | 双向 | 错误通知 |
|
||||
| `permission_request`/`response`/`cancel` | 双向 | 权限审批转发 |
|
||||
|
||||
### 传输层
|
||||
|
||||
```
|
||||
本机 LAN
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ PipeServer │ │ PipeServer │
|
||||
│ UDS sock │ │ UDS sock │
|
||||
│ TCP :rand │◄───TCP───►│ TCP :rand │
|
||||
├──────────────┤ ├──────────────┤
|
||||
│ LanBeacon │◄──UDP────►│ LanBeacon │
|
||||
│ 224.0.71.67 │ mcast │ 224.0.71.67 │
|
||||
└──────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
- **UDS / Named Pipe**:本机实例间通讯,通过文件系统路径寻址(`~/.claude/pipes/cli-xxx.sock`)
|
||||
- **TCP**:LAN 实例间通讯,动态端口,通过 beacon 发现
|
||||
- **UDP Multicast**:peer 发现,组地址 `224.0.71.67`,端口 `7101`,TTL=1(不跨路由器),3 秒广播一次 announce 包
|
||||
|
||||
### 角色模型
|
||||
|
||||
| 角色 | 说明 |
|
||||
|------|------|
|
||||
| `main` | 首个启动的实例,管理 registry |
|
||||
| `sub` | 后续启动的同机实例(或被 attach 的 LAN 实例) |
|
||||
| `master` | attach 了至少一个 slave 的实例 |
|
||||
| `slave` | 被 master attach 控制的实例 |
|
||||
|
||||
**角色转换规则:**
|
||||
|
||||
- 首个启动 → `main`
|
||||
- 同机后续启动 → `sub`(自动被 main attach → `slave`)
|
||||
- LAN 发现 → 两边都是 `main`,heartbeat 自动互相 attach(跨机器 attach 时,两边都可以是 main——不要求对方必须是 sub)
|
||||
- 被 attach → 变为 `slave`(可通过 `/detach` 恢复)
|
||||
|
||||
### 发现机制
|
||||
|
||||
**本机**:通过 `~/.claude/pipes/registry.json` 文件(带文件锁),`machineId` 绑定主机身份。同机 peer 层读取 `~/.claude/sessions/*.json`,按 `messagingSocketPath` 寻址。
|
||||
|
||||
**LAN**:通过 UDP multicast beacon:
|
||||
|
||||
1. 每台机器启动时创建 UDP multicast beacon,每 3 秒广播一次 `{ proto, pipeName, machineId, ip, tcpPort, role }`
|
||||
2. 收到其他实例的 announce → 记入 peers Map
|
||||
3. 15 秒未收到广播 → 标记 peer lost
|
||||
4. Heartbeat 合并 local registry + beacon peers → 统一 attach 目标列表
|
||||
|
||||
### Heartbeat 循环(5 秒间隔)
|
||||
|
||||
**main/master 角色:**
|
||||
|
||||
1. `cleanupStaleEntries()` — 清理 registry 中死掉的条目
|
||||
2. `getAliveSubs()` — 获取存活的本地 subs
|
||||
3. `refreshDiscoveredPipes()` — 刷新 discoveredPipes(包含 LAN peers)
|
||||
4. 合并 LAN peers 到 state
|
||||
5. 构建统一 attach 目标列表 — 本地 subs + LAN peers
|
||||
6. 遍历未连接的目标 → 自动 attach
|
||||
7. 清理断开的 slave 连接 — 同时检查 local registry 和 beacon
|
||||
|
||||
**sub 角色:**
|
||||
|
||||
1. 检测 main 是否存活
|
||||
2. main 死亡 → 同机则接管 main 角色,跨机则独立
|
||||
|
||||
### 当前 REPL 行为
|
||||
|
||||
当前线上行为由 `src/screens/REPL.tsx` 的内联实现负责(以该文件、`pipeTransport.ts`、`pipeRegistry.ts` 为事实来源):
|
||||
|
||||
1. 启动时创建当前 REPL 的 pipe server
|
||||
2. 通过 `pipeRegistry` 判定 `main` / `sub`
|
||||
3. 处理 `attach_request` / `detach` / `prompt`
|
||||
4. 主实例心跳探测并维护 `slaves`
|
||||
5. `/pipes` 打开状态栏并维护选择器
|
||||
6. 提交普通消息时,仅向**已连接**的 selected pipes 广播
|
||||
|
||||
过去的未接线 hook 方案已收敛,选中但未连接的 pipe 不会导致本地处理被错误跳过。
|
||||
|
||||
## 关键文件
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/utils/pipeTransport.ts` | PipeServer(双模 UDS+TCP)、PipeClient、类型定义 |
|
||||
| `src/utils/lanBeacon.ts` | UDP multicast beacon、singleton 管理 |
|
||||
| `src/utils/pipeRegistry.ts` | Registry CRUD、角色判定、machineId、LAN merge |
|
||||
| `src/utils/peerAddress.ts` | 地址解析(uds:/bridge:/tcp: scheme) |
|
||||
| `src/utils/udsMessaging.ts` | UDS peer messaging 服务端 |
|
||||
| `src/utils/udsClient.ts` | UDS peer messaging 客户端 |
|
||||
| `src/screens/REPL.tsx` | Bootstrap、heartbeat、cleanup、prompt 路由 |
|
||||
| `src/hooks/useMasterMonitor.ts` | Slave client registry、消息订阅 |
|
||||
| `src/hooks/useSlaveNotifications.ts` | Slave 端通知处理 |
|
||||
| `src/commands/pipes/pipes.ts` | /pipes 命令 |
|
||||
| `src/commands/attach/attach.ts` | /attach 命令 |
|
||||
| `src/commands/send/send.ts` | /send 命令 |
|
||||
| `packages/builtin-tools/src/tools/SendMessageTool/SendMessageTool.ts` | AI 发消息工具(含 tcp: 支持) |
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 看不到 LAN peer
|
||||
|
||||
1. 检查防火墙是否放行 UDP 7101
|
||||
2. `Get-NetConnectionProfile`(Windows)确认网络为"专用"
|
||||
3. 确认两台机器在同一子网(`ping` 能通)
|
||||
4. 路由器未开启 AP 隔离
|
||||
|
||||
### 连接超时
|
||||
|
||||
1. 检查 TCP 入站防火墙规则
|
||||
2. 确认没有 VPN 劫持流量
|
||||
3. 尝试 `/send tcp:ip:port hello` 直接测试
|
||||
|
||||
### beacon 绑到了错误网卡
|
||||
|
||||
Windows 上 WSL/Docker 虚拟网卡可能劫持 multicast。beacon 会自动选择非内部 IPv4 接口。如果选错,检查 `getLocalIp()` 返回值。
|
||||
|
||||
## 配置
|
||||
|
||||
### Feature Flag
|
||||
|
||||
| Flag | 控制范围 | 默认 |
|
||||
|------|----------|------|
|
||||
| `UDS_INBOX` | 本机 Pipe IPC 全部功能(含 UDS peer messaging + pipes control plane) | dev/build 启用 |
|
||||
| `LAN_PIPES` | 局域网 TCP + UDP beacon 扩展 | dev/build 启用 |
|
||||
|
||||
手动启用:
|
||||
|
||||
```bash
|
||||
FEATURE_UDS_INBOX=1 FEATURE_LAN_PIPES=1 bun run dev
|
||||
```
|
||||
|
||||
### 安全说明
|
||||
|
||||
- TCP 连接当前**无认证**——同 LAN 内知道端口号即可连接
|
||||
- Multicast TTL=1,不跨路由器
|
||||
- 建议仅在信任的局域网中使用
|
||||
|
||||
### 后续优化方向
|
||||
|
||||
**安全(P0)**
|
||||
|
||||
1. TCP 认证:首次连接时交换 HMAC-SHA256 token(基于 machineId + session secret)
|
||||
2. JSON schema 验证:在所有 `JSON.parse` 入口点增加 Zod 校验,防 prototype pollution
|
||||
3. Beacon 信息脱敏:hash machineId 后再广播
|
||||
|
||||
**可靠性(P1)**
|
||||
|
||||
4. 多网卡选择:`getLocalIp()` 应优先选择 RFC 1918 地址,排除 VPN/Docker 接口
|
||||
5. TCP target 验证:`parseTcpTarget()` 应限制目标为已知 beacon peers 或 RFC 1918 范围
|
||||
6. PipeServer close():改为 `Promise.allSettled` 并行关闭 UDS + TCP,加 `_closing` guard
|
||||
|
||||
**功能(P2)**
|
||||
|
||||
7. mDNS/DNS-SD:作为 multicast 受限环境下的 beacon 替代方案
|
||||
8. 固定端口配置:允许用户指定 TCP 端口范围,便于防火墙精确配置
|
||||
9. TLS 加密:TCP 传输加密,防中间人窃听
|
||||
10. 双向 prompt:当前只有 master → slave 方向,可考虑 slave 主动向 master 发送结果/请求
|
||||
@@ -1,576 +0,0 @@
|
||||
# Claude Code Best (CCB) — 全功能使用指南
|
||||
|
||||
本文档覆盖我们通过 13 个 PR 为 CCB 恢复/新增的**全部功能**,按类别组织,每个功能包含说明、使用方法和示例。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [Buddy 伴侣系统](#1-buddy-伴侣系统)
|
||||
2. [Remote Control 远程控制](#2-remote-control-远程控制)
|
||||
3. [定时任务 /triggers](#3-定时任务-triggers)
|
||||
4. [Voice Mode 语音模式](#4-voice-mode-语音模式)
|
||||
5. [Chrome 浏览器控制](#5-chrome-浏览器控制)
|
||||
6. [Computer Use 屏幕操控](#6-computer-use-屏幕操控)
|
||||
7. [Feature Flags 与 GrowthBook](#7-feature-flags-与-growthbook)
|
||||
8. [/ultraplan 高级规划](#8-ultraplan-高级规划)
|
||||
9. [Daemon 后台守护](#9-daemon-后台守护)
|
||||
10. [Pipe IPC 多实例协作](#10-pipe-ipc-多实例协作)
|
||||
11. [LAN Pipes 局域网群控](#11-lan-pipes-局域网群控)
|
||||
12. [Monitor 后台监控](#12-monitor-后台监控)
|
||||
13. [Workflow 工作流脚本](#13-workflow-工作流脚本)
|
||||
14. [Coordinator 多Worker协调](#14-coordinator-多worker协调)
|
||||
15. [Proactive 自主模式](#15-proactive-自主模式)
|
||||
16. [History / Snip 历史管理](#16-history--snip-历史管理)
|
||||
17. [Fork 子Agent](#17-fork-子agent)
|
||||
18. [其他恢复的工具](#18-其他恢复的工具)
|
||||
|
||||
---
|
||||
|
||||
## 1. Buddy 伴侣系统
|
||||
|
||||
**PR**: #82 `refactor(buddy): align companion system with official CLI`
|
||||
**Feature Flag**: `BUDDY`
|
||||
|
||||
### 说明
|
||||
Buddy 是一个后台运行的伴侣 AI,在你主对话进行的同时,异步观察会话内容并提供建议。
|
||||
|
||||
### 使用
|
||||
```bash
|
||||
# 启动时自动加载(feature 默认开启)
|
||||
bun run dev
|
||||
|
||||
# 在对话中,Buddy 会在适当时机自动提供建议
|
||||
# 例如当你在调试时,Buddy 可能提示你检查日志
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Remote Control 远程控制
|
||||
|
||||
**PR**: #60 `feat: enable Remote Control (BRIDGE_MODE)` + #170 `feat: restore daemon supervisor`
|
||||
**Feature Flag**: `BRIDGE_MODE`
|
||||
|
||||
### 说明
|
||||
通过 WebSocket 远程控制 Claude Code 会话。支持自托管私有部署。
|
||||
|
||||
### 使用
|
||||
```bash
|
||||
# 启动远程控制模式
|
||||
bun run dev -- remote-control
|
||||
|
||||
# 使用自托管服务器
|
||||
CLAUDE_BRIDGE_BASE_URL=https://your-server.com CLAUDE_BRIDGE_OAUTH_TOKEN=your-token bun run dev --remote-control
|
||||
|
||||
# 或通过 /remote-control 命令在会话中启动
|
||||
/remote-control
|
||||
```
|
||||
|
||||
### 命令
|
||||
- `claude remote-control` / `claude rc` — 启动远程控制客户端
|
||||
- `claude bridge` — 同上(别名)
|
||||
|
||||
---
|
||||
|
||||
## 3. 定时任务 /triggers
|
||||
|
||||
**PR**: #88 `feat: enable /schedule by adding AGENT_TRIGGERS_REMOTE`
|
||||
**Feature Flag**: `AGENT_TRIGGERS_REMOTE`
|
||||
|
||||
> 命令名已从 `/schedule` 改为 `/triggers`,避免与上游 bundled skill `schedule` 冲突。`/cron` 是别名。
|
||||
|
||||
### 说明
|
||||
创建定时执行的远程 agent 任务,支持 cron 表达式。
|
||||
|
||||
### 使用
|
||||
```
|
||||
/triggers create "每天检查依赖更新" --cron "0 9 * * *" --prompt "检查 package.json 中的过期依赖并创建更新 PR"
|
||||
/triggers list — 列出所有定时任务
|
||||
/triggers delete <id> — 删除指定任务
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Voice Mode 语音模式
|
||||
|
||||
**PR**: #92 `feat: enable /voice mode with native audio binaries`
|
||||
**Feature Flag**: `VOICE_MODE`
|
||||
|
||||
### 说明
|
||||
Push-to-Talk 语音输入,音频通过 WebSocket 流式传输到 Anthropic STT(Nova 3)。需要 Anthropic OAuth 认证(非 API key)。
|
||||
|
||||
### 使用
|
||||
```bash
|
||||
# 确保已通过 OAuth 登录
|
||||
claude auth login
|
||||
|
||||
# 在会话中按住指定键说话
|
||||
# 松开后自动转写为文字输入
|
||||
```
|
||||
|
||||
### 前提条件
|
||||
- Anthropic OAuth 认证(不支持 API key 模式)
|
||||
- 系统麦克风权限
|
||||
|
||||
---
|
||||
|
||||
## 5. Chrome 浏览器控制
|
||||
|
||||
**PR**: #93 `feat: enable Claude in Chrome MCP with full browser control`
|
||||
**Feature Flag**: `CHICAGO_MCP`
|
||||
|
||||
### 说明
|
||||
通过 Chrome 扩展控制浏览器:导航、点击、填表、截图、执行 JS。
|
||||
|
||||
### 使用
|
||||
```bash
|
||||
# 启动带 Chrome 控制的模式
|
||||
bun run dev -- --chrome
|
||||
|
||||
# 安装 Chrome 扩展后,AI 可以:
|
||||
# - 打开网页、点击按钮
|
||||
# - 填写表单
|
||||
# - 截取页面内容
|
||||
# - 执行 JavaScript
|
||||
```
|
||||
|
||||
### AI 可用工具
|
||||
- `navigate` — 导航到 URL
|
||||
- `click` / `find` / `form_input` — 页面交互
|
||||
- `get_page_text` / `read_page` — 读取内容
|
||||
- `javascript_tool` — 执行 JS
|
||||
- `gif_creator` — 录制操作 GIF
|
||||
|
||||
---
|
||||
|
||||
## 6. Computer Use 屏幕操控
|
||||
|
||||
**PR**: #98 + #137 `feat: Computer Use — 跨平台 Executor + Python Bridge + GUI 无障碍`
|
||||
**Feature Flag**: `CHICAGO_MCP`
|
||||
|
||||
### 说明
|
||||
跨平台屏幕操控:截图、键鼠模拟、应用管理。支持 macOS + Windows,Linux 后端待完成。
|
||||
|
||||
### 使用
|
||||
```bash
|
||||
# 启动后 AI 可自动调用屏幕操控工具
|
||||
bun run dev
|
||||
|
||||
# AI 可以:
|
||||
# - 截取屏幕/窗口截图
|
||||
# - 模拟键盘输入和鼠标操作
|
||||
# - 列出运行的应用
|
||||
# - 使用剪贴板
|
||||
```
|
||||
|
||||
### 平台支持
|
||||
| 平台 | 截图 | 键鼠 | 应用管理 |
|
||||
|------|------|------|----------|
|
||||
| macOS | ✅ | ✅ | ✅ |
|
||||
| Windows | ✅ | ✅ | ✅ |
|
||||
| Linux | ⏳ | ⏳ | ⏳ |
|
||||
|
||||
---
|
||||
|
||||
## 7. Feature Flags 与 GrowthBook
|
||||
|
||||
**PR**: #140 + #153 `feat: enable GrowthBook local gate defaults`
|
||||
**Feature Flags**: `SHOT_STATS`, `PROMPT_CACHE_BREAK_DETECTION`, `TOKEN_BUDGET`
|
||||
|
||||
### 说明
|
||||
本地 GrowthBook gate defaults 机制,绕过远程 feature flag 服务,确保功能在无网络时也可使用。
|
||||
|
||||
### 使用
|
||||
```bash
|
||||
# 通过环境变量启用任意 feature
|
||||
FEATURE_PROACTIVE=1 bun run dev
|
||||
|
||||
# dev/build 模式有各自的默认启用列表
|
||||
# 查看 scripts/dev.ts 中的 DEFAULT_FEATURES
|
||||
```
|
||||
|
||||
### 关键 feature flags
|
||||
| Flag | 说明 |
|
||||
|------|------|
|
||||
| `SHOT_STATS` | API 调用统计 |
|
||||
| `TOKEN_BUDGET` | Token 预算控制 |
|
||||
| `PROMPT_CACHE_BREAK_DETECTION` | Prompt 缓存命中检测 |
|
||||
|
||||
---
|
||||
|
||||
## 8. /ultraplan 高级规划
|
||||
|
||||
**PR**: #156 `feat: enable /ultraplan and harden GrowthBook fallback chain`
|
||||
**Feature Flag**: `ULTRAPLAN`
|
||||
|
||||
### 说明
|
||||
高级多 agent 规划模式。将复杂任务分解为多个阶段,每阶段可分配给不同 agent 并行执行。
|
||||
|
||||
### 使用
|
||||
```
|
||||
/ultraplan 实现一个完整的用户认证系统,包括注册、登录、密码重置、OAuth 集成
|
||||
```
|
||||
|
||||
AI 会生成:
|
||||
1. 任务分解(多阶段)
|
||||
2. 每阶段的 agent 分配
|
||||
3. 依赖关系图
|
||||
4. 并行执行计划
|
||||
|
||||
---
|
||||
|
||||
## 9. Daemon 后台守护
|
||||
|
||||
**PR**: #170 `feat: restore daemon supervisor and remoteControlServer command`
|
||||
**Feature Flag**: `DAEMON`
|
||||
|
||||
### 说明
|
||||
Daemon 模式允许 Claude Code 作为后台长驻进程运行,管理多个 worker。
|
||||
|
||||
### 使用
|
||||
```bash
|
||||
# 启动 daemon
|
||||
claude daemon start
|
||||
|
||||
# 查看状态
|
||||
claude daemon status
|
||||
|
||||
# 停止
|
||||
claude daemon stop
|
||||
|
||||
# 启动远程控制服务器
|
||||
bun run rcs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Pipe IPC 多实例协作
|
||||
|
||||
**PR**: #241 `feat: restore pipe IPC, LAN pipes, monitor tool`
|
||||
**Feature Flag**: `UDS_INBOX`
|
||||
|
||||
### 说明
|
||||
同一台机器上的多个 Claude Code 实例通过 UDS(Unix Domain Socket / Windows Named Pipe)自动发现并协作。首个启动的实例成为 main,后续自动注册为 sub。
|
||||
|
||||
### 使用
|
||||
|
||||
**启动多实例**:
|
||||
```bash
|
||||
# 终端 1
|
||||
bun run dev
|
||||
# → 自动成为 main
|
||||
|
||||
# 终端 2
|
||||
bun run dev
|
||||
# → 自动成为 sub-1,被 main attach
|
||||
```
|
||||
|
||||
**管理实例**:
|
||||
```
|
||||
/pipes — 显示所有实例,Shift+↓ 展开选择面板
|
||||
/pipes select <name> — 选中实例
|
||||
/pipes all — 全选
|
||||
/pipes none — 取消全选
|
||||
/attach <name> — 手动 attach 某实例
|
||||
/detach <name> — 断开连接
|
||||
/send <name> <msg> — 向指定实例发送消息
|
||||
/claim-main — 强制声明为 main
|
||||
/pipe-status — 显示详细状态
|
||||
/peers — 列出所有已发现的 peer
|
||||
```
|
||||
|
||||
**选择面板操作**:
|
||||
1. 按 `Shift+↓` 展开面板
|
||||
2. `↑/↓` 移动光标
|
||||
3. `Space` 选中/取消 pipe
|
||||
4. `Enter` 确认关闭
|
||||
5. `←/→` 切换路由模式(selected pipes ↔ local main)
|
||||
|
||||
**消息广播**:
|
||||
选中 pipe 后,输入的消息自动路由到所有选中的 slave 执行,结果流式回传到 main。
|
||||
|
||||
**权限转发**:
|
||||
slave 执行需要权限的工具时(如 BashTool),权限请求自动转发到 main 的确认队列。
|
||||
|
||||
---
|
||||
|
||||
## 11. LAN Pipes 局域网群控
|
||||
|
||||
**PR**: #241(同上)
|
||||
**Feature Flag**: `LAN_PIPES`
|
||||
|
||||
### 说明
|
||||
在 Pipe IPC 基础上增加 TCP 传输层和 UDP Multicast 发现,实现跨机器零配置协作。
|
||||
|
||||
### 使用
|
||||
|
||||
**局域网多机器**:
|
||||
```bash
|
||||
# 机器 A (192.168.50.22)
|
||||
bun run dev
|
||||
|
||||
# 机器 B (192.168.50.27)
|
||||
bun run dev
|
||||
|
||||
# 两边启动后 3-5 秒自动发现和 attach
|
||||
# /pipes 显示 [LAN] 标记的远端实例
|
||||
```
|
||||
|
||||
**防火墙配置**(每台机器都需要):
|
||||
|
||||
Windows(管理员 PowerShell):
|
||||
```powershell
|
||||
New-NetFirewallRule -DisplayName "CCB LAN Beacon (UDP)" -Direction Inbound -Protocol UDP -LocalPort 7101 -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "CCB LAN Pipes (TCP)" -Direction Inbound -Protocol TCP -LocalPort 1024-65535 -Program (Get-Command bun).Source -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "CCB LAN Beacon Out (UDP)" -Direction Outbound -Protocol UDP -RemotePort 7101 -Action Allow -Profile Private
|
||||
```
|
||||
|
||||
macOS:
|
||||
```bash
|
||||
# 首次运行时系统弹对话框,点"允许"即可
|
||||
```
|
||||
|
||||
Linux:
|
||||
```bash
|
||||
sudo firewall-cmd --zone=trusted --add-port=7101/udp --permanent
|
||||
sudo firewall-cmd --zone=trusted --add-port=1024-65535/tcp --permanent
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
**通知显示格式**:
|
||||
```
|
||||
# 本机 sub
|
||||
Routed to [sub-1]; main can continue other tasks
|
||||
|
||||
# LAN peer
|
||||
Routed to [main] vmwin11/192.168.50.27; main can continue other tasks
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Monitor 后台监控
|
||||
|
||||
**PR**: #241(同上)
|
||||
**Feature Flag**: `MONITOR_TOOL`
|
||||
|
||||
### 说明
|
||||
在后台运行 shell 命令持续监控输出(类似 `watch` 命令)。AI 也可自主调用 MonitorTool。
|
||||
|
||||
### 使用
|
||||
|
||||
**用户命令**:
|
||||
```
|
||||
/monitor tail -f /var/log/syslog
|
||||
/monitor watch -n 5 docker ps
|
||||
/monitor "while true; do curl -s localhost:3000/health; sleep 10; done"
|
||||
```
|
||||
|
||||
**查看监控**:
|
||||
- 按 `Shift+Down` 展开后台任务面板
|
||||
- 查看监控输出和状态
|
||||
|
||||
**Windows 兼容**:
|
||||
`watch -n <sec> <cmd>` 自动转为 PowerShell 循环:
|
||||
```powershell
|
||||
while($true){ <cmd>; Start-Sleep -Seconds <sec> }
|
||||
```
|
||||
|
||||
**AI 调用**:
|
||||
AI 可在对话中自动调用 `MonitorTool` 监控日志、构建输出等。
|
||||
|
||||
---
|
||||
|
||||
## 13. Workflow 工作流脚本
|
||||
|
||||
**PR**: #241(同上)
|
||||
**Feature Flag**: `WORKFLOW_SCRIPTS`
|
||||
|
||||
### 说明
|
||||
执行 `.claude/workflows/` 目录下的用户定义工作流脚本。
|
||||
|
||||
### 使用
|
||||
|
||||
**创建工作流**:
|
||||
```bash
|
||||
mkdir -p .claude/workflows
|
||||
cat > .claude/workflows/deploy.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
echo "Running tests..."
|
||||
bun test
|
||||
echo "Building..."
|
||||
bun run build
|
||||
echo "Deploying..."
|
||||
EOF
|
||||
chmod +x .claude/workflows/deploy.sh
|
||||
```
|
||||
|
||||
**列出可用工作流**:
|
||||
```
|
||||
/workflows
|
||||
```
|
||||
|
||||
**AI 调用**:
|
||||
AI 可通过 `WorkflowTool` 自动执行工作流:
|
||||
```
|
||||
请执行 deploy 工作流
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. Coordinator 多Worker协调
|
||||
|
||||
**PR**: #241(同上)
|
||||
**Feature Flag**: `COORDINATOR_MODE`
|
||||
|
||||
### 说明
|
||||
启用 coordinator 模式后,AI 可自动将任务分配给多个 worker 并行执行。
|
||||
|
||||
### 使用
|
||||
```
|
||||
/coordinator — 切换 coordinator 模式开/关
|
||||
```
|
||||
|
||||
启用后,AI 在处理复杂任务时会:
|
||||
1. 分析任务可并行的部分
|
||||
2. 自动创建 worker 分支
|
||||
3. 分配子任务
|
||||
4. 汇总结果
|
||||
|
||||
---
|
||||
|
||||
## 15. Proactive 自主模式
|
||||
|
||||
**PR**: #241(同上)
|
||||
**Feature Flag**: `PROACTIVE` / `KAIROS`
|
||||
|
||||
### 说明
|
||||
启用后 AI 会主动发起操作(而不仅回应用户输入),例如自动检测文件变更、主动提出优化建议。
|
||||
|
||||
### 使用
|
||||
```
|
||||
/proactive — 切换 proactive 模式开/关
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16. History / Snip 历史管理
|
||||
|
||||
**PR**: #241(同上)
|
||||
**Feature Flag**: `HISTORY_SNIP`
|
||||
|
||||
### 说明
|
||||
查看和管理对话历史,支持手动截断以释放上下文窗口空间。
|
||||
|
||||
### 使用
|
||||
```
|
||||
/history — 显示对话历史摘要
|
||||
/force-snip — 强制在当前位置截断历史
|
||||
```
|
||||
|
||||
AI 也可通过 `SnipTool` 自动截断过长的对话:
|
||||
```
|
||||
对话太长了,请帮我截断历史
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17. Fork 子Agent
|
||||
|
||||
**PR**: #241(同上)
|
||||
**Feature Flag**: `FORK_SUBAGENT`
|
||||
|
||||
### 说明
|
||||
在当前对话上下文中 fork 一个独立的子 agent,继承完整会话状态独立执行。
|
||||
|
||||
### 使用
|
||||
```
|
||||
/fork — 基于当前上下文 fork 子 agent
|
||||
```
|
||||
|
||||
子 agent 会:
|
||||
- 继承当前的全部对话历史
|
||||
- 在独立的执行环境中运行
|
||||
- 不影响主会话状态
|
||||
|
||||
---
|
||||
|
||||
## 18. 其他恢复的工具
|
||||
|
||||
以下工具从 stub 恢复为完整实现:
|
||||
|
||||
| 工具 | 说明 | 使用 |
|
||||
|------|------|------|
|
||||
| `SleepTool` | 暂停执行指定时间 | AI 在轮询场景自动调用 |
|
||||
| `WebBrowserTool` | 终端内网页交互 | AI 需要查看网页时调用 |
|
||||
| `SubscribePRTool` | 订阅 GitHub PR 变更 | `/subscribe-pr` 或 AI 调用 |
|
||||
| `PushNotificationTool` | 推送桌面通知 | AI 在长任务完成时调用 |
|
||||
| `CtxInspectTool` | 检查上下文窗口使用 | AI 判断上下文剩余空间 |
|
||||
| `TerminalCaptureTool` | 截取终端屏幕 | AI 需要看终端输出时调用 |
|
||||
| `SendUserFileTool` | 向用户发送文件 | AI 导出文件时调用 |
|
||||
| `REPLTool` | 启动子 REPL 会话 | AI 需要独立交互环境时调用 |
|
||||
| `VerifyPlanExecutionTool` | 验证执行计划完成度 | AI 完成计划后自动验证 |
|
||||
| `SuggestBackgroundPRTool` | 建议创建后台 PR | AI 发现可独立的变更时提议 |
|
||||
| `ListPeersTool` | 列出已发现的 peer | AI 查询多实例状态时调用 |
|
||||
|
||||
---
|
||||
|
||||
## 附录:全部 Feature Flags
|
||||
|
||||
| Flag | 默认 | 说明 |
|
||||
|------|------|------|
|
||||
| `BUDDY` | ✅ dev only | 伴侣系统 |
|
||||
| `BRIDGE_MODE` | ✅ dev only | 远程控制 |
|
||||
| `VOICE_MODE` | ✅ dev+build | 语音模式 |
|
||||
| `CHICAGO_MCP` | ✅ dev+build | Computer Use + Chrome |
|
||||
| `AGENT_TRIGGERS_REMOTE` | ✅ dev+build | 定时任务 |
|
||||
| `SHOT_STATS` | ✅ dev+build | API 统计 |
|
||||
| `TOKEN_BUDGET` | ✅ dev+build | Token 预算 |
|
||||
| `PROMPT_CACHE_BREAK_DETECTION` | ✅ dev+build | 缓存检测 |
|
||||
| `ULTRAPLAN` | ✅ dev+build | 高级规划 |
|
||||
| `DAEMON` | ✅ dev+build | 后台守护 |
|
||||
| `UDS_INBOX` | ✅ dev only | Pipe IPC |
|
||||
| `LAN_PIPES` | ✅ dev only | LAN 群控 |
|
||||
| `MONITOR_TOOL` | ✅ dev+build | 后台监控 |
|
||||
| `WORKFLOW_SCRIPTS` | ✅ dev+build | 工作流脚本 |
|
||||
| `FORK_SUBAGENT` | ✅ dev+build | 子 Agent |
|
||||
| `KAIROS` | ✅ dev+build | Kairos 调度 |
|
||||
| `COORDINATOR_MODE` | ✅ dev+build | 多 Worker |
|
||||
| `HISTORY_SNIP` | ✅ dev+build | 历史管理 |
|
||||
| `CONTEXT_COLLAPSE` | ✅ dev+build | 上下文折叠 |
|
||||
| `ULTRATHINK` | ✅ dev+build | 扩展思考 |
|
||||
| `EXTRACT_MEMORIES` | ✅ dev+build | 自动记忆提取 |
|
||||
| `VERIFICATION_AGENT` | ✅ dev+build | 验证 Agent |
|
||||
| `KAIROS_BRIEF` | ✅ dev+build | Brief 模式 |
|
||||
| `AWAY_SUMMARY` | ✅ dev+build | 离开摘要 |
|
||||
| `ACP` | ✅ dev+build | ACP 协议 |
|
||||
| `LODESTONE` | ✅ dev+build | 深度链接 |
|
||||
| `BUILTIN_EXPLORE_PLAN_AGENTS` | ✅ dev+build | 内置 Explore/Plan agent |
|
||||
| `AGENT_TRIGGERS` | ✅ dev+build | 本地定时任务 |
|
||||
| `BG_SESSIONS` | ✅ dev only | 后台会话 |
|
||||
| `TEMPLATES` | ✅ dev only | 模板系统 |
|
||||
| `TRANSCRIPT_CLASSIFIER` | ✅ dev only | 对话分类 |
|
||||
|
||||
手动启用任意 flag:
|
||||
```bash
|
||||
FEATURE_FLAG_NAME=1 bun run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 附录:PR 列表
|
||||
|
||||
| PR | 日期 | 标题 |
|
||||
|----|------|------|
|
||||
| #60 | 2026-04-02 | feat: enable Remote Control (BRIDGE_MODE) |
|
||||
| #82 | 2026-04-03 | refactor(buddy): align companion system |
|
||||
| #88 | 2026-04-03 | feat: enable /schedule (AGENT_TRIGGERS_REMOTE) |
|
||||
| #89 | 2026-04-03 | feat: built-in status line |
|
||||
| #92 | 2026-04-03 | feat: enable /voice mode |
|
||||
| #93 | 2026-04-03 | feat: enable Chrome MCP |
|
||||
| #98 | 2026-04-03 | feat: enable Computer Use (macOS + Windows + Linux) |
|
||||
| #137 | 2026-04-05 | feat: Computer Use v2 — 跨平台 Executor |
|
||||
| #140 | 2026-04-05 | feat: enable SHOT_STATS, TOKEN_BUDGET |
|
||||
| #153 | 2026-04-06 | feat: enable GrowthBook local gate defaults |
|
||||
| #156 | 2026-04-06 | feat: enable /ultraplan |
|
||||
| #170 | 2026-04-07 | feat: restore daemon supervisor |
|
||||
| #241 | 2026-04-11 | feat: restore pipe IPC, LAN pipes, monitor tool |
|
||||
@@ -1,769 +0,0 @@
|
||||
# `/autofix-pr` 命令实现规格文档
|
||||
|
||||
> **状态**:规划阶段(2026-04-29),等待评审通过后进入实施。
|
||||
> **Worktree**:`E:\Source_code\Claude-code-bast-autofix-pr`,分支 `feat/autofix-pr`,基于 `origin/main` 4f1649e2。
|
||||
> **架构**:R(Remote-via-CCR),完整版(含 stop 子命令、单例锁、subscribePR、in-process teammate、skills 探测)。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
### 1.1 问题
|
||||
|
||||
本仓库(`Claude-code-bast`)是 Anthropic 官方 `@anthropic-ai/claude-code` 的反编译/重构版本。许多远程能力被 stub 化处理 —— `/autofix-pr` 是其中之一:
|
||||
|
||||
```js
|
||||
// src/commands/autofix-pr/index.js(当前 stub)
|
||||
export default { isEnabled: () => false, isHidden: true, name: 'stub' };
|
||||
```
|
||||
|
||||
三个字段共同导致命令在斜杠菜单中完全不可见、不可调起:
|
||||
|
||||
| 字段 | 值 | 效果 |
|
||||
|---|---|---|
|
||||
| `isEnabled` | `() => false` | 注册时被判定不可用 |
|
||||
| `isHidden` | `true` | 即使被列出也被过滤 |
|
||||
| `name` | `'stub'` | 实际注册名是 `'stub'`,输入 `/autofix-pr` 无法匹配 |
|
||||
|
||||
### 1.2 用户场景
|
||||
|
||||
用户在 fork 仓库(`feat/autonomy-lifecycle-upstream` 分支)尝试对上游 `claude-code-best/claude-code#386` 跑 `/autofix-pr 386`,多次报 `git_repository source setup error`。根因:官方派发的远程 session 落在被 MCP 拒绝访问的仓库(`amdosion/claude-code-bast`),权限/可见性问题。
|
||||
|
||||
### 1.3 目标
|
||||
|
||||
| ID | 需求 | 验收 |
|
||||
|---|---|---|
|
||||
| R1 | 命令在斜杠菜单可见可调起 | 输入 `/au` 出现补全 |
|
||||
| R2 | 跨仓库 PR:从本地 fork 触发对上游 PR 的修复 | `/autofix-pr 386` 不报 repo-not-allowed |
|
||||
| R3 | 远端真正完成修复并 push 回 PR 分支 | PR 出现来自远端的新 commit |
|
||||
| R4 | 不破坏现存其他 stub(如 `share`) | 只动 `autofix-pr` |
|
||||
| R5 | TypeScript 严格模式,`bun run typecheck` 零错误 | CI 绿 |
|
||||
| R6 | bridge 可触发(Remote Control 场景) | `bridgeSafe: true` 生效 |
|
||||
| R7 | 支持 stop/off 子命令 | `/autofix-pr stop` 能终止当前监控 |
|
||||
| R8 | 单例锁防止重复派发 | 已监控 PR 时拒绝新启动并提示 |
|
||||
|
||||
---
|
||||
|
||||
## 二、反编译调研结论(来源:`C:\Users\12180\.local\bin\claude.exe`)
|
||||
|
||||
`claude.exe` 是 242MB 的 Bun 原生编译产物(JS 源码 embed 在二进制内)。通过对该文件的字符串提取(`grep -aoE`)反推出完整调用链。
|
||||
|
||||
### 2.1 主入口函数结构
|
||||
|
||||
```js
|
||||
async function entry(input, q, ctx) {
|
||||
const isStop = input === "stop" || input === "off"
|
||||
const args = { freeformPrompt: input }
|
||||
return main(args, q, ctx)
|
||||
}
|
||||
|
||||
async function main(args, q, { signal, onProgress }) {
|
||||
// args 字段:{ prNumber, target, freeformPrompt, repoPath, skills }
|
||||
d("tengu_autofix_pr_started", {
|
||||
action: "start",
|
||||
has_pr_number: String(args.prNumber !== undefined),
|
||||
has_repo_path: String(args.repoPath !== undefined),
|
||||
})
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 `teleportToRemote` 调用签名(黄金证据)
|
||||
|
||||
```ts
|
||||
const session = await teleportToRemote({
|
||||
initialMessage: C, // 给远端的初始消息
|
||||
source: "autofix_pr", // ⚠️ 新字段,本仓库 teleport.tsx 没有
|
||||
branchName: N, // PR 头分支
|
||||
reuseOutcomeBranch: N, // 与 branchName 同 — 远端 push 回原分支
|
||||
title: `Autofix PR: ${owner}/${repo}#${prNumber} (${branch})`,
|
||||
useDefaultEnvironment: true, // ⚠️ 不用 synthetic env(与 ultrareview 不同)
|
||||
signal,
|
||||
githubPr: { owner, repo, number },
|
||||
cwd: repoPath,
|
||||
onBundleFail: (msg) => { /* ... */ },
|
||||
})
|
||||
```
|
||||
|
||||
**与 `ultrareview` 的关键差异**:
|
||||
|
||||
| 字段 | ultrareview | autofix-pr |
|
||||
|---|---|---|
|
||||
| `environmentId` | `env_011111111111111111111113`(synthetic) | 不传 |
|
||||
| `useDefaultEnvironment` | 不传 | `true` |
|
||||
| `useBundle` | 有(branch mode) | 不传(`skipBundle` 隐含于不传 bundle) |
|
||||
| `reuseOutcomeBranch` | 不传 | 传(远端 push 回原 PR 分支) |
|
||||
| `githubPr` | 不传 | 必传 |
|
||||
| `source` | 不传 | `"autofix_pr"` |
|
||||
| `environmentVariables` | `BUGHUNTER_*` 一堆 | 不传 |
|
||||
|
||||
### 2.3 `registerRemoteAgentTask` 调用
|
||||
|
||||
```ts
|
||||
registerRemoteAgentTask({
|
||||
remoteTaskType: "autofix-pr",
|
||||
session: { id: session.id, title: session.title },
|
||||
command,
|
||||
isLongRunning: true, // poll 不消费 result,靠通知周期驱动
|
||||
})
|
||||
```
|
||||
|
||||
### 2.4 子命令解析
|
||||
|
||||
```
|
||||
/autofix-pr <PR#> → 启动监控 + 派 CCR session
|
||||
/autofix-pr stop → 停止当前监控
|
||||
/autofix-pr off → 同 stop
|
||||
/autofix-pr <freeform-prompt> → 自由 prompt 模式(无 PR 号)
|
||||
/autofix-pr <owner>/<repo>#<n> → 跨仓库(覆盖 R2 验收)
|
||||
```
|
||||
|
||||
### 2.5 状态模型
|
||||
|
||||
- **单例锁**:同一时刻只能监控一个 PR。重复启动报:`already monitoring ${repo}#${prNumber}. Run /autofix-pr stop first.`(error_code: `rc_already_monitoring_other`)
|
||||
- **PR 订阅**:调 `kairos.subscribePR(owner, repo, taskId)` —— 依赖 `KAIROS_GITHUB_WEBHOOKS` feature flag(用户已订阅,可用)
|
||||
- **in-process teammate**:注册后台 agent
|
||||
```ts
|
||||
const teammate = {
|
||||
agentId,
|
||||
agentName: "autofix-pr",
|
||||
teamName: "_autofix",
|
||||
color: undefined,
|
||||
planModeRequired: false,
|
||||
parentSessionId,
|
||||
}
|
||||
```
|
||||
- **Skills 探测**:扫项目里 autofix-related skills(如 `.claude/skills/autofix-*` 或根目录 `AUTOFIX.md`),命中后拼到 prompt:`Run X and Y for custom instructions on how to autofix.`
|
||||
|
||||
### 2.6 Telemetry
|
||||
|
||||
| 事件 | 字段 |
|
||||
|---|---|
|
||||
| `tengu_autofix_pr_started` | `{ action, has_pr_number, has_repo_path }` |
|
||||
| `tengu_autofix_pr_result` | `{ result, error_code? }` |
|
||||
|
||||
`result` 取值:`success_rc` / `failed` / `cancelled`
|
||||
|
||||
`error_code` 取值:
|
||||
|
||||
| code | 含义 |
|
||||
|---|---|
|
||||
| `rc_already_monitoring_other` | 已在监控其他 PR |
|
||||
| `session_create_failed` | teleport 失败 |
|
||||
| `exception` | 未捕获异常 |
|
||||
|
||||
### 2.7 错误返回结构
|
||||
|
||||
```ts
|
||||
function errorResult(message: string, code: string) {
|
||||
d("tengu_autofix_pr_result", { result: "failed", error_code: code })
|
||||
return {
|
||||
kind: "error",
|
||||
message: `Autofix PR failed: ${message}`,
|
||||
code,
|
||||
}
|
||||
}
|
||||
|
||||
function cancelledResult() {
|
||||
d("tengu_autofix_pr_result", { result: "cancelled" })
|
||||
return { kind: "cancelled" }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、本仓库现有基础设施盘点
|
||||
|
||||
下表列出实现 `/autofix-pr` 时**直接复用**的现成能力(已确认完整可用):
|
||||
|
||||
| 能力 | 文件 | 角色 |
|
||||
|---|---|---|
|
||||
| `teleportToRemote` | `src/utils/teleport.tsx:947` | 派 CCR 远端 session(缺 `source` 字段,需补) |
|
||||
| `registerRemoteAgentTask` | `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx:526` | 注册 long-running 任务到 store |
|
||||
| `checkRemoteAgentEligibility` | `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx:185` | 前置鉴权检查 |
|
||||
| `getRemoteTaskSessionUrl` | `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx` | 生成 session 跟踪 URL |
|
||||
| `formatPreconditionError` | `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx` | 错误文案格式化 |
|
||||
| `REMOTE_TASK_TYPES` | `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx:103` | 已含 `'autofix-pr'` 类型 |
|
||||
| `AutofixPrRemoteTaskMetadata` | `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx:112` | `{ owner, repo, prNumber }` schema |
|
||||
| `RemoteSessionProgress` | `src/components/tasks/RemoteSessionProgress.tsx` | 进度面板 UI(已认 autofix-pr 类型) |
|
||||
| `detectCurrentRepositoryWithHost` | `src/utils/detectRepository.ts` | 解析 owner/repo |
|
||||
| `getDefaultBranch` / `gitExe` | `src/utils/git.ts` | git 工具 |
|
||||
| `feature('FLAG')` | `bun:bundle` | feature flag 系统(CLAUDE.md 红线:只能在 if/三元条件位置直接调用) |
|
||||
|
||||
### 模板答案文件
|
||||
|
||||
以下三个文件已确认完整工作,是本次实现的"参考答案":
|
||||
|
||||
- `src/commands/review/reviewRemote.ts`(317 行)—— **主模板**,照抄改造
|
||||
- `src/commands/ultraplan.tsx`(525 行)
|
||||
- `src/commands/review/ultrareviewCommand.tsx`(89 行)
|
||||
|
||||
---
|
||||
|
||||
## 四、命令对象规格
|
||||
|
||||
### 4.1 `Command` 类型选择
|
||||
|
||||
`Command` 类型定义在 `src/types/command.ts`,三态之一:`PromptCommand` / `LocalCommand` / `LocalJSXCommand`。
|
||||
|
||||
**选 `LocalJSXCommand`**,因为:
|
||||
- 需要 spawn 远端 session 并显示进度面板
|
||||
- 兄弟命令 `ultraplan` / `ultrareview` 都用 local-jsx
|
||||
- 接口签名:`call(onDone, context, args) => Promise<React.ReactNode>`
|
||||
|
||||
### 4.2 `index.ts` 完整形状
|
||||
|
||||
```ts
|
||||
import { feature } from 'bun:bundle'
|
||||
import type { Command } from '../../types/command.js'
|
||||
|
||||
const autofixPr: Command = {
|
||||
type: 'local-jsx',
|
||||
name: 'autofix-pr', // 关键:必须是 'autofix-pr' 不是 'stub'
|
||||
description: 'Auto-fix CI failures on a pull request',
|
||||
argumentHint: '<pr-number> | stop | <owner>/<repo>#<n>',
|
||||
isEnabled: () => feature('AUTOFIX_PR'),
|
||||
isHidden: false,
|
||||
bridgeSafe: true,
|
||||
getBridgeInvocationError: (args) => {
|
||||
const trimmed = args.trim()
|
||||
if (!trimmed) return 'PR number required, e.g. /autofix-pr 386'
|
||||
if (trimmed === 'stop' || trimmed === 'off') return undefined
|
||||
if (/^\d+$/.test(trimmed)) return undefined
|
||||
if (/^[\w.-]+\/[\w.-]+#\d+$/.test(trimmed)) return undefined
|
||||
return 'Invalid args. Use /autofix-pr <pr-number> | stop | <owner>/<repo>#<n>'
|
||||
},
|
||||
load: async () => {
|
||||
const m = await import('./launchAutofixPr.js')
|
||||
return { call: m.callAutofixPr }
|
||||
},
|
||||
}
|
||||
|
||||
export default autofixPr
|
||||
```
|
||||
|
||||
### 4.3 参数解析规则
|
||||
|
||||
```
|
||||
^stop$ | ^off$ → { action: 'stop' }
|
||||
^\d+$ → { action: 'start', prNumber, owner: <git>, repo: <git> }
|
||||
^([\w.-]+)/([\w.-]+)#(\d+)$ → { action: 'start', prNumber, owner, repo }
|
||||
其他 → { action: 'start', freeformPrompt: <input> }
|
||||
空字符串 → 错误
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、文件结构
|
||||
|
||||
```
|
||||
src/commands/autofix-pr/
|
||||
├── index.ts # 命令对象(替换 index.js)
|
||||
├── launchAutofixPr.ts # 主流程
|
||||
├── parseArgs.ts # 参数解析(独立便于测试)
|
||||
├── monitorState.ts # 单例锁
|
||||
├── inProcessAgent.ts # 后台 teammate
|
||||
├── skillDetect.ts # 项目 skills 探测
|
||||
└── __tests__/
|
||||
├── parseArgs.test.ts
|
||||
├── monitorState.test.ts
|
||||
├── launchAutofixPr.test.ts
|
||||
└── index.test.ts # bridge invocation error 测试
|
||||
```
|
||||
|
||||
**删除**:原 `index.js`、`index.d.ts`(合并进 `index.ts`)。
|
||||
|
||||
**修改**:
|
||||
- `scripts/defines.ts` —— 加 `AUTOFIX_PR` flag
|
||||
- `scripts/dev.ts` —— dev 默认开启
|
||||
- `src/utils/teleport.tsx` —— `teleportToRemote` 选项加 `source?: string` 字段并透传
|
||||
- `src/commands.ts` —— **不动**(import 路径 `'./commands/autofix-pr/index.js'` 在 ESM/Bun 下会自动解析到 `.ts`)
|
||||
|
||||
---
|
||||
|
||||
## 六、模块详细规格
|
||||
|
||||
### 6.1 `parseArgs.ts`
|
||||
|
||||
```ts
|
||||
export type ParsedArgs =
|
||||
| { action: 'stop' }
|
||||
| { action: 'start'; prNumber: number; owner?: string; repo?: string }
|
||||
| { action: 'freeform'; prompt: string }
|
||||
| { action: 'invalid'; reason: string }
|
||||
|
||||
export function parseAutofixArgs(raw: string): ParsedArgs {
|
||||
const trimmed = raw.trim()
|
||||
if (!trimmed) return { action: 'invalid', reason: 'empty' }
|
||||
if (trimmed === 'stop' || trimmed === 'off') return { action: 'stop' }
|
||||
if (/^\d+$/.test(trimmed)) {
|
||||
return { action: 'start', prNumber: parseInt(trimmed, 10) }
|
||||
}
|
||||
const cross = trimmed.match(/^([\w.-]+)\/([\w.-]+)#(\d+)$/)
|
||||
if (cross) {
|
||||
return {
|
||||
action: 'start',
|
||||
owner: cross[1],
|
||||
repo: cross[2],
|
||||
prNumber: parseInt(cross[3], 10),
|
||||
}
|
||||
}
|
||||
return { action: 'freeform', prompt: trimmed }
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 `monitorState.ts`
|
||||
|
||||
```ts
|
||||
import type { UUID } from 'crypto'
|
||||
|
||||
type MonitorState = {
|
||||
taskId: UUID
|
||||
owner: string
|
||||
repo: string
|
||||
prNumber: number
|
||||
abortController: AbortController
|
||||
startedAt: number
|
||||
}
|
||||
|
||||
let active: MonitorState | null = null
|
||||
|
||||
export function getActiveMonitor(): Readonly<MonitorState> | null {
|
||||
return active
|
||||
}
|
||||
|
||||
export function setActiveMonitor(state: MonitorState): void {
|
||||
if (active) throw new Error(`Monitor already active: ${active.repo}#${active.prNumber}`)
|
||||
active = state
|
||||
}
|
||||
|
||||
export function clearActiveMonitor(): void {
|
||||
if (active) {
|
||||
active.abortController.abort()
|
||||
active = null
|
||||
}
|
||||
}
|
||||
|
||||
export function isMonitoring(owner: string, repo: string, prNumber: number): boolean {
|
||||
return active?.owner === owner && active?.repo === repo && active?.prNumber === prNumber
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 `inProcessAgent.ts`
|
||||
|
||||
仿官方 `xd9` 函数:
|
||||
|
||||
```ts
|
||||
import { randomUUID, type UUID } from 'crypto'
|
||||
import { getCurrentSessionId } from '../../bootstrap/state.js'
|
||||
|
||||
export type AutofixTeammate = {
|
||||
agentId: UUID
|
||||
agentName: 'autofix-pr'
|
||||
teamName: '_autofix'
|
||||
color: undefined
|
||||
planModeRequired: false
|
||||
parentSessionId: UUID
|
||||
abortController: AbortController
|
||||
taskId: UUID
|
||||
}
|
||||
|
||||
export function createAutofixTeammate(
|
||||
initialMessage: string,
|
||||
target: string,
|
||||
): AutofixTeammate {
|
||||
return {
|
||||
agentId: randomUUID(),
|
||||
agentName: 'autofix-pr',
|
||||
teamName: '_autofix',
|
||||
color: undefined,
|
||||
planModeRequired: false,
|
||||
parentSessionId: getCurrentSessionId(),
|
||||
abortController: new AbortController(),
|
||||
taskId: randomUUID(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 `skillDetect.ts`
|
||||
|
||||
```ts
|
||||
import { existsSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
export function detectAutofixSkills(cwd: string): string[] {
|
||||
const candidates = [
|
||||
'AUTOFIX.md',
|
||||
'.claude/skills/autofix.md',
|
||||
'.claude/skills/autofix-pr/SKILL.md',
|
||||
]
|
||||
return candidates.filter(rel => existsSync(join(cwd, rel)))
|
||||
}
|
||||
|
||||
export function formatSkillsHint(skills: string[]): string {
|
||||
if (skills.length === 0) return ''
|
||||
return ` Run ${skills.join(' and ')} for custom instructions on how to autofix.`
|
||||
}
|
||||
```
|
||||
|
||||
### 6.5 `launchAutofixPr.ts`
|
||||
|
||||
主流程伪代码(约 250 行):
|
||||
|
||||
```ts
|
||||
import type { LocalJSXCommandCall } from '../../types/command.js'
|
||||
import { parseAutofixArgs } from './parseArgs.js'
|
||||
import { getActiveMonitor, setActiveMonitor, clearActiveMonitor, isMonitoring } from './monitorState.js'
|
||||
import { createAutofixTeammate } from './inProcessAgent.js'
|
||||
import { detectAutofixSkills, formatSkillsHint } from './skillDetect.js'
|
||||
import { teleportToRemote } from '../../utils/teleport.js'
|
||||
import { checkRemoteAgentEligibility, registerRemoteAgentTask, getRemoteTaskSessionUrl } from '../../tasks/RemoteAgentTask/RemoteAgentTask.js'
|
||||
import { detectCurrentRepositoryWithHost } from '../../utils/detectRepository.js'
|
||||
import { logEvent } from '../../services/analytics/index.js'
|
||||
|
||||
export const callAutofixPr: LocalJSXCommandCall = async (onDone, context, args) => {
|
||||
const parsed = parseAutofixArgs(args)
|
||||
|
||||
// 1. stop 子命令
|
||||
if (parsed.action === 'stop') {
|
||||
const m = getActiveMonitor()
|
||||
if (!m) {
|
||||
onDone('No active autofix monitor.', { display: 'system' })
|
||||
return null
|
||||
}
|
||||
clearActiveMonitor()
|
||||
onDone(`Stopped monitoring ${m.repo}#${m.prNumber}.`, { display: 'system' })
|
||||
return null
|
||||
}
|
||||
|
||||
// 2. invalid
|
||||
if (parsed.action === 'invalid') {
|
||||
return errorView(`Invalid args: ${parsed.reason}`)
|
||||
}
|
||||
|
||||
// 3. freeform — 暂不支持,提示用户
|
||||
if (parsed.action === 'freeform') {
|
||||
return errorView('Freeform prompt mode not yet supported. Use /autofix-pr <pr-number>.')
|
||||
}
|
||||
|
||||
// 4. start
|
||||
logEvent('tengu_autofix_pr_started', {
|
||||
action: 'start',
|
||||
has_pr_number: 'true',
|
||||
has_repo_path: String(!!process.cwd()),
|
||||
})
|
||||
|
||||
// 4.1 解析 owner/repo
|
||||
let owner = parsed.owner
|
||||
let repo = parsed.repo
|
||||
if (!owner || !repo) {
|
||||
const detected = await detectCurrentRepositoryWithHost()
|
||||
if (!detected || detected.host !== 'github.com') {
|
||||
return errorResult('Cannot detect GitHub repo from current directory.', 'session_create_failed')
|
||||
}
|
||||
owner = detected.owner
|
||||
repo = detected.name
|
||||
}
|
||||
|
||||
// 4.2 单例锁
|
||||
if (isMonitoring(owner, repo, parsed.prNumber)) {
|
||||
return errorResult(`already monitoring ${repo}#${parsed.prNumber} in background`, 'success_rc')
|
||||
}
|
||||
if (getActiveMonitor()) {
|
||||
const m = getActiveMonitor()!
|
||||
return errorResult(
|
||||
`already monitoring ${m.repo}#${m.prNumber}. Run /autofix-pr stop first.`,
|
||||
'rc_already_monitoring_other',
|
||||
)
|
||||
}
|
||||
|
||||
// 4.3 资格检查
|
||||
const eligibility = await checkRemoteAgentEligibility()
|
||||
if (!eligibility.eligible) {
|
||||
return errorResult('Remote agent not available.', 'session_create_failed')
|
||||
}
|
||||
|
||||
// 4.4 探测 skills
|
||||
const skills = detectAutofixSkills(process.cwd())
|
||||
const skillsHint = formatSkillsHint(skills)
|
||||
|
||||
// 4.5 拼初始消息
|
||||
const target = `${owner}/${repo}#${parsed.prNumber}`
|
||||
const branchName = `refs/pull/${parsed.prNumber}/head`
|
||||
const initialMessage = `Auto-fix failing CI checks on PR #${parsed.prNumber} in ${owner}/${repo}.${skillsHint}`
|
||||
|
||||
// 4.6 创建 in-process teammate
|
||||
const teammate = createAutofixTeammate(initialMessage, target)
|
||||
|
||||
// 4.7 调 teleport
|
||||
let bundleFailMsg: string | undefined
|
||||
const session = await teleportToRemote({
|
||||
initialMessage,
|
||||
source: 'autofix_pr',
|
||||
branchName,
|
||||
reuseOutcomeBranch: branchName,
|
||||
title: `Autofix PR: ${target} (${branchName})`,
|
||||
useDefaultEnvironment: true,
|
||||
signal: teammate.abortController.signal,
|
||||
githubPr: { owner, repo, number: parsed.prNumber },
|
||||
cwd: process.cwd(),
|
||||
onBundleFail: (msg) => { bundleFailMsg = msg },
|
||||
})
|
||||
|
||||
if (!session) {
|
||||
return errorResult(bundleFailMsg ?? 'remote session creation failed.', 'session_create_failed')
|
||||
}
|
||||
|
||||
// 4.8 注册任务到 store
|
||||
registerRemoteAgentTask({
|
||||
remoteTaskType: 'autofix-pr',
|
||||
session,
|
||||
command: `/autofix-pr ${parsed.prNumber}`,
|
||||
context,
|
||||
})
|
||||
|
||||
// 4.9 设置单例锁
|
||||
setActiveMonitor({
|
||||
taskId: teammate.taskId,
|
||||
owner,
|
||||
repo,
|
||||
prNumber: parsed.prNumber,
|
||||
abortController: teammate.abortController,
|
||||
startedAt: Date.now(),
|
||||
})
|
||||
|
||||
// 4.10 PR webhooks 订阅(feature-gated)
|
||||
if (feature('KAIROS_GITHUB_WEBHOOKS')) {
|
||||
await kairosSubscribePR(owner, repo, teammate.taskId).catch(() => {/* non-fatal */})
|
||||
}
|
||||
|
||||
// 4.11 返回 JSX 进度面板
|
||||
const sessionUrl = getRemoteTaskSessionUrl(session.id)
|
||||
logEvent('tengu_autofix_pr_launched', { target })
|
||||
onDone(
|
||||
`Autofix launched for ${target}. Track: ${sessionUrl}`,
|
||||
{ display: 'system' },
|
||||
)
|
||||
return null // 进度面板由 RemoteAgentTask 自动渲染
|
||||
}
|
||||
|
||||
function errorResult(message: string, code: string) {
|
||||
logEvent('tengu_autofix_pr_result', { result: 'failed', error_code: code })
|
||||
// ... 渲染错误 JSX
|
||||
}
|
||||
```
|
||||
|
||||
> **注意**:`feature('KAIROS_GITHUB_WEBHOOKS')` 必须直接放在 if 条件位置,不能赋值给变量(CLAUDE.md 红线)。
|
||||
|
||||
### 6.6 `teleport.tsx` 补 `source` 字段
|
||||
|
||||
```diff
|
||||
export async function teleportToRemote(options: {
|
||||
initialMessage: string | null
|
||||
branchName?: string
|
||||
title?: string
|
||||
description?: string
|
||||
+ /**
|
||||
+ * Identifies which command/flow originated this teleport. CCR backend
|
||||
+ * uses this for routing/billing/observability. Known values: 'autofix_pr',
|
||||
+ * 'ultrareview', 'ultraplan'. Pass-through field — not interpreted client-side.
|
||||
+ */
|
||||
+ source?: string
|
||||
model?: string
|
||||
permissionMode?: PermissionMode
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
并在内部构造 request 时透传到 session_context(具体字段名按现有 review/ultraplan 调用结构对齐)。
|
||||
|
||||
---
|
||||
|
||||
## 七、Feature Flag
|
||||
|
||||
### 7.1 新增 flag
|
||||
|
||||
`scripts/defines.ts` 已有的 flag 集合中加 `AUTOFIX_PR`。
|
||||
|
||||
### 7.2 启用矩阵
|
||||
|
||||
| 环境 | 是否默认开启 | 说明 |
|
||||
|---|---|---|
|
||||
| dev (`bun run dev`) | 是 | `scripts/dev.ts` 加进默认列表 |
|
||||
| build (production `bun run build`) | 否 | 灰度上线,需要 `FEATURE_AUTOFIX_PR=1` 显式开启 |
|
||||
| 测试 | 按需 | 测试文件通过 mock `bun:bundle` 控制 |
|
||||
|
||||
### 7.3 与官方上游同步策略
|
||||
|
||||
如果上游某天恢复官方实现,本仓库的本地实现优先(项目即 fork):
|
||||
1. 保留 `AUTOFIX_PR` flag 名
|
||||
2. 保留 `RemoteTaskType` 字段不动
|
||||
3. 冲突时合并:吸收上游的 `source` 字段值变更、env var 变更,保留我们的本地 launcher 函数
|
||||
|
||||
---
|
||||
|
||||
## 八、测试计划
|
||||
|
||||
### 8.1 测试文件
|
||||
|
||||
| 文件 | 覆盖目标 | 测试用例数 |
|
||||
|---|---|---|
|
||||
| `parseArgs.test.ts` | 参数解析全分支 | ~10 |
|
||||
| `monitorState.test.ts` | 单例锁正确性 | ~6 |
|
||||
| `launchAutofixPr.test.ts` | 主流程 happy path + 失败路径 | ~12 |
|
||||
| `index.test.ts` | bridge invocation error 校验 | ~5 |
|
||||
|
||||
### 8.2 关键断言
|
||||
|
||||
`launchAutofixPr.test.ts`:
|
||||
|
||||
```ts
|
||||
test('start with PR number teleports with correct args', async () => {
|
||||
// mock teleportToRemote, registerRemoteAgentTask, detectCurrentRepositoryWithHost
|
||||
await callAutofixPr(onDone, context, '386')
|
||||
expect(teleportMock).toHaveBeenCalledWith(expect.objectContaining({
|
||||
source: 'autofix_pr',
|
||||
useDefaultEnvironment: true,
|
||||
githubPr: { owner: 'amDosion', repo: 'claude-code-bast', number: 386 },
|
||||
branchName: 'refs/pull/386/head',
|
||||
reuseOutcomeBranch: 'refs/pull/386/head',
|
||||
}))
|
||||
expect(registerMock).toHaveBeenCalledWith(expect.objectContaining({
|
||||
remoteTaskType: 'autofix-pr',
|
||||
}))
|
||||
})
|
||||
|
||||
test('cross-repo syntax owner/repo#n parses correctly', async () => {
|
||||
await callAutofixPr(onDone, context, 'anthropics/claude-code#999')
|
||||
expect(teleportMock).toHaveBeenCalledWith(expect.objectContaining({
|
||||
githubPr: { owner: 'anthropics', repo: 'claude-code', number: 999 },
|
||||
}))
|
||||
})
|
||||
|
||||
test('singleton lock blocks second start', async () => {
|
||||
await callAutofixPr(onDone, context, '386')
|
||||
const result = await callAutofixPr(onDone, context, '999')
|
||||
expect(extractError(result)).toMatch(/already monitoring.*386.*Run \/autofix-pr stop first/)
|
||||
})
|
||||
|
||||
test('stop clears active monitor', async () => {
|
||||
await callAutofixPr(onDone, context, '386')
|
||||
await callAutofixPr(onDone, context, 'stop')
|
||||
expect(getActiveMonitor()).toBeNull()
|
||||
})
|
||||
```
|
||||
|
||||
### 8.3 Mock 策略
|
||||
|
||||
按本仓库 `tests/mocks/` 共享 mock 习惯:
|
||||
- `tests/mocks/log.ts` 和 `tests/mocks/debug.ts` —— 必 mock
|
||||
- `bun:bundle` —— mock `feature` 返回 `true`
|
||||
- `teleportToRemote` —— 模块级 mock,断言入参
|
||||
- `registerRemoteAgentTask` —— 模块级 mock,断言入参
|
||||
- `detectCurrentRepositoryWithHost` —— mock 返回 `{ owner, name, host }`
|
||||
|
||||
### 8.4 类型检查
|
||||
|
||||
```bash
|
||||
bun run typecheck # 必须零错误
|
||||
bun run test:all # 必须全绿
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、实施步骤(11 步清单)
|
||||
|
||||
```
|
||||
[ ] Step 1 scripts/defines.ts + scripts/dev.ts 加 AUTOFIX_PR flag
|
||||
[ ] Step 2 src/utils/teleport.tsx 加 source?: string 字段(约 5 行)
|
||||
[ ] Step 3 删除 src/commands/autofix-pr/{index.js, index.d.ts}
|
||||
新建 src/commands/autofix-pr/index.ts(约 50 行)
|
||||
[ ] Step 4 新建 src/commands/autofix-pr/parseArgs.ts(约 30 行)
|
||||
[ ] Step 5 新建 src/commands/autofix-pr/monitorState.ts(约 40 行)
|
||||
[ ] Step 6 新建 src/commands/autofix-pr/inProcessAgent.ts(约 60 行)
|
||||
[ ] Step 7 新建 src/commands/autofix-pr/skillDetect.ts(约 30 行)
|
||||
[ ] Step 8 新建 src/commands/autofix-pr/launchAutofixPr.ts(约 250 行)
|
||||
照抄 reviewRemote.ts,按 §2.2 差异表改造
|
||||
[ ] Step 9 新建四份测试文件(约 150 行)
|
||||
[ ] Step 10 bun run typecheck && bun run test:all 全绿
|
||||
[ ] Step 11 dev 模式手测:
|
||||
a. /autofix-pr 386 → 期望出现 RemoteSessionProgress 面板
|
||||
b. /autofix-pr stop → 期望提示已停止
|
||||
c. /autofix-pr anthropics/claude-code#999 → 期望跨仓库
|
||||
d. 第二次 /autofix-pr 386 → 期望被单例锁拒绝
|
||||
[ ] Step 12 commit:feat: implement /autofix-pr command (replace stub)
|
||||
```
|
||||
|
||||
预计工作量:约 600 行新增代码(含测试 150 行)。
|
||||
|
||||
---
|
||||
|
||||
## 十、风险与回退
|
||||
|
||||
| 风险 | 触发场景 | 回退策略 |
|
||||
|---|---|---|
|
||||
| `source` 字段 CCR 后端不识别 | 后端只认特定枚举 | 不传该字段,看是否能跑通;如不行回头看官方 cli.js 是否传了别的字段 |
|
||||
| `subscribePR` API 在本仓库 client 不完整 | KAIROS_GITHUB_WEBHOOKS 客户端代码缺失 | 用 `.catch(() => {})` 容忍失败,订阅是 nice-to-have |
|
||||
| 用户账号无 CCR 权限 | `checkRemoteAgentEligibility` 返回 false | 命令降级到错误文案,不破坏会话 |
|
||||
| 远端能起 session 但不修代码 | env vars 命名错误 | 看 `getRemoteTaskSessionUrl` 给的会话页容器日志,调整 |
|
||||
| PR 在 fork 仓库且 CCR 没访问权 | `git_repository source error` | 命令应在前置检查中识别并提示用户先把 PR 转到主仓 |
|
||||
| 上游恢复官方实现导致冲突 | 上游 sync 时 | 项目是 fork,本地实现优先;冲突手工 merge |
|
||||
|
||||
### 回退命令
|
||||
|
||||
```bash
|
||||
# 完全撤回本次实现
|
||||
git checkout main
|
||||
git worktree remove E:/Source_code/Claude-code-bast-autofix-pr
|
||||
git branch -D feat/autofix-pr
|
||||
```
|
||||
|
||||
`AUTOFIX_PR` flag 默认在 production 关闭,所以即使代码已合入 main,没显式 `FEATURE_AUTOFIX_PR=1` 时不会影响用户。
|
||||
|
||||
---
|
||||
|
||||
## 十一、验收清单
|
||||
|
||||
实施完成后逐项核对:
|
||||
|
||||
- [ ] R1:dev 模式下输入 `/au` 出现 `/autofix-pr` 补全
|
||||
- [ ] R2:`/autofix-pr anthropics/claude-code#999` 不报 repo-not-allowed
|
||||
- [ ] R3:远端 session 跑完后目标 PR 出现新 commit
|
||||
- [ ] R4:其他 stub(`share` 等)依然 hidden
|
||||
- [ ] R5:`bun run typecheck` 零错误
|
||||
- [ ] R6:通过 RC bridge 触发 `/autofix-pr 386` 能跑通
|
||||
- [ ] R7:`/autofix-pr stop` 终止当前监控
|
||||
- [ ] R8:第二次 `/autofix-pr` 不同 PR 时被锁拒绝并提示
|
||||
|
||||
---
|
||||
|
||||
## 十二、附录
|
||||
|
||||
### 附录 A:相关文件路径速查
|
||||
|
||||
| 路径 | 角色 |
|
||||
|---|---|
|
||||
| `E:\Source_code\Claude-code-bast-autofix-pr` | 实施 worktree |
|
||||
| `C:\Users\12180\.local\bin\claude.exe` | 反编译来源(242MB Bun 编译产物) |
|
||||
| `C:\Users\12180\.claude\projects\E--Source-code-Claude-code-bast\memory\project_autofix_pr_implementation.md` | 内存备忘(精简版) |
|
||||
| `src/commands/review/reviewRemote.ts` | 主模板 |
|
||||
| `src/utils/teleport.tsx:947` | `teleportToRemote` 入口 |
|
||||
| `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx:103` | `REMOTE_TASK_TYPES` |
|
||||
| `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx:526` | `registerRemoteAgentTask` |
|
||||
| `src/types/command.ts` | `Command` 类型定义 |
|
||||
|
||||
### 附录 B:未决问题
|
||||
|
||||
| # | 问题 | 当前处理 | 后续 |
|
||||
|---|---|---|---|
|
||||
| Q1 | `source` 字段在 CCR backend 是否被解析 | 暂传 `'autofix_pr'`,按官方做法 | 端到端测试时观察远端日志 |
|
||||
| Q2 | `subscribePR` 的 client SDK 在本仓库是否完整 | `try/catch` 容忍失败 | Step 11 手测时单独验证 |
|
||||
| Q3 | freeform prompt 模式是否实现 | 暂报"not supported" | 第二期再加 |
|
||||
|
||||
---
|
||||
|
||||
## 十三、变更日志
|
||||
|
||||
| 日期 | 作者 | 变更 |
|
||||
|---|---|---|
|
||||
| 2026-04-29 | Claude Opus 4.7 | 初始规格文档创建(基于 claude.exe 反编译 + 仓库现有基础设施盘点) |
|
||||
@@ -1,225 +0,0 @@
|
||||
# Background Agent Selector — 底部统一后台 Agent 切换器
|
||||
|
||||
> Feature Flag: 无(直接启用)
|
||||
> 实现状态:完整可用
|
||||
> 依赖:`viewingAgentTaskId` / `enterTeammateView` / `exitTeammateView` 已有机制
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
Background Agent Selector 是渲染在 PromptInput 下方的常驻状态条,列出当前所有 **backgrounded 的 local_agent 任务**(包括 `/fork` 派生的 fork agent 和 Task/AgentTool 调用 `run_in_background: true` 派生的子 agent)。用户可以用 ↑/↓ 方向键在 `main` 和各 agent 之间切换焦点,按 Enter 把 REPL 主视图替换为所选 agent 的实时 transcript,再按 Enter 选中 `main` 即可回到主对话。
|
||||
|
||||
整个机制完全复用官方已有的 teammate transcript 查看基础设施,不引入新的视图层 / 数据流,仅新增一条 footer pill 类型。
|
||||
|
||||
### 核心特性
|
||||
|
||||
- **统一入口**:`/fork`、Task 派生的 subagent、所有 `run_in_background: true` 的 agent 都在同一栏显示
|
||||
- **就地切换**:prompt 为空时按 ↓ 溢出进入底部 selector,↑↓ 选中某行,Enter 即切主视图
|
||||
- **实时状态**:每行显示 agent 类型 + 描述 + 运行时长 + 已消耗 token;running 时圆点为绿色
|
||||
- **Keep-alive 视图**:agent 完成后在 `evictAfter` grace 窗口内保留一段时间,用户可回看
|
||||
- **零界面侵入**:tasks 数为 0 时 selector 完全不渲染,不占屏幕高度
|
||||
- **与旧 Dialog 共存**:Shift+↓ 打开的 `BackgroundTasksDialog` 原有行为保留,selector 只作为展示 + 快捷切换
|
||||
|
||||
## 二、用户交互
|
||||
|
||||
### 触发方式
|
||||
|
||||
有任何 background agent 时,selector 自动出现在 `bypass permissions on` 行下方:
|
||||
|
||||
```
|
||||
claude-code | Opus 4.7 (1M context) | ctx:4%
|
||||
▶▶ bypass permissions on (shift+tab to cycle)
|
||||
|
||||
○ main ↑/↓ to select · Enter to view
|
||||
● Explore Research src/hooks 23s · ↓ 10.9k tokens
|
||||
○ Explore Research src/components 22s · ↓ 9.5k tokens
|
||||
○ Explore Research src/utils 21s · ↓ 13.6k tokens
|
||||
```
|
||||
|
||||
### 键盘路由
|
||||
|
||||
| 位置 / 状态 | 按键 | 行为 |
|
||||
|---|---|---|
|
||||
| PromptInput 非空 | ↑↓ | 光标移动 / 翻历史(不变) |
|
||||
| PromptInput 空 + 历史底部 | ↓ | 焦点下放到 selector,高亮到 `● main` |
|
||||
| Selector 聚焦(`footerSelection === 'bg_agent'`) | ↓ | 高亮下移,-1 → 0 → ... → N-1 |
|
||||
| Selector 聚焦 | ↑ | 高亮上移;在 `main` 再 ↑ → 焦点回 PromptInput |
|
||||
| Selector 聚焦 | Enter | `-1` → `exitTeammateView`;`>=0` → `enterTeammateView(agentId)`。焦点保留在 pill |
|
||||
| Selector 聚焦 | Esc | `footer:clearSelection`,焦点回 PromptInput |
|
||||
|
||||
### 视觉规则
|
||||
|
||||
- `● main` / `● <agent>`:当前被**查看**(viewingAgentTaskId 指向)或被**光标聚焦**(pill focused 时以光标为准)的一行
|
||||
- running 状态的 agent:圆点渲染为 `success` 色(绿色),与 `BackgroundTasksDialog` 状态语义对齐
|
||||
- 右上角 hint 随状态变化:
|
||||
- pill 聚焦:`↑/↓ to select · Enter to view`
|
||||
- 已选中 running agent:`shift+↓ to manage · x to stop`
|
||||
- 已选中 terminal agent:`shift+↓ to manage · x to clear`
|
||||
- 未选中任何 agent:`shift+↓ to manage background agents`
|
||||
|
||||
## 三、实现架构
|
||||
|
||||
### 3.1 数据层:`useBackgroundAgentTasks`
|
||||
|
||||
文件:`src/hooks/useBackgroundAgentTasks.ts`
|
||||
|
||||
封装对 `useAppState(s => s.tasks)` 的过滤:
|
||||
|
||||
```ts
|
||||
export function useBackgroundAgentTasks(): LocalAgentTaskState[] {
|
||||
const tasks = useAppState(s => s.tasks)
|
||||
return useMemo(() => {
|
||||
const now = Date.now()
|
||||
return Object.values(tasks)
|
||||
.filter(isLocalAgentTask)
|
||||
.filter(t => t.agentType !== 'main-session')
|
||||
.filter(t => t.isBackgrounded !== false)
|
||||
.filter(t => t.evictAfter === undefined || t.evictAfter > now)
|
||||
.sort((a, b) => a.startTime - b.startTime)
|
||||
}, [tasks])
|
||||
}
|
||||
```
|
||||
|
||||
`/fork` 和 `AgentTool` 的 `run_in_background: true` 底层都走 `registerAsyncAgent → runAsyncAgentLifecycle`,最终写入同一个 `appState.tasks` Map;此 hook 是唯一数据源,Selector 和 PromptInput 的 `bgAgentList` 都消费它。
|
||||
|
||||
### 3.2 状态层:新增两个字段
|
||||
|
||||
文件:`src/state/AppStateStore.ts`
|
||||
|
||||
```ts
|
||||
export type FooterItem =
|
||||
| 'tasks' | 'tmux' | 'bagel' | 'teams' | 'bridge' | 'companion'
|
||||
| 'bg_agent' // ← 新增
|
||||
|
||||
export type AppState = DeepImmutable<{
|
||||
// ...
|
||||
selectedBgAgentIndex: number // -1 = main, 0..N-1 = 选中的 agent
|
||||
}>
|
||||
```
|
||||
|
||||
- `'bg_agent'` 作为 `FooterItem` 加入 footer pill 体系,享受既有的 `footer:up` / `footer:down` / `footer:openSelected` keybinding 路由
|
||||
- `selectedBgAgentIndex` 记录 selector 的光标位置,与 `viewingAgentTaskId`("正在看什么")独立;它不可从 `viewingAgentTaskId` 派生——Enter 后光标留在 pill 继续导航,查看目标才变
|
||||
|
||||
### 3.3 键盘路由:PromptInput footer pill 分支
|
||||
|
||||
文件:`src/components/PromptInput/PromptInput.tsx`
|
||||
|
||||
1. **`bg_agent` 进入 footerItems[0]**:保证 prompt ↓ 溢出时(`handleHistoryDown` → `selectFooterItem(footerItems[0])`)直接进入 selector,而不是 `tasks` 等其他 pill
|
||||
2. **`footer:up` 分支**:`bgAgentSelected` 时 `selectedBgAgentIndex > -1` 则递减;在 -1 → `selectFooterItem(null)` 退出 pill
|
||||
3. **`footer:down` 分支**:`selectedBgAgentIndex < bgAgentList.length - 1` 则递增,到底 clamp
|
||||
4. **`footer:openSelected` 分支**:index === -1 → `exitTeammateView`;否则 `enterTeammateView(bgAgentList[i].agentId)`。**不清理 pill 焦点**,光标留在 selector 上继续导航
|
||||
5. **`selectFooterItem('bg_agent')`**:入 pill 时重置 `selectedBgAgentIndex = -1`(光标落到 `main`)
|
||||
|
||||
### 3.4 渲染层:`BackgroundAgentSelector`
|
||||
|
||||
文件:`src/components/tasks/BackgroundAgentSelector.tsx`
|
||||
|
||||
纯展示组件,不订阅键盘:
|
||||
|
||||
```tsx
|
||||
const tasks = useBackgroundAgentTasks()
|
||||
const viewingId = useAppState(s => s.viewingAgentTaskId)
|
||||
const footerSelection = useAppState(s => s.footerSelection)
|
||||
const selectedBgIndex = useAppState(s => s.selectedBgAgentIndex)
|
||||
|
||||
if (tasks.length === 0) return null
|
||||
|
||||
const pillFocused = footerSelection === 'bg_agent'
|
||||
const highlightedId = pillFocused
|
||||
? (selectedBgIndex === -1 ? null : tasks[selectedBgIndex]?.agentId ?? null)
|
||||
: (viewingId ?? null)
|
||||
```
|
||||
|
||||
**高亮派生规则**:pill 聚焦 → 跟 `selectedBgAgentIndex`;未聚焦 → 镜像 `viewingAgentTaskId`。这样当用户通过 Shift+↓ Dialog 或 `enterTeammateView` 其它途径切换视图时,selector 也会正确反映。
|
||||
|
||||
### 3.5 主视图切换:复用 `viewingAgentTaskId`
|
||||
|
||||
REPL.tsx 主体仍复用原有查看逻辑:
|
||||
|
||||
```ts
|
||||
const viewedTask = viewingAgentTaskId ? tasks[viewingAgentTaskId] : undefined
|
||||
const viewedAgentTask = ... (isLocalAgentTask(viewedTask) ? viewedTask : undefined)
|
||||
const displayedMessages = viewedAgentTask ? displayedAgentMessages : messages
|
||||
```
|
||||
|
||||
当 `enterTeammateView(agentId)` 把 `viewingAgentTaskId` 设成某个 local_agent 的 id:
|
||||
|
||||
- `viewedAgentTask` 解析成该 agent
|
||||
- `displayedMessages` 切换到 agent 的 messages
|
||||
- 消息列表、spinner、unseen divider 等一整套组件自动用 agent transcript 重渲染
|
||||
- 主对话流被"暂停"(并非销毁,回到 `main` 时仍在原处)
|
||||
|
||||
`enterTeammateView` 同步负责:设 `retain: true` 阻止 eviction、清 `evictAfter`、触发 disk bootstrap 从 `agent-<id>.jsonl` 加载完整 transcript 到 `task.messages`。
|
||||
|
||||
#### Fork agent prompt 归一化
|
||||
|
||||
`/fork` agent 的 transcript 和普通 subagent 不同:它继承 main agent 的上下文,真实初始消息形态是:
|
||||
|
||||
```text
|
||||
...parent messages
|
||||
assistant([...tool_use])
|
||||
user([tool_result..., text("<fork-boilerplate>...Your directive: <prompt>")])
|
||||
...fork live messages
|
||||
```
|
||||
|
||||
这里的 prompt 文本混在 `[tool_result..., text]` 多 block user message 里。消息渲染管线会优先把这条 user message 当作 tool-result plumbing 来处理,导致 `<fork-boilerplate>` 里的用户 prompt 不稳定可见。为保证切换到 fork agent 时总能看到用户发起的 fork prompt,REPL.tsx 对 fork 视图做一次展示层归一化:
|
||||
|
||||
1. 仅当 `viewedAgentTask.agentType === 'fork'` 时启用,不影响普通 Explore / Task subagent。
|
||||
2. 从原始 messages 中识别包含 `<fork-boilerplate>` 的 carrier message。
|
||||
3. 剥离 carrier message 里的 boilerplate text block,但保留 `tool_result` blocks,避免破坏父 assistant `tool_use` 的承接关系。
|
||||
4. 强制插入一条独立 `createUserMessage({ content: viewedAgentTask.prompt })` 作为可见用户 prompt。
|
||||
5. 插入位置优先为 boilerplate carrier 后;如果 sidechain bootstrap 还没读到 carrier,则插到最后一条 inherited `assistant tool_use` 后面,确保 prompt 接在 main 上下文之后,而不是跑到视图顶部。
|
||||
|
||||
这个归一化只影响 UI 展示用的 `displayedAgentMessages`,不回写 `task.messages`,也不改变发送给模型的 fork transcript。
|
||||
|
||||
### 3.6 生命周期
|
||||
|
||||
完全复用官方既有机制:
|
||||
|
||||
- **运行中**:`isBackgroundTask()` 谓词为真,selector 列出
|
||||
- **完成 / 失败 / 中止**:`completeAgentTask` / `failAgentTask` / `killAsyncAgent` 设 `status` 为 terminal
|
||||
- **回访后退出**:`exitTeammateView` 调 `release(task)`——清 `retain`、清 `messages`、terminal 状态下设 `evictAfter = now + PANEL_GRACE_MS (30s)`
|
||||
- **evictAfter 过期**:`useBackgroundAgentTasks` 过滤时自然剔除,selector 行消失
|
||||
- **手动清除**:`stopOrDismissAgent(taskId)` 设 `evictAfter = 0`,立即消失
|
||||
|
||||
## 四、设计决策
|
||||
|
||||
1. **数据源单一**:`useBackgroundAgentTasks` 是唯一过滤点,PromptInput 也复用,避免过滤条件散落
|
||||
2. **pill 聚焦保留**:Enter 切视图后不松焦,让 ↑↓ 连续导航,贴近官方体验
|
||||
3. **`bg_agent` 放 footerItems[0]**:确保 ↓ 溢出直接进入 selector 而非其它 pill
|
||||
4. **selector 不订阅键盘**:所有按键路由集中在 PromptInput 的 `footer:*` 分支,避免 selector 组件和 PromptInput 双重 `useInput` 的冲突
|
||||
5. **`selectedBgAgentIndex` 存 AppState 而非局部 state**:selector 和 PromptInput 分别在两棵不同子树,需要全局字段协调;该值不能从 `viewingAgentTaskId` 派生
|
||||
6. **与 `BackgroundTasksDialog` 共存**:Shift+↓ 行为完全不变,selector 是补充快捷入口;Dialog 仍管 shell / workflow / monitor_mcp 等 selector 不显示的 task 类型
|
||||
7. **fork prompt 展示层兜底**:fork prompt 不依赖 boilerplate 自身渲染,统一在 `displayedAgentMessages` 中合成独立用户消息;普通 subagent 不走该分支,避免 prompt 重复
|
||||
|
||||
## 五、关键 API 复用
|
||||
|
||||
| 官方已有能力 | selector 如何使用 |
|
||||
|---|---|
|
||||
| `AppState.tasks` | 单一数据源,无需 file watcher / output JSONL 订阅 |
|
||||
| `registerAsyncAgent` | `/fork` 和 AgentTool 共用,selector 不区分来源 |
|
||||
| `enterTeammateView(id)` | Enter 时调用,负责 retain + disk bootstrap |
|
||||
| `exitTeammateView` | Enter 选中 `main` 时调用 |
|
||||
| `release(task)` + `PANEL_GRACE_MS` | 30s keep-alive,selector 自动生效 |
|
||||
| `useElapsedTime` | 每行时长显示,非 running 自动停 interval |
|
||||
| `formatTokens` (`utils/format.ts`) | token 数 1k 缩写 |
|
||||
| `footer:up` / `footer:down` / `footer:openSelected` keybinding | 键盘路由复用 Footer context |
|
||||
|
||||
## 六、文件索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/hooks/useBackgroundAgentTasks.ts` | 数据过滤 hook(backgrounded local_agent + evictAfter 过滤 + startTime 排序) |
|
||||
| `src/components/tasks/BackgroundAgentSelector.tsx` | 底部 selector UI,纯展示 |
|
||||
| `src/components/PromptInput/PromptInput.tsx` | 新增 `'bg_agent'` footer pill + 对应的 `footer:up/down/openSelected` 分支 |
|
||||
| `src/state/AppStateStore.ts` | `FooterItem` 加 `'bg_agent'`;新增 `selectedBgAgentIndex` 字段 |
|
||||
| `src/main.tsx` | `getDefaultAppState` 同步初始化 `selectedBgAgentIndex: -1` |
|
||||
| `src/screens/REPL.tsx` | 在 PromptInput + SessionBackgroundHint 之后挂载 `<BackgroundAgentSelector />`;切换 agent 主视图;对 fork transcript 做 prompt 归一化 |
|
||||
| `src/components/messages/AssistantToolUseMessage.tsx` | 新增 `defaultCollapsed?: boolean` prop,为后续详情视图默认折叠工具块预留 |
|
||||
| `src/components/messages/UserTextMessage.tsx` | 识别 `<fork-boilerplate>`,交给 fork 专用 renderer 处理 |
|
||||
| `src/components/messages/UserForkBoilerplateMessage.tsx` | 将 fork boilerplate text 折叠为纯用户 prompt;作为 transcript 中原位渲染的兼容路径 |
|
||||
|
||||
## 七、已知限制
|
||||
|
||||
- `Date.now()` 在 `useBackgroundAgentTasks` 的 useMemo 里冻结于 `[tasks]` 触发时:若长时间没有新 task 变更事件,某个 terminal agent 的 grace 期过期后不会立即从 selector 消失,要等下一次 tasks 变化才刷新。在典型使用(主对话一直在产生消息)下感知不到,暂不额外加 interval。
|
||||
- Selector 当前不处理 Shell Task / Workflow / Monitor MCP 等类型——这些仍走 `BackgroundTasksDialog`(Shift+↓)管理。
|
||||
- `AssistantToolUseMessage` 的 `defaultCollapsed` prop 目前无调用方传值,保留作为后续"agent 详情视图内工具块默认折叠"扩展点。
|
||||
@@ -1,107 +0,0 @@
|
||||
# BASH_CLASSIFIER — Bash 命令分类器
|
||||
|
||||
> Feature Flag: `FEATURE_BASH_CLASSIFIER=1`
|
||||
> 实现状态:bashClassifier.ts 全部 Stub,yoloClassifier.ts 完整实现可参考
|
||||
> 引用数:45
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
BASH_CLASSIFIER 使用 LLM 对 bash 命令进行意图分类(允许/拒绝/询问),实现自动权限决策。用户不需要逐个审批 bash 命令,分类器根据命令内容和上下文自动判断安全性。
|
||||
|
||||
### 核心特性
|
||||
|
||||
- **LLM 驱动分类**:使用 Opus 模型评估命令安全性
|
||||
- **两阶段分类**:快速阻止/允许 → 深度思考链
|
||||
- **自动审批**:分类器判定安全的命令自动通过
|
||||
- **UI 集成**:权限对话框显示分类器状态和审核选项
|
||||
|
||||
## 二、实现架构
|
||||
|
||||
### 2.1 模块状态
|
||||
|
||||
| 模块 | 文件 | 状态 | 说明 |
|
||||
|------|------|------|------|
|
||||
| Bash 分类器 | `src/utils/permissions/bashClassifier.ts` | **Stub** | 所有函数返回空操作。注释:"ANT-ONLY" |
|
||||
| YOLO 分类器 | `src/utils/permissions/yoloClassifier.ts` | **完整** | 1496 行,两阶段 XML 分类器 |
|
||||
| 审批信号 | `src/utils/classifierApprovals.ts` | **完整** | Map + 信号管理分类器决策 |
|
||||
| 权限 UI | `src/components/permissions/BashPermissionRequest.tsx` | **布线** | 分类器状态显示、审核选项 |
|
||||
| 权限管道 | `src/hooks/toolPermission/handlers/*.ts` | **布线** | 分类器结果路由到决策 |
|
||||
| API beta 标头 | `src/services/api/withRetry.ts` | **布线** | 启用时发送 `bash_classifier` beta |
|
||||
|
||||
### 2.2 参考实现:yoloClassifier.ts
|
||||
|
||||
文件:`src/utils/permissions/yoloClassifier.ts`(1496 行)
|
||||
|
||||
这是已实现的完整分类器,可作为 bashClassifier.ts 的参考:
|
||||
|
||||
```
|
||||
两阶段分类:
|
||||
1. 快速阶段:构建对话记录 → 调用 sideQuery(Opus)→ 快速阻止/允许
|
||||
2. 深度阶段:思考链分析 → 最终决策
|
||||
```
|
||||
|
||||
特性:
|
||||
- 构建完整对话记录上下文
|
||||
- 调用安全系统提示的 sideQuery
|
||||
- GrowthBook 配置和指标
|
||||
- 错误处理和降级
|
||||
|
||||
### 2.3 分类器在权限管道中的位置
|
||||
|
||||
```
|
||||
bash 命令到达
|
||||
│
|
||||
▼
|
||||
bashPermissions.ts 权限检查
|
||||
│
|
||||
├── 传统规则匹配(字符串级别)
|
||||
│
|
||||
└── [BASH_CLASSIFIER] LLM 分类
|
||||
│
|
||||
├── allow → 自动通过
|
||||
├── deny → 自动拒绝
|
||||
└── ask → 显示权限对话框
|
||||
│
|
||||
├── 分类器自动审批标记
|
||||
└── 审核选项(用户可覆盖)
|
||||
```
|
||||
|
||||
## 三、需要补全的内容
|
||||
|
||||
| 函数 | 需要实现 | 说明 |
|
||||
|------|---------|------|
|
||||
| `classifyBashCommand()` | LLM 调用评估安全性 | 参考 yoloClassifier.ts 的两阶段模式 |
|
||||
| `isClassifierPermissionsEnabled()` | GrowthBook/配置检查 | 控制分类器是否激活 |
|
||||
| `getBashPromptDenyDescriptions()` | 返回基于提示的拒绝规则 | 权限设置描述 |
|
||||
| `getBashPromptAskDescriptions()` | 返回询问规则 | 需要用户确认的命令 |
|
||||
| `getBashPromptAllowDescriptions()` | 返回允许规则 | 自动通过的命令 |
|
||||
| `generateGenericDescription()` | LLM 生成命令描述 | 为权限对话框提供说明 |
|
||||
| `extractPromptDescription()` | 解析规则内容 | 从规则中提取描述 |
|
||||
|
||||
## 四、关键设计决策
|
||||
|
||||
1. **ANT-ONLY 标记**:bashClassifier.ts 标注为 "ANT-ONLY",可能是 Anthropic 内部服务端分类器的客户端适配
|
||||
2. **两阶段分类**:快速阶段处理明确情况(减少延迟),深度阶段处理模糊情况
|
||||
3. **分类器结果可审核**:权限 UI 显示分类器决策,用户可覆盖
|
||||
4. **YOLO 分类器参考**:yoloClassifier.ts 提供完整的分类器实现模式,可直接参考
|
||||
|
||||
## 五、使用方式
|
||||
|
||||
```bash
|
||||
# 启用 feature
|
||||
FEATURE_BASH_CLASSIFIER=1 bun run dev
|
||||
|
||||
# 配合 TREE_SITTER_BASH 使用(AST + LLM 双重安全)
|
||||
FEATURE_BASH_CLASSIFIER=1 FEATURE_TREE_SITTER_BASH=1 bun run dev
|
||||
```
|
||||
|
||||
## 六、文件索引
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `src/utils/permissions/bashClassifier.ts` | — | Bash 分类器(stub,ANT-ONLY) |
|
||||
| `src/utils/permissions/yoloClassifier.ts` | 1496 | YOLO 分类器(完整参考实现) |
|
||||
| `src/utils/classifierApprovals.ts` | — | 分类器审批信号管理 |
|
||||
| `src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx` | — | 分类器 UI |
|
||||
| `src/hooks/toolPermission/handlers/interactiveHandler.ts` | — | 交互式权限处理 |
|
||||
| `src/services/api/withRetry.ts` | — | API beta 标头 |
|
||||
@@ -1,158 +0,0 @@
|
||||
# BRIDGE_MODE — 远程控制
|
||||
|
||||
> Feature Flag: `FEATURE_BRIDGE_MODE=1`
|
||||
> 实现状态:完整可用(v1 + v2 实现)
|
||||
> 引用数:28
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
BRIDGE_MODE 将本地 CLI 注册为"bridge 环境",可从 claude.ai 或其他控制面远程驱动。本地终端变为一个"执行者",接受远程指令并执行。
|
||||
|
||||
### 核心特性
|
||||
|
||||
- **环境注册**:本地 CLI 向 Anthropic 服务器注册为可用的 bridge 环境
|
||||
- **工作轮询**:长轮询(long-poll)等待远程任务分配
|
||||
- **会话管理**:创建、恢复、归档远程会话
|
||||
- **权限透传**:远程权限请求发送到控制面,用户在 claude.ai 上批准/拒绝
|
||||
- **心跳保活**:定期发送 heartbeat 延长任务租约
|
||||
- **可信设备**:v2 支持可信设备令牌增强安全性
|
||||
|
||||
## 二、实现架构
|
||||
|
||||
### 2.1 版本演进
|
||||
|
||||
| 版本 | 实现 | 特点 |
|
||||
|------|------|------|
|
||||
| v1(env-based) | `src/bridge/replBridge.ts` | 基于环境变量的传统 bridge |
|
||||
| v2(env-less) | `src/bridge/remoteBridgeCore.ts` | 无需环境变量,更安全的 bridge |
|
||||
|
||||
### 2.2 API 协议
|
||||
|
||||
文件:`src/bridge/bridgeApi.ts`
|
||||
|
||||
Bridge API Client 提供 9 个核心操作:
|
||||
|
||||
| 操作 | HTTP | 说明 |
|
||||
|------|------|------|
|
||||
| `registerBridgeEnvironment` | POST `/v1/environments/bridge` | 注册本地环境,获取 `environment_id` + `environment_secret` |
|
||||
| `pollForWork` | GET `/v1/environments/{id}/work/poll` | 长轮询等待任务(10s 超时) |
|
||||
| `acknowledgeWork` | POST `/v1/environments/{id}/work/{workId}/ack` | 确认接收任务 |
|
||||
| `stopWork` | POST `/v1/environments/{id}/work/{workId}/stop` | 停止任务 |
|
||||
| `heartbeatWork` | POST `/v1/environments/{id}/work/{workId}/heartbeat` | 续约任务租约 |
|
||||
| `deregisterEnvironment` | DELETE `/v1/environments/bridge/{id}` | 注销环境 |
|
||||
| `archiveSession` | POST `/v1/sessions/{id}/archive` | 归档会话(409 = 已归档,幂等) |
|
||||
| `sendPermissionResponseEvent` | POST `/v1/sessions/{id}/events` | 发送权限审批结果 |
|
||||
| `reconnectSession` | POST `/v1/environments/{id}/bridge/reconnect` | 重连已存在的会话 |
|
||||
|
||||
### 2.3 认证流程
|
||||
|
||||
```
|
||||
注册: OAuth Bearer Token → 获取 environment_secret
|
||||
轮询: environment_secret 作为 Authorization
|
||||
├── 401 → 尝试 OAuth token 刷新(onAuth401)
|
||||
└── 刷新成功 → 重试一次
|
||||
```
|
||||
|
||||
**OAuth 刷新**:API client 内置 `withOAuthRetry` 机制。401 时调用 `handleOAuth401Error`(同 withRetry.ts 的 v1/messages 模式),刷新后重试一次。
|
||||
|
||||
### 2.4 安全设计
|
||||
|
||||
- **路径穿越防护**:`validateBridgeId()` 使用 `/^[a-zA-Z0-9_-]+$/` 白名单验证所有服务端 ID
|
||||
- **BridgeFatalError**:不可重试的错误(401/403/404/410)直接抛出,阻止重试循环
|
||||
- **可信设备令牌**:v2 通过 `X-Trusted-Device-Token` header 增强安全层级
|
||||
- **幂关注册**:支持 `reuseEnvironmentId` 实现会话恢复,避免重复创建环境
|
||||
|
||||
### 2.5 数据流
|
||||
|
||||
```
|
||||
claude.ai 用户选择远程环境
|
||||
│
|
||||
▼
|
||||
POST /v1/environments/bridge (注册)
|
||||
│
|
||||
◀── environment_id + environment_secret
|
||||
│
|
||||
▼
|
||||
GET .../work/poll (长轮询)
|
||||
│
|
||||
◀── WorkResponse { id, data: { type, sessionId } }
|
||||
│
|
||||
▼
|
||||
POST .../work/{id}/ack (确认)
|
||||
│
|
||||
▼
|
||||
sessionRunner 创建 REPL session
|
||||
│
|
||||
├── 权限请求 → sendPermissionResponseEvent
|
||||
├── 心跳 → heartbeatWork (续约)
|
||||
└── 任务完成 → 自动归档
|
||||
```
|
||||
|
||||
### 2.6 模块结构
|
||||
|
||||
| 模块 | 文件 | 职责 |
|
||||
|------|------|------|
|
||||
| API Client | `bridgeApi.ts` | HTTP 通信(注册/轮询/确认/心跳/注销) |
|
||||
| Session Runner | `sessionRunner.ts` | 创建/恢复 REPL 会话 |
|
||||
| Bridge Config | `bridgeConfig.ts` | 配置管理(machine name、max sessions 等) |
|
||||
| Transport | `replBridgeTransport.ts` | Bridge 传输层 |
|
||||
| Permission Callbacks | `bridgePermissionCallbacks.ts` | 权限请求处理 |
|
||||
| Pointer | `bridgePointer.ts` | 当前活跃 bridge 状态指针 |
|
||||
| Flush Gate | `flushGate.ts` | 刷新控制 |
|
||||
| JWT Utils | `jwtUtils.ts` | JWT 令牌工具 |
|
||||
| Trusted Device | `trustedDevice.ts` | 可信设备管理 |
|
||||
| Debug Utils | `debugUtils.ts` | 调试日志 |
|
||||
| Types | `types.ts` | 类型定义 |
|
||||
|
||||
## 三、关键设计决策
|
||||
|
||||
1. **长轮询而非 WebSocket**:`pollForWork` 使用 HTTP GET + 10s 超时。简单可靠,无需维护 WebSocket 连接
|
||||
2. **OAuth 刷新内嵌**:API client 自带 `withOAuthRetry`,无需外层重试逻辑
|
||||
3. **ETag 条件请求**:注册时支持 `reuseEnvironmentId` 实现幂等会话恢复
|
||||
4. **v1/v2 共存**:代码中同时存在两套实现,v2 是更安全的升级版
|
||||
5. **权限双向流动**:本地权限请求发送到 claude.ai,用户在 web 上审批
|
||||
|
||||
## 四、使用方式
|
||||
|
||||
```bash
|
||||
# 启用 bridge mode
|
||||
FEATURE_BRIDGE_MODE=1 bun run dev
|
||||
|
||||
# 从 claude.ai/code 远程连接
|
||||
# 在 web 界面选择已注册的环境
|
||||
|
||||
# 配合 DAEMON 使用(后台守护)
|
||||
FEATURE_BRIDGE_MODE=1 FEATURE_DAEMON=1 bun run dev
|
||||
```
|
||||
|
||||
## 五、外部依赖
|
||||
|
||||
| 依赖 | 说明 |
|
||||
|------|------|
|
||||
| Anthropic OAuth | claude.ai 订阅登录 |
|
||||
| GrowthBook | `tengu_ccr_bridge` 门控 |
|
||||
| Bridge API | `/v1/environments/bridge` 系列端点 |
|
||||
|
||||
## 六、文件索引
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `src/bridge/bridgeApi.ts` | 541 | API Client(核心) |
|
||||
| `src/bridge/sessionRunner.ts` | — | 会话运行器 |
|
||||
| `src/bridge/bridgeConfig.ts` | — | 配置管理 |
|
||||
| `src/bridge/replBridgeTransport.ts` | — | 传输层 |
|
||||
| `src/bridge/bridgePermissionCallbacks.ts` | — | 权限回调 |
|
||||
| `src/bridge/bridgePointer.ts` | — | 状态指针 |
|
||||
| `src/bridge/flushGate.ts` | — | 刷新控制 |
|
||||
| `src/bridge/jwtUtils.ts` | — | JWT 工具 |
|
||||
| `src/bridge/trustedDevice.ts` | — | 可信设备 |
|
||||
| `src/bridge/remoteBridgeCore.ts` | — | v2 核心实现 |
|
||||
| `src/bridge/types.ts` | — | 类型定义 |
|
||||
| `src/bridge/debugUtils.ts` | — | 调试工具 |
|
||||
| `src/bridge/pollConfigDefaults.ts` | — | 轮询配置默认值 |
|
||||
| `src/bridge/bridgeUI.ts` | — | UI 组件 |
|
||||
| `src/bridge/codeSessionApi.ts` | — | 代码会话 API |
|
||||
| `src/bridge/peerSessions.ts` | — | 对等会话管理 |
|
||||
| `src/bridge/sessionIdCompat.ts` | — | Session ID 兼容层 |
|
||||
| `src/bridge/createSession.ts` | — | 会话创建 |
|
||||
| `src/bridge/replBridgeHandle.ts` | — | Bridge 句柄 |
|
||||
@@ -1,90 +0,0 @@
|
||||
---
|
||||
title: "Buddy 宠物系统"
|
||||
description: "Buddy 是 CLI 中的虚拟宠物伴侣,通过 /buddy 命令孵化、互动,会出现在输入框旁边陪伴你写代码。"
|
||||
keywords: ["buddy", "宠物", "companion", "伴侣", "虚拟宠物"]
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
Buddy 是 Claude Code 内置的虚拟宠物系统。在 REPL 中通过 `/buddy` 命令可以孵化一只随机生成的宠物伴侣,它会出现在输入框旁边,陪伴你的编码过程。
|
||||
|
||||
> Feature Flag: `FEATURE_BUDDY=1`
|
||||
|
||||
## 启用方式
|
||||
|
||||
```bash
|
||||
FEATURE_BUDDY=1 bun run dev
|
||||
```
|
||||
|
||||
孵化窗口:2026 年 4 月 1-7 日期间启动时,会在 REPL 顶部显示彩虹色的 `/buddy` 提示。4 月 7 日之后命令仍然可用,但不再自动提示。
|
||||
|
||||
## 命令
|
||||
|
||||
| 命令 | 说明 |
|
||||
|---|---|
|
||||
| `/buddy` | 查看当前宠物信息和属性 |
|
||||
| `/buddy hatch` | 孵化一只新宠物(首次使用) |
|
||||
| `/buddy rehatch` | 重新随机生成宠物(替换现有) |
|
||||
| `/buddy pet` | 撸宠物,触发爱心动画 |
|
||||
| `/buddy mute` | 静音宠物(隐藏) |
|
||||
| `/buddy unmute` | 取消静音 |
|
||||
|
||||
## 宠物属性
|
||||
|
||||
### 物种(18 种)
|
||||
|
||||
| | | | |
|
||||
|---|---|---|---|
|
||||
| Duck | Goose | Blob | Cat |
|
||||
| Dragon | Octopus | Owl | Penguin |
|
||||
| Turtle | Snail | Ghost | Axolotl |
|
||||
| Capybara | Cactus | Robot | Rabbit |
|
||||
| Mushroom | Chonk | | |
|
||||
|
||||
### 稀有度
|
||||
|
||||
| 稀有度 | 星级 | 权重 |
|
||||
|---|---|---|
|
||||
| Common | ★ | 60% |
|
||||
| Uncommon | ★★ | 25% |
|
||||
| Rare | ★★★ | 10% |
|
||||
| Epic | ★★★★ | 4% |
|
||||
| Legendary | ★★★★★ | 1% |
|
||||
|
||||
孵化时基于种子随机决定,存在极低概率出现 Shiny(闪光)变体。
|
||||
|
||||
### 属性值
|
||||
|
||||
每只宠物拥有 5 项属性(0-100):
|
||||
|
||||
- **DEBUGGING** — 调试能力
|
||||
- **PATIENCE** — 耐心程度
|
||||
- **CHAOS** — 混乱指数
|
||||
- **WISDOM** — 智慧值
|
||||
- **SNARK** — 毒舌度
|
||||
|
||||
### 外观
|
||||
|
||||
每只宠物还有随机的外观配件:
|
||||
|
||||
- **眼睛**: `·` `✦` `×` `◉` `@` `°`
|
||||
- **帽子**: none, crown, tophat, propeller, halo, wizard, beanie, tinyduck
|
||||
|
||||
## 数据存储
|
||||
|
||||
宠物信息存储在 `~/.claude.json` 的 `companion` 字段中。宠物的外观属性(物种、稀有度、属性值等)基于用户 ID 的哈希确定性生成,不可通过编辑配置文件来篡改稀有度。
|
||||
|
||||
## 相关源码
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `src/commands/buddy/index.ts` | `/buddy` 命令注册 |
|
||||
| `src/commands/buddy/buddy.ts` | `/buddy` 命令处理 |
|
||||
| `src/buddy/companion.ts` | 宠物生成与加载 |
|
||||
| `src/buddy/companionReact.ts` | 宠物反应系统(REPL 每轮查询后触发) |
|
||||
| `src/buddy/types.ts` | 类型定义(物种、稀有度、属性) |
|
||||
| `src/buddy/sprites.ts` | 终端像素画渲染 |
|
||||
| `src/buddy/CompanionSprite.tsx` | React 组件(输入框旁显示) |
|
||||
| `src/buddy/CompanionCard.tsx` | 宠物信息卡片(`/buddy` 无参数时展示) |
|
||||
| `src/buddy/useBuddyNotification.tsx` | 启动提示通知 |
|
||||
| `src/buddy/prompt.ts` | 宠物相关 prompt 模板 |
|
||||
@@ -1,30 +0,0 @@
|
||||
# Chrome Use — 浏览器自动化快速指南
|
||||
|
||||
让 Claude Code 直接控制你的 Chrome 浏览器,用自然语言完成网页操作。
|
||||
|
||||
## 快速开始(3 分钟)
|
||||
|
||||
### 第一步:安装 Chrome 扩展
|
||||
|
||||
1. 下载扩展:https://github.com/hangwin/mcp-chrome/releases
|
||||
2. 解压 zip 文件
|
||||
3. 打开 Chrome 访问 `chrome://extensions/`
|
||||
4. 开启右上角「开发者模式」
|
||||
5. 点击「加载已解压的扩展程序」,选择解压后的文件夹
|
||||
|
||||
### 第二步:启动 Claude Code
|
||||
|
||||
```bash
|
||||
bun run dev
|
||||
ccb # 或者 ccb 安装版也行
|
||||
```
|
||||
|
||||
### 第三步:启用 Chrome MCP
|
||||
|
||||
1. 在 REPL 中输入 `/mcp` 打开 MCP 面板
|
||||
2. 找到 `mcp-chrome`,按空格键启用
|
||||
3. 按 Enter 确认
|
||||
|
||||
## 相关文档
|
||||
|
||||
- GitHub 仓库:https://github.com/hangwin/mcp-chrome
|
||||
@@ -1,137 +0,0 @@
|
||||
# Claude in Chrome — 用户操作指南
|
||||
|
||||
## 1. 功能简介
|
||||
|
||||
Claude in Chrome 让 Claude Code 直接控制你的 Chrome 浏览器。你可以用自然语言让 Claude 帮你:
|
||||
|
||||
- 打开网页、导航、前进后退
|
||||
- 填写表单、上传图片
|
||||
- 截图、录制 GIF
|
||||
- 读取页面内容(DOM、纯文本)
|
||||
- 执行 JavaScript
|
||||
- 监控网络请求和控制台日志
|
||||
- 管理标签页
|
||||
|
||||
## 2. 前置条件
|
||||
|
||||
| 条件 | 说明 |
|
||||
|------|------|
|
||||
| Claude Code 订阅 | 需要 Claude Pro、Max 或 Team 订阅,浏览器插件功能不向免费用户开放 |
|
||||
| Chrome 浏览器 | 需已安装 Google Chrome |
|
||||
| Claude in Chrome 扩展 | 从 Chrome Web Store 安装(`claude.ai/chrome`) |
|
||||
| Claude Code CLI | 已通过 `bun run dev` 或构建产物运行 |
|
||||
|
||||
## 3. 启用方式
|
||||
|
||||
### Dev 模式
|
||||
|
||||
```bash
|
||||
bun run dev -- --chrome
|
||||
```
|
||||
|
||||
启动后 Claude 会自动检测 Chrome 扩展是否已安装,并注册浏览器控制工具。
|
||||
|
||||
### 构建产物
|
||||
|
||||
```bash
|
||||
node dist/cli.js --chrome
|
||||
```
|
||||
|
||||
### 禁用
|
||||
|
||||
```bash
|
||||
bun run dev -- --no-chrome
|
||||
```
|
||||
|
||||
或在 REPL 中通过 `/chrome` 命令切换启用/禁用状态。
|
||||
|
||||
### 通过配置默认启用
|
||||
|
||||
在 Claude Code 设置中将 `claudeInChromeDefaultEnabled` 设为 `true`,以后启动无需加 `--chrome` 参数。
|
||||
|
||||
## 4. 使用流程
|
||||
|
||||
1. **启动 CLI** — 加 `--chrome` 参数启动 Claude Code
|
||||
2. **确认连接** — REPL 中输入 `/chrome`,查看扩展状态是否显示 "Installed / Connected"
|
||||
3. **开始对话** — 正常与 Claude 对话,当需要操作浏览器时直接说,例如:
|
||||
- "打开 https://example.com 并截图"
|
||||
- "在当前页面搜索关键词 xxx"
|
||||
- "填写登录表单,用户名 admin"
|
||||
- "帮我录制当前操作的 GIF"
|
||||
4. **权限审批** — 首次执行浏览器操作时,Claude 会请求你的确认
|
||||
5. **操作完成** — Claude 完成操作后会返回结果(截图、文本、执行结果等)
|
||||
|
||||
## 5. 可用操作
|
||||
|
||||
### 页面交互
|
||||
|
||||
| 操作 | 说明 |
|
||||
|------|------|
|
||||
| `navigate` | 导航到指定 URL,或前进/后退 |
|
||||
| `computer` | 鼠标点击、移动、拖拽、键盘输入、截图等(13 种 action) |
|
||||
| `form_input` | 填写表单字段 |
|
||||
| `upload_image` | 上传图片到文件输入框或拖拽区域 |
|
||||
| `javascript_tool` | 在页面上下文执行 JavaScript |
|
||||
|
||||
### 页面读取
|
||||
|
||||
| 操作 | 说明 |
|
||||
|------|------|
|
||||
| `read_page` | 获取页面可访问性树(DOM 结构) |
|
||||
| `get_page_text` | 提取页面纯文本内容 |
|
||||
| `find` | 用自然语言搜索页面元素 |
|
||||
|
||||
### 标签页管理
|
||||
|
||||
| 操作 | 说明 |
|
||||
|------|------|
|
||||
| `tabs_context_mcp` | 获取当前标签组信息 |
|
||||
| `tabs_create_mcp` | 创建新标签页 |
|
||||
|
||||
### 监控与调试
|
||||
|
||||
| 操作 | 说明 |
|
||||
|------|------|
|
||||
| `read_console_messages` | 读取浏览器控制台日志 |
|
||||
| `read_network_requests` | 读取网络请求记录 |
|
||||
|
||||
### 其他
|
||||
|
||||
| 操作 | 说明 |
|
||||
|------|------|
|
||||
| `resize_window` | 调整浏览器窗口尺寸 |
|
||||
| `gif_creator` | 录制 GIF 并导出 |
|
||||
| `shortcuts_list` | 列出可用快捷方式 |
|
||||
| `shortcuts_execute` | 执行快捷方式 |
|
||||
| `update_plan` | 向你提交操作计划供审批 |
|
||||
| `switch_browser` | 切换到其他 Chrome 浏览器(仅 Bridge 模式) |
|
||||
|
||||
## 6. 通信模式
|
||||
|
||||
Claude in Chrome 支持两种与浏览器通信的方式:
|
||||
|
||||
### 本地 Socket(默认)
|
||||
|
||||
Chrome 扩展通过 Native Messaging Host 与 CLI 建立 Unix socket 连接。适用于本地开发,无需额外配置。
|
||||
|
||||
### Bridge WebSocket
|
||||
|
||||
通过 Anthropic 的 bridge 服务中转,支持远程操控浏览器。需要 claude.ai OAuth 登录。
|
||||
|
||||
## 7. 常见问题
|
||||
|
||||
### 扩展显示未安装
|
||||
|
||||
确认已从 Chrome Web Store 安装 "Claude in Chrome" 扩展,安装后重启浏览器。
|
||||
|
||||
### 工具未出现在工具列表
|
||||
|
||||
检查启动时是否加了 `--chrome` 参数,或通过 `/chrome` 命令确认状态。
|
||||
|
||||
### 连接超时
|
||||
|
||||
确保 Chrome 浏览器正在运行且扩展已启用。Native Messaging Host 在扩展安装时自动注册,如果重装过扩展需要重启浏览器。
|
||||
|
||||
### 不使用 Chrome 功能时
|
||||
|
||||
不带 `--chrome` 参数正常启动即可,不会加载任何浏览器相关模块,不影响其他功能。
|
||||
@@ -1,325 +0,0 @@
|
||||
# Computer Use 架构修正方案 v2
|
||||
|
||||
更新时间:2026-04-04
|
||||
|
||||
## 1. 当前架构的问题
|
||||
|
||||
### 问题 A:平台代码混在错误的包里
|
||||
|
||||
`@ant/computer-use-swift` 是 macOS Swift 原生模块的包装器,但我们把 Windows(`backends/win32.ts`)和 Linux(`backends/linux.ts`)的截图/应用管理代码塞进了这个包。"swift" 在名字里就意味着 macOS,后期维护者无法区分。
|
||||
|
||||
`@ant/computer-use-input` 同样——原本是 macOS enigo Rust 模块,我们也往里面塞了 win32/linux 后端。
|
||||
|
||||
### 问题 B:输入方式不对
|
||||
|
||||
当前 Windows 后端(`packages/@ant/computer-use-input/src/backends/win32.ts`)使用 `SetCursorPos` + `SendInput` + `keybd_event`——这是**全局输入**:
|
||||
|
||||
- 鼠标真的会移动到屏幕上
|
||||
- 键盘真的打到当前前台窗口
|
||||
- **会影响用户当前的操作**
|
||||
|
||||
绑定窗口句柄后,应该用 `SendMessage`/`PostMessage` 向目标 HWND 发送消息:
|
||||
|
||||
- `WM_CHAR` — 发送字符,不移动光标
|
||||
- `WM_KEYDOWN`/`WM_KEYUP` — 发送按键
|
||||
- `WM_LBUTTONDOWN`/`WM_LBUTTONUP` — 发送鼠标点击(窗口客户区相对坐标)
|
||||
- `PrintWindow` — 截取窗口内容,不需要窗口在前台
|
||||
- **不抢焦点、不影响用户当前操作**
|
||||
|
||||
已验证:向记事本 `SendMessage(WM_CHAR)` 成功写入文字,记事本在后台,终端保持前台。
|
||||
|
||||
### 问题 C:截图是公共能力,不属于 swift
|
||||
|
||||
截图(screenshot)、显示器枚举(display)、应用管理(apps)是所有平台都需要的公共能力,不应该放在 `@ant/computer-use-swift`(macOS 专属包名)里。
|
||||
|
||||
## 2. 修正后的架构
|
||||
|
||||
### 2.1 分层原则
|
||||
|
||||
```
|
||||
packages/@ant/ ← macOS 原生模块包装器(不放其他平台代码)
|
||||
├── computer-use-input/ ← macOS: enigo .node 键鼠(仅 darwin)
|
||||
├── computer-use-swift/ ← macOS: Swift .node 截图/应用(仅 darwin)
|
||||
└── computer-use-mcp/ ← 跨平台: MCP server + 工具定义(不改)
|
||||
|
||||
src/utils/computerUse/
|
||||
├── platforms/ ← 新增: 跨平台抽象层
|
||||
│ ├── types.ts ← 公共接口: InputPlatform, ScreenshotPlatform, AppsPlatform, DisplayPlatform
|
||||
│ ├── index.ts ← 平台分发器: 按 process.platform 加载后端
|
||||
│ ├── darwin.ts ← macOS: 委托给 @ant/computer-use-{input,swift}
|
||||
│ ├── win32.ts ← Windows: SendMessage 输入 + PrintWindow 截图 + EnumWindows + UIA + OCR
|
||||
│ └── linux.ts ← Linux: xdotool + scrot + xrandr + wmctrl
|
||||
│
|
||||
├── win32/ ← Windows 专属增强能力(不在公共接口中)
|
||||
│ ├── windowCapture.ts ← PrintWindow 窗口绑定截图
|
||||
│ ├── windowEnum.ts ← EnumWindows 窗口枚举
|
||||
│ ├── windowMessage.ts ← SendMessage/PostMessage 无焦点输入(新增)
|
||||
│ ├── uiAutomation.ts ← IUIAutomation UI 元素操作
|
||||
│ └── ocr.ts ← Windows.Media.Ocr 文字识别
|
||||
│
|
||||
├── executor.ts ← 改: 通过 platforms/ 获取平台实现,不直接调 @ant 包
|
||||
├── swiftLoader.ts ← 改: 仅 darwin 使用
|
||||
├── inputLoader.ts ← 改: 仅 darwin 使用
|
||||
└── ...其他文件不动
|
||||
```
|
||||
|
||||
### 2.2 公共接口(`platforms/types.ts`)
|
||||
|
||||
```typescript
|
||||
/** 窗口标识 — 跨平台 */
|
||||
export interface WindowHandle {
|
||||
id: string // macOS: bundleId, Windows: HWND string, Linux: window ID
|
||||
pid: number
|
||||
title: string
|
||||
exePath?: string // Windows/Linux: 进程路径
|
||||
}
|
||||
|
||||
/** 输入平台接口 — 两种模式 */
|
||||
export interface InputPlatform {
|
||||
// 模式 A: 全局输入(macOS/Linux 默认,向前台窗口发送)
|
||||
moveMouse(x: number, y: number): Promise<void>
|
||||
click(x: number, y: number, button: 'left' | 'right' | 'middle'): Promise<void>
|
||||
typeText(text: string): Promise<void>
|
||||
key(name: string, action: 'press' | 'release'): Promise<void>
|
||||
keys(combo: string[]): Promise<void>
|
||||
scroll(amount: number, direction: 'vertical' | 'horizontal'): Promise<void>
|
||||
mouseLocation(): Promise<{ x: number; y: number }>
|
||||
|
||||
// 模式 B: 窗口绑定输入(Windows SendMessage,不抢焦点)
|
||||
sendChar?(hwnd: string, char: string): Promise<void>
|
||||
sendKey?(hwnd: string, vk: number, action: 'down' | 'up'): Promise<void>
|
||||
sendClick?(hwnd: string, x: number, y: number, button: 'left' | 'right'): Promise<void>
|
||||
sendText?(hwnd: string, text: string): Promise<void>
|
||||
}
|
||||
|
||||
/** 截图平台接口 */
|
||||
export interface ScreenshotPlatform {
|
||||
// 全屏截图
|
||||
captureScreen(displayId?: number): Promise<ScreenshotResult>
|
||||
// 区域截图
|
||||
captureRegion(x: number, y: number, w: number, h: number): Promise<ScreenshotResult>
|
||||
// 窗口截图(Windows: PrintWindow,macOS: SCContentFilter,Linux: xdotool+import)
|
||||
captureWindow?(hwnd: string): Promise<ScreenshotResult | null>
|
||||
}
|
||||
|
||||
/** 显示器平台接口 */
|
||||
export interface DisplayPlatform {
|
||||
listAll(): DisplayInfo[]
|
||||
getSize(displayId?: number): DisplayInfo
|
||||
}
|
||||
|
||||
/** 应用管理平台接口 */
|
||||
export interface AppsPlatform {
|
||||
listRunning(): WindowHandle[]
|
||||
listInstalled(): Promise<InstalledApp[]>
|
||||
open(name: string): Promise<void>
|
||||
getFrontmostApp(): FrontmostAppInfo | null
|
||||
findWindowByTitle(title: string): WindowHandle | null
|
||||
}
|
||||
|
||||
export interface ScreenshotResult {
|
||||
base64: string
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export interface DisplayInfo {
|
||||
width: number
|
||||
height: number
|
||||
scaleFactor: number
|
||||
displayId: number
|
||||
}
|
||||
|
||||
export interface InstalledApp {
|
||||
id: string // macOS: bundleId, Windows: exe path, Linux: .desktop name
|
||||
displayName: string
|
||||
path: string
|
||||
}
|
||||
|
||||
export interface FrontmostAppInfo {
|
||||
id: string
|
||||
appName: string
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 平台分发器(`platforms/index.ts`)
|
||||
|
||||
```typescript
|
||||
import type { InputPlatform, ScreenshotPlatform, DisplayPlatform, AppsPlatform } from './types.js'
|
||||
|
||||
export interface Platform {
|
||||
input: InputPlatform
|
||||
screenshot: ScreenshotPlatform
|
||||
display: DisplayPlatform
|
||||
apps: AppsPlatform
|
||||
}
|
||||
|
||||
export function loadPlatform(): Platform {
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
return require('./darwin.js').platform
|
||||
case 'win32':
|
||||
return require('./win32.js').platform
|
||||
case 'linux':
|
||||
return require('./linux.js').platform
|
||||
default:
|
||||
throw new Error(`Computer Use not supported on ${process.platform}`)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 各平台实现
|
||||
|
||||
**`platforms/darwin.ts`** — 委托给 @ant 包(保持兼容):
|
||||
```typescript
|
||||
// macOS: 通过 @ant/computer-use-input 和 @ant/computer-use-swift
|
||||
// 这两个包的 darwin 后端保留不动
|
||||
import { requireComputerUseInput } from '../inputLoader.js'
|
||||
import { requireComputerUseSwift } from '../swiftLoader.js'
|
||||
|
||||
export const platform = {
|
||||
input: { /* 委托给 requireComputerUseInput() */ },
|
||||
screenshot: { /* 委托给 requireComputerUseSwift().screenshot */ },
|
||||
display: { /* 委托给 requireComputerUseSwift().display */ },
|
||||
apps: { /* 委托给 requireComputerUseSwift().apps */ },
|
||||
}
|
||||
```
|
||||
|
||||
**`platforms/win32.ts`** — 使用 `src/utils/computerUse/win32/` 模块:
|
||||
```typescript
|
||||
// Windows: SendMessage 输入 + PrintWindow 截图 + EnumWindows 应用
|
||||
import { sendChar, sendKey, sendClick, sendText } from '../win32/windowMessage.js'
|
||||
import { captureWindow } from '../win32/windowCapture.js'
|
||||
import { listWindows } from '../win32/windowEnum.js'
|
||||
// ... PowerShell P/Invoke 全局输入作为 fallback
|
||||
|
||||
export const platform = {
|
||||
input: {
|
||||
// 全局模式: PowerShell SetCursorPos/SendInput(fallback)
|
||||
// 窗口模式: SendMessage(首选)
|
||||
sendChar, sendKey, sendClick, sendText, // 窗口绑定
|
||||
moveMouse, click, typeText, ... // 全局 fallback
|
||||
},
|
||||
screenshot: {
|
||||
captureScreen, // CopyFromScreen
|
||||
captureRegion, // CopyFromScreen(rect)
|
||||
captureWindow, // PrintWindow(不抢焦点)
|
||||
},
|
||||
display: { /* Screen.AllScreens */ },
|
||||
apps: { /* EnumWindows */ },
|
||||
}
|
||||
```
|
||||
|
||||
**`platforms/linux.ts`** — 使用 xdotool/scrot:
|
||||
```typescript
|
||||
// Linux: xdotool + scrot + xrandr + wmctrl
|
||||
export const platform = {
|
||||
input: { /* xdotool mousemove/click/key/type */ },
|
||||
screenshot: { /* scrot */ },
|
||||
display: { /* xrandr */ },
|
||||
apps: { /* wmctrl + ps */ },
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 executor.ts 改造
|
||||
|
||||
```typescript
|
||||
// 之前: 直接调 requireComputerUseSwift() 和 requireComputerUseInput()
|
||||
// 之后: 通过 platforms/ 统一获取
|
||||
|
||||
import { loadPlatform } from './platforms/index.js'
|
||||
|
||||
const platform = loadPlatform()
|
||||
|
||||
// 截图
|
||||
platform.screenshot.captureScreen()
|
||||
platform.screenshot.captureWindow(hwnd) // 窗口绑定
|
||||
|
||||
// 输入(窗口绑定模式,不抢焦点)
|
||||
platform.input.sendText?.(hwnd, 'Hello')
|
||||
platform.input.sendClick?.(hwnd, 100, 200, 'left')
|
||||
|
||||
// 输入(全局模式,fallback)
|
||||
platform.input.moveMouse(500, 500)
|
||||
platform.input.click(500, 500, 'left')
|
||||
```
|
||||
|
||||
## 3. Windows 输入模式对比
|
||||
|
||||
| 方式 | API | 抢焦点 | 移鼠标 | 窗口可最小化 | 适用场景 |
|
||||
|------|-----|--------|--------|-------------|---------|
|
||||
| **全局输入** | `SetCursorPos` + `SendInput` | ✅ 抢 | ✅ 动 | ❌ 不行 | 需要坐标点击(fallback) |
|
||||
| **窗口消息** | `SendMessage(WM_CHAR/WM_KEYDOWN)` | ❌ 不抢 | ❌ 不动 | ✅ 可以 | 打字、按键(首选) |
|
||||
| **窗口消息** | `SendMessage(WM_LBUTTONDOWN)` | ❌ 不抢 | ❌ 不动 | ⚠️ 部分 | 窗口内点击 |
|
||||
| **窗口截图** | `PrintWindow(hwnd, PW_RENDERFULLCONTENT)` | ❌ 不抢 | ❌ 不动 | ✅ 可以 | 窗口截图 |
|
||||
| **UI 操作** | `UIAutomation InvokePattern` | ❌ 不抢 | ❌ 不动 | ✅ 可以 | 按钮点击、文本写入 |
|
||||
|
||||
**策略**:优先用窗口消息 + UIAutomation(不干扰用户),全局输入作为 fallback。
|
||||
|
||||
## 4. 需要新增的文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `src/utils/computerUse/platforms/types.ts` | 公共接口定义 |
|
||||
| `src/utils/computerUse/platforms/index.ts` | 平台分发器 |
|
||||
| `src/utils/computerUse/platforms/darwin.ts` | macOS: 委托给 @ant 包 |
|
||||
| `src/utils/computerUse/platforms/win32.ts` | Windows: 组合 win32/ 下各模块 |
|
||||
| `src/utils/computerUse/platforms/linux.ts` | Linux: xdotool/scrot |
|
||||
| `src/utils/computerUse/win32/windowMessage.ts` | **新增**: SendMessage 无焦点输入 |
|
||||
|
||||
## 5. 需要移除/清理的文件
|
||||
|
||||
| 文件 | 操作 | 原因 |
|
||||
|------|------|------|
|
||||
| `packages/@ant/computer-use-input/src/backends/win32.ts` | 删除 | Windows 代码不应在 macOS 包里 |
|
||||
| `packages/@ant/computer-use-input/src/backends/linux.ts` | 删除 | Linux 代码不应在 macOS 包里 |
|
||||
| `packages/@ant/computer-use-swift/src/backends/win32.ts` | 删除 | 同上 |
|
||||
| `packages/@ant/computer-use-swift/src/backends/linux.ts` | 删除 | 同上 |
|
||||
| `packages/@ant/computer-use-input/src/types.ts` | 删除 | 移到 platforms/types.ts |
|
||||
| `packages/@ant/computer-use-swift/src/types.ts` | 删除 | 移到 platforms/types.ts |
|
||||
|
||||
## 6. 需要修改的文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `packages/@ant/computer-use-input/src/index.ts` | 恢复为仅 darwin dispatcher(去掉 win32/linux case) |
|
||||
| `packages/@ant/computer-use-swift/src/index.ts` | 恢复为仅 darwin dispatcher(去掉 win32/linux case) |
|
||||
| `src/utils/computerUse/executor.ts` | 通过 `platforms/` 获取平台实现,不直接调 @ant 包 |
|
||||
| `src/utils/computerUse/swiftLoader.ts` | 仅 darwin 加载 |
|
||||
| `src/utils/computerUse/inputLoader.ts` | 仅 darwin 加载 |
|
||||
|
||||
## 7. @ant 包的定位(修正后)
|
||||
|
||||
| 包 | 职责 | 平台 |
|
||||
|---|------|------|
|
||||
| `@ant/computer-use-input` | macOS enigo 键鼠原生模块包装 | **仅 darwin** |
|
||||
| `@ant/computer-use-swift` | macOS Swift 截图/应用原生模块包装 | **仅 darwin** |
|
||||
| `@ant/computer-use-mcp` | MCP Server + 工具定义 + 调用路由 | **跨平台**(不含平台代码) |
|
||||
|
||||
Windows/Linux 的平台实现全部在 `src/utils/computerUse/platforms/` 和 `src/utils/computerUse/win32/` 中。
|
||||
|
||||
## 8. 执行顺序
|
||||
|
||||
```
|
||||
Phase 1: 创建 platforms/ 抽象层
|
||||
├── platforms/types.ts(公共接口)
|
||||
├── platforms/index.ts(分发器)
|
||||
└── platforms/darwin.ts(委托 @ant 包)
|
||||
|
||||
Phase 2: 创建 Windows 平台实现
|
||||
├── win32/windowMessage.ts(SendMessage 无焦点输入)
|
||||
└── platforms/win32.ts(组合 win32/ 各模块)
|
||||
|
||||
Phase 3: 创建 Linux 平台实现
|
||||
└── platforms/linux.ts(xdotool/scrot)
|
||||
|
||||
Phase 4: 改造 executor.ts
|
||||
└── 通过 platforms/ 获取实现,不直接调 @ant
|
||||
|
||||
Phase 5: 清理 @ant 包
|
||||
├── 删除 @ant/computer-use-input/src/backends/{win32,linux}.ts
|
||||
├── 删除 @ant/computer-use-swift/src/backends/{win32,linux}.ts
|
||||
└── 恢复 index.ts 为 darwin-only
|
||||
|
||||
Phase 6: 验证 + PR
|
||||
```
|
||||
@@ -1,277 +0,0 @@
|
||||
# Computer Use MCP 工具测试报告
|
||||
|
||||
> 测试日期: 2026-04-04
|
||||
> 测试环境: macOS Darwin 25.4.0, Cursor (IDE tier: click)
|
||||
> MCP Server: `@ant/computer-use-mcp`
|
||||
|
||||
## 工具总览
|
||||
|
||||
共 17 个工具(含 batch 复合操作),分为 5 大类:
|
||||
|
||||
| 类别 | 工具 | 数量 |
|
||||
|------|------|------|
|
||||
| 截图/显示 | `screenshot`, `switch_display`, `zoom` | 3 |
|
||||
| 鼠标操作 | `left_click`, `right_click`, `double_click`, `triple_click`, `middle_click`, `left_click_drag`, `mouse_move` | 7 |
|
||||
| 键盘操作 | `key`, `type`, `hold_key` | 3 |
|
||||
| 状态查询 | `cursor_position`, `request_access` | 2 |
|
||||
| 复合/辅助 | `computer_batch`, `wait` | 2 |
|
||||
|
||||
---
|
||||
|
||||
## 测试结果
|
||||
|
||||
### 1. 权限管理
|
||||
|
||||
#### `request_access` — 请求应用访问权限
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ✅ 通过 |
|
||||
| 行为 | 弹出系统对话框请求用户授权,支持批量申请多个应用 |
|
||||
| 返回 | `{ granted: [...], denied: [...], tierGuidance: "..." }` |
|
||||
| 权限分级 | `click`(仅点击), `full`(完整控制) |
|
||||
| 说明 | IDE 类应用(Cursor、VSCode、Terminal)默认授予 `click` tier,限制键盘输入和右键操作;系统应用(System Settings)授予 `full` tier |
|
||||
|
||||
#### 已授权应用
|
||||
|
||||
| 应用 | Tier | 能力 |
|
||||
|------|------|------|
|
||||
| Cursor | click | 可见 + 纯左键点击(无键盘输入、右键、修饰键点击、拖拽) |
|
||||
| Terminal | click | 同上 |
|
||||
| System Settings | full | 完整控制(键鼠、拖拽等) |
|
||||
| Finder | — | 已授权 |
|
||||
|
||||
---
|
||||
|
||||
### 2. 截图与显示
|
||||
|
||||
#### `screenshot` — 截取屏幕截图
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ⚠️ 部分通过 |
|
||||
| 执行 | 工具成功执行,返回 `ok: true` |
|
||||
| 图片 | **未返回可视图片内容**(output 为空字符串) |
|
||||
| `save_to_disk` | 设置后仍无输出 |
|
||||
| 分析 | 可能原因:(1) macOS 屏幕录制权限未授予;(2) 当前前台应用未被过滤导致截图为空;(3) MCP 传输层未正确编码图片数据 |
|
||||
| 建议 | 检查 **系统设置 → 隐私与安全性 → 屏幕录制** 是否授权给运行 Claude Code 的应用 |
|
||||
|
||||
#### `switch_display` — 切换显示器
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ✅ 通过 |
|
||||
| 行为 | 接受显示器名称或 `"auto"`(自动选择) |
|
||||
| 返回 | 确认消息 |
|
||||
|
||||
#### `zoom` — 区域放大截图
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ⏭️ 跳过 |
|
||||
| 原因 | 依赖 `screenshot` 返回的图片坐标,截图未返回图片无法测试 |
|
||||
|
||||
---
|
||||
|
||||
### 3. 鼠标操作
|
||||
|
||||
> 以下测试在 Cursor 窗口上执行(tier: click)
|
||||
|
||||
#### `mouse_move` — 移动鼠标
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ✅ 通过 |
|
||||
| 输入 | `coordinate: [500, 500]` |
|
||||
| 返回 | `"Moved."` |
|
||||
|
||||
#### `left_click` — 左键单击
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ✅ 通过 |
|
||||
| 输入 | `coordinate: [500, 500]` |
|
||||
| 返回 | `"Clicked."` |
|
||||
|
||||
#### `double_click` — 双击
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ✅ 通过 |
|
||||
| 输入 | `coordinate: [500, 500]` |
|
||||
| 返回 | `"Clicked."` |
|
||||
|
||||
#### `triple_click` — 三击
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ✅ 通过 |
|
||||
| 输入 | `coordinate: [500, 500]` |
|
||||
| 返回 | `"Clicked."` |
|
||||
|
||||
#### `right_click` — 右键点击
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ⚠️ 受 tier 限制 |
|
||||
| Cursor (click tier) | ❌ 被拒绝 — `"Code" is granted at tier "click" — right-click, middle-click, and clicks with modifier keys require tier "full"` |
|
||||
| Finder (full tier) | ✅ 通过 — 返回 `"Clicked."` |
|
||||
| 结论 | 功能正常,IDE 安全限制符合预期 |
|
||||
|
||||
#### `middle_click` — 中键点击
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ⚠️ 受 tier 限制 |
|
||||
| Cursor (click tier) | ❌ 被拒绝 — 同 `right_click`,需要 full tier |
|
||||
| Finder (full tier) | ✅ 通过 — 返回 `"Clicked."` |
|
||||
| 结论 | 功能正常,IDE 安全限制符合预期 |
|
||||
|
||||
#### `left_click_drag` — 拖拽
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ⚠️ 受 tier 限制 |
|
||||
| Cursor (click tier) | ❌ 被拒绝 — 拖拽被视为修饰键点击,需要 full tier |
|
||||
| Finder (full tier) | ✅ 通过 — 返回 `"Dragged."` |
|
||||
| 结论 | 功能正常,IDE 安全限制符合预期 |
|
||||
|
||||
#### `scroll` — 滚轮滚动
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ✅ 通过 |
|
||||
| 输入 | `coordinate: [500, 500]`, `scroll_direction: "down"`, `scroll_amount: 3` |
|
||||
| 返回 | `"Scrolled."` |
|
||||
| 反向 | ✅ `scroll_direction: "up"` 也通过 |
|
||||
|
||||
---
|
||||
|
||||
### 4. 键盘操作
|
||||
|
||||
> 以下测试在 Cursor 窗口上执行(tier: click)— 所有键盘操作均被拒绝
|
||||
|
||||
#### `key` — 按键/快捷键
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ⚠️ 受 tier 限制 |
|
||||
| Cursor (click tier) | ❌ 被拒绝 — IDE tier 限制键盘输入 |
|
||||
| Finder (full tier) | ✅ 通过 — `escape` 按键成功,返回 `"Key pressed."` |
|
||||
| 结论 | 功能正常,IDE 安全限制符合预期 |
|
||||
|
||||
#### `type` — 输入文本
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ⚠️ 受 tier 限制 |
|
||||
| Cursor (click tier) | ❌ 被拒绝 — IDE tier 限制文本输入 |
|
||||
| Finder (full tier) | ✅ 通过 — 输入 `"hello"` 成功,返回 `"Typed 5 grapheme(s)."` |
|
||||
| 结论 | 功能正常,IDE 安全限制符合预期 |
|
||||
|
||||
#### `hold_key` — 按住按键
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ⚠️ 受 tier 限制 |
|
||||
| Cursor (click tier) | ❌ 被拒绝 — IDE tier 限制键盘输入 |
|
||||
| Finder (full tier) | ✅ 通过 — 按住 `shift` 1 秒成功,返回 `"Key held."` |
|
||||
| 结论 | 功能正常,IDE 安全限制符合预期 |
|
||||
|
||||
---
|
||||
|
||||
### 5. 状态查询
|
||||
|
||||
#### `cursor_position` — 获取鼠标位置
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ✅ 通过 |
|
||||
| 返回 | `{"x": null, "y": null, "coordinateSpace": "image_pixels"}` |
|
||||
| 说明 | 坐标为 null 是因为没有成功截图,无参考坐标系 |
|
||||
|
||||
---
|
||||
|
||||
### 6. 复合/辅助操作
|
||||
|
||||
#### `computer_batch` — 批量执行操作
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ✅ 通过 |
|
||||
| 行为 | 按顺序执行操作列表,遇到失败则停止后续操作 |
|
||||
| 返回 | `{ completed: [...], failed: {...}, remaining: N }` |
|
||||
| 特点 | 单次 API 调用执行多个操作,减少往返延迟 |
|
||||
| 错误处理 | 失败的操作会中断后续操作,返回已完成和剩余数量 |
|
||||
|
||||
#### `wait` — 等待
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 状态 | ✅ 通过 |
|
||||
| 输入 | `duration: 1` (秒) |
|
||||
| 返回 | `"Waited 1s."` |
|
||||
| 最大值 | 100 秒 |
|
||||
|
||||
---
|
||||
|
||||
## 汇总统计
|
||||
|
||||
| 状态 | 数量 | 工具 |
|
||||
|------|------|------|
|
||||
| ✅ 通过 | 10 | `request_access`, `switch_display`, `mouse_move`, `left_click`, `double_click`, `triple_click`, `scroll`, `cursor_position`, `computer_batch`, `wait` |
|
||||
| ⚠️ 部分通过 | 7 | `screenshot`(执行成功但无图片返回), `right_click`, `middle_click`, `left_click_drag`, `key`, `type`, `hold_key`(均在 full tier 应用上通过,IDE click tier 限制是预期行为) |
|
||||
| ❌ 被拒绝 | 0 | — |
|
||||
| ⏭️ 跳过 | 1 | `zoom`(依赖截图) |
|
||||
|
||||
---
|
||||
|
||||
## 已知问题
|
||||
|
||||
### P0: 截图无图片返回
|
||||
|
||||
`screenshot` 工具执行成功但未返回图片内容,导致:
|
||||
- 无法获取屏幕坐标参考
|
||||
- `cursor_position` 返回 null 坐标
|
||||
- `zoom` 无法使用
|
||||
- 所有点击操作只能盲点(无截图验证)
|
||||
|
||||
**可能原因**:
|
||||
1. macOS 屏幕录制权限未授予
|
||||
2. MCP 图片传输/编码问题
|
||||
3. 截图内容被安全过滤机制过滤
|
||||
|
||||
**建议排查**: 检查 `系统设置 → 隐私与安全性 → 屏幕录制` 权限。
|
||||
|
||||
### P1: IDE 应用键盘操作受限 — ✅ 已确认功能正常
|
||||
|
||||
IDE 类应用(Cursor、VSCode、Terminal)被限制在 `click` tier,无法执行:
|
||||
- 键盘输入(`key`, `type`, `hold_key`)
|
||||
- 右键/中键点击(`right_click`, `middle_click`)
|
||||
- 拖拽操作(`left_click_drag`)
|
||||
|
||||
这是安全设计,防止 AI 操控 IDE 终端。**在 full tier 应用(Finder、System Settings)上,以上 6 个操作均测试通过,功能完全正常。**
|
||||
|
||||
---
|
||||
|
||||
## 权限模型说明
|
||||
|
||||
Computer Use MCP 采用分级权限模型:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Tier: full │
|
||||
│ - 所有鼠标操作(左键、右键、中键、拖拽) │
|
||||
│ - 键盘输入(type, key, hold_key) │
|
||||
│ - 适用于: 系统应用、Finder 等 │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Tier: click │
|
||||
│ - 仅纯左键点击 │
|
||||
│ - 滚轮滚动 │
|
||||
│ - 适用于: IDE、Terminal 等 │
|
||||
├─────────────────────────────────────────┤
|
||||
│ 未授权 │
|
||||
│ - 所有操作被拒绝 │
|
||||
│ - 需通过 request_access 申请 │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
@@ -1,315 +0,0 @@
|
||||
# Computer Use Windows 增强实施计划
|
||||
|
||||
更新时间:2026-04-03
|
||||
依赖文档:`docs/features/windows-ai-desktop-control.md`、`docs/features/computer-use.md`
|
||||
|
||||
## 1. 目标
|
||||
|
||||
在已有的 PowerShell 子进程方案基础上,利用 Windows 原生 API 增强 Computer Use 的 Windows 实现,解决 3 个核心问题:
|
||||
|
||||
1. **窗口绑定截图**:当前 `CopyFromScreen` 只能全屏截图,无法对指定窗口截图(尤其是被遮挡/最小化窗口)
|
||||
2. **UI 结构感知**:当前只能通过坐标点击,无法像 macOS Accessibility 那样理解 UI 元素树
|
||||
3. **性能**:每次 PowerShell 启动约 273ms,剪贴板/窗口枚举等高频操作需要更快的方式
|
||||
|
||||
## 2. 已验证的 Windows API 能力
|
||||
|
||||
以下 API 全部通过 PowerShell P/Invoke 实测通过:
|
||||
|
||||
| 能力 | API | 验证结果 |
|
||||
|------|-----|---------|
|
||||
| 窗口绑定截图 | `PrintWindow(hwnd, hdc, PW_RENDERFULLCONTENT)` | ✅ VS Code 342KB, Chrome 273KB |
|
||||
| 枚举窗口+HWND | `EnumWindows` + `GetWindowText` + `GetWindowThreadProcessId` | ✅ 38 个窗口,含 HWND/PID/标题 |
|
||||
| UI 元素树 | `System.Windows.Automation.AutomationElement` | ✅ 记事本 39 个元素 |
|
||||
| UI 写值 | `ValuePattern.SetValue()` | ✅ 成功写入记事本文本 |
|
||||
| UI 点击 | `InvokePattern.Invoke()` | ✅ 按钮可程序化点击 |
|
||||
| 坐标元素识别 | `AutomationElement.FromPoint(x, y)` | ✅ 返回元素类型+名称 |
|
||||
| OCR | `Windows.Media.Ocr.OcrEngine` | ✅ 英语+中文引擎可用 |
|
||||
| 全局热键 | `RegisterHotKey` | ✅ API 可调 |
|
||||
| 剪贴板直接操作 | `System.Windows.Forms.Clipboard` | ✅ 读/写/图片检测 |
|
||||
| Shell 启动 | `ShellExecute` | ✅ 打开文件/URL/应用 |
|
||||
|
||||
## 3. 架构设计
|
||||
|
||||
### 3.1 文件结构
|
||||
|
||||
在现有 `backends/win32.ts` 基础上新增 Windows 专属模块:
|
||||
|
||||
```
|
||||
packages/@ant/computer-use-input/src/
|
||||
├── backends/
|
||||
│ ├── darwin.ts ← 不动
|
||||
│ ├── win32.ts ← 增强:直接 Win32 API 替代部分 PowerShell
|
||||
│ └── linux.ts ← 不动
|
||||
|
||||
packages/@ant/computer-use-swift/src/
|
||||
├── backends/
|
||||
│ ├── darwin.ts ← 不动
|
||||
│ ├── win32.ts ← 增强:PrintWindow 窗口截图 + EnumWindows
|
||||
│ └── linux.ts ← 不动
|
||||
|
||||
packages/@ant/computer-use-mcp/src/
|
||||
│ └── tools.ts ← 增加 Windows 专属工具定义(UI Automation、OCR)
|
||||
|
||||
src/utils/computerUse/
|
||||
│ └── win32/ ← 新增目录:Windows 专属能力
|
||||
│ ├── uiAutomation.ts ← UI 元素树、点击、写值
|
||||
│ ├── ocr.ts ← 截图 + OCR 文字识别
|
||||
│ ├── windowCapture.ts ← PrintWindow 窗口绑定截图
|
||||
│ └── windowEnum.ts ← EnumWindows 窗口枚举
|
||||
```
|
||||
|
||||
### 3.2 分层
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ Computer Use MCP Tools │
|
||||
│ screenshot / click / type / request_access │
|
||||
│ + Windows 专属: ui_tree / ocr / window_cap │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ src/utils/computerUse/ │
|
||||
│ executor.ts → 按平台 dispatch │
|
||||
│ win32/ → Windows 专属能力模块 │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ packages/@ant/computer-use-{input,swift} │
|
||||
│ backends/win32.ts → PowerShell + Win32 API │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ Windows Native API │
|
||||
│ PrintWindow / EnumWindows / UI Automation │
|
||||
│ SendInput / Clipboard / OCR / ShellExecute │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 4. 实施计划
|
||||
|
||||
### Phase A:窗口绑定截图(解决核心问题)
|
||||
|
||||
**问题**:当前 `CopyFromScreen` 只能全屏截图,无法对指定窗口截图。
|
||||
**方案**:用 `PrintWindow` + `FindWindow` 实现窗口级截图。
|
||||
|
||||
| 步骤 | 文件 | 改动 |
|
||||
|------|------|------|
|
||||
| A.1 | `src/utils/computerUse/win32/windowCapture.ts` | 新建:`captureWindow(title)` 用 PrintWindow 截取指定窗口 |
|
||||
| A.2 | `src/utils/computerUse/win32/windowEnum.ts` | 新建:`listWindows()` 用 EnumWindows 返回 {hwnd, pid, title}[] |
|
||||
| A.3 | `packages/@ant/computer-use-swift/src/backends/win32.ts` | `screenshot.captureExcluding` 增加按窗口截图能力 |
|
||||
| A.4 | `packages/@ant/computer-use-swift/src/backends/win32.ts` | `apps.listRunning` 用 EnumWindows 替代 Get-Process(返回 HWND) |
|
||||
|
||||
**PowerShell 脚本核心**:
|
||||
|
||||
```powershell
|
||||
# PrintWindow 截取指定窗口
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
Add-Type -ReferencedAssemblies System.Drawing @'
|
||||
using System; using System.Runtime.InteropServices; using System.Drawing; using System.Drawing.Imaging;
|
||||
public class WinCap {
|
||||
[DllImport("user32.dll", CharSet=CharSet.Unicode)]
|
||||
public static extern IntPtr FindWindow(string c, string t);
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool GetWindowRect(IntPtr h, out RECT r);
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool PrintWindow(IntPtr h, IntPtr hdc, uint f);
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RECT { public int L, T, R, B; }
|
||||
// ... CaptureByTitle(string title) → base64
|
||||
}
|
||||
'@
|
||||
```
|
||||
|
||||
**验证标准**:
|
||||
- 能按窗口标题截图
|
||||
- 被遮挡的窗口也能截图
|
||||
- 返回 base64 + width + height
|
||||
|
||||
### Phase B:UI Automation(Windows 专属新能力)
|
||||
|
||||
**问题**:macOS 有 Accessibility API 可以读取/操作 UI 元素,Windows 当前只能坐标点击。
|
||||
**方案**:用 `System.Windows.Automation` 实现 UI 树读取和元素操作。
|
||||
|
||||
| 步骤 | 文件 | 改动 |
|
||||
|------|------|------|
|
||||
| B.1 | `src/utils/computerUse/win32/uiAutomation.ts` | 新建:核心 UIA 操作封装 |
|
||||
| B.2 | `packages/@ant/computer-use-mcp/src/tools.ts` | 增加 Windows 专属工具定义 |
|
||||
|
||||
**uiAutomation.ts 导出函数**:
|
||||
|
||||
```typescript
|
||||
// 获取窗口的 UI 元素树
|
||||
getUITree(windowTitle: string, depth: number): UIElement[]
|
||||
|
||||
// 按名称/类型/AutomationId 查找元素
|
||||
findElement(windowTitle: string, query: {name?, controlType?, automationId?}): UIElement | null
|
||||
|
||||
// 点击元素(InvokePattern)
|
||||
clickElement(windowTitle: string, automationId: string): boolean
|
||||
|
||||
// 设置元素值(ValuePattern)
|
||||
setValue(windowTitle: string, automationId: string, value: string): boolean
|
||||
|
||||
// 获取坐标处的元素
|
||||
elementAtPoint(x: number, y: number): UIElement | null
|
||||
```
|
||||
|
||||
**UIElement 类型**:
|
||||
```typescript
|
||||
interface UIElement {
|
||||
name: string
|
||||
controlType: string // Button, Edit, Text, List, etc.
|
||||
automationId: string
|
||||
boundingRect: { x: number, y: number, w: number, h: number }
|
||||
isEnabled: boolean
|
||||
value?: string // ValuePattern 可用时
|
||||
children?: UIElement[]
|
||||
}
|
||||
```
|
||||
|
||||
**PowerShell 脚本核心**:
|
||||
```powershell
|
||||
Add-Type -AssemblyName UIAutomationClient
|
||||
Add-Type -AssemblyName UIAutomationTypes
|
||||
|
||||
# 读取 UI 树
|
||||
$root = [AutomationElement]::RootElement
|
||||
$window = $root.FindFirst([TreeScope]::Children,
|
||||
[PropertyCondition]::new([AutomationElement]::NameProperty, $title))
|
||||
$elements = $window.FindAll([TreeScope]::Descendants, [Condition]::TrueCondition)
|
||||
|
||||
# 写入文本
|
||||
$element.GetCurrentPattern([ValuePattern]::Pattern).SetValue($text)
|
||||
|
||||
# 点击按钮
|
||||
$element.GetCurrentPattern([InvokePattern]::Pattern).Invoke()
|
||||
```
|
||||
|
||||
**验证标准**:
|
||||
- 能读取记事本的 UI 树(按钮、文本框、菜单)
|
||||
- 能向文本框写入内容
|
||||
- 能点击按钮
|
||||
- 能识别坐标处的元素
|
||||
|
||||
### Phase C:OCR 屏幕文字识别
|
||||
|
||||
**问题**:截图后 AI 只能看到图片,无法直接读取文字。
|
||||
**方案**:用 `Windows.Media.Ocr` 对截图进行文字识别。
|
||||
|
||||
| 步骤 | 文件 | 改动 |
|
||||
|------|------|------|
|
||||
| C.1 | `src/utils/computerUse/win32/ocr.ts` | 新建:截图 + OCR 识别 |
|
||||
| C.2 | `packages/@ant/computer-use-mcp/src/tools.ts` | 增加 `screen_ocr` 工具定义 |
|
||||
|
||||
**ocr.ts 导出函数**:
|
||||
```typescript
|
||||
// 对屏幕区域 OCR
|
||||
ocrRegion(x: number, y: number, w: number, h: number, lang?: string): OcrResult
|
||||
|
||||
// 对指定窗口 OCR
|
||||
ocrWindow(windowTitle: string, lang?: string): OcrResult
|
||||
|
||||
interface OcrResult {
|
||||
text: string
|
||||
lines: { text: string, bounds: {x,y,w,h} }[]
|
||||
language: string
|
||||
}
|
||||
```
|
||||
|
||||
**已确认可用语言**:英语 (en-US) + 中文 (zh-Hans-CN)
|
||||
|
||||
**验证标准**:
|
||||
- 能识别屏幕区域中的英文和中文
|
||||
- 返回文字内容 + 每行的位置信息
|
||||
|
||||
### Phase D:高频操作性能优化
|
||||
|
||||
**问题**:每次 PowerShell 启动 273ms,鼠标移动等高频操作太慢。
|
||||
**方案**:用 .NET `System.Windows.Forms.Clipboard` 等直接 API 替代 PowerShell 子进程。
|
||||
|
||||
| 步骤 | 文件 | 改动 |
|
||||
|------|------|------|
|
||||
| D.1 | `src/utils/computerUse/executor.ts` | 剪贴板操作用直接 API 替代 PowerShell |
|
||||
| D.2 | 考虑驻留 PowerShell 进程 | 通过 stdin/stdout 交互,摊平启动成本 |
|
||||
|
||||
**剪贴板直接 API**(不需要 PowerShell 子进程):
|
||||
```powershell
|
||||
# 读:50ms → <1ms
|
||||
[System.Windows.Forms.Clipboard]::GetText()
|
||||
|
||||
# 写:50ms → <1ms
|
||||
[System.Windows.Forms.Clipboard]::SetText($text)
|
||||
|
||||
# 图片检测
|
||||
[System.Windows.Forms.Clipboard]::ContainsImage()
|
||||
```
|
||||
|
||||
### Phase E:`request_access` Windows 适配
|
||||
|
||||
**问题**:`request_access` 依赖 macOS bundleId 识别应用,Windows 没有这个概念。
|
||||
**方案**:在 Windows 上用 exe 路径 + 窗口标题替代 bundleId。
|
||||
|
||||
| 步骤 | 文件 | 改动 |
|
||||
|------|------|------|
|
||||
| E.1 | `packages/@ant/computer-use-mcp/src/toolCalls.ts` | `resolveRequestedApps` 在 Windows 上用 exe 路径匹配 |
|
||||
| E.2 | `packages/@ant/computer-use-mcp/src/sentinelApps.ts` | 增加 Windows 危险应用列表(cmd.exe, powershell.exe 等) |
|
||||
| E.3 | `packages/@ant/computer-use-mcp/src/deniedApps.ts` | 增加 Windows 浏览器/终端识别规则 |
|
||||
| E.4 | `src/utils/computerUse/hostAdapter.ts` | `ensureOsPermissions` Windows 上检查 UAC 状态 |
|
||||
|
||||
**Windows 应用标识映射**:
|
||||
```
|
||||
macOS bundleId → Windows 等价
|
||||
com.apple.Safari → C:\Program Files\...\msedge.exe(或窗口标题匹配)
|
||||
com.google.Chrome → chrome.exe
|
||||
com.apple.Terminal → WindowsTerminal.exe / cmd.exe
|
||||
```
|
||||
|
||||
### Phase F:全局热键(ESC 拦截)
|
||||
|
||||
**问题**:当前非 darwin 直接跳过 ESC 热键,用 Ctrl+C 替代。
|
||||
**方案**:用 `RegisterHotKey` 或 `SetWindowsHookEx(WH_KEYBOARD_LL)` 实现。
|
||||
|
||||
| 步骤 | 文件 | 改动 |
|
||||
|------|------|------|
|
||||
| F.1 | `src/utils/computerUse/escHotkey.ts` | Windows 分支:RegisterHotKey 注册 ESC |
|
||||
|
||||
**优先级低**——当前 Ctrl+C fallback 可用,ESC 热键是体验优化。
|
||||
|
||||
## 5. 执行优先级
|
||||
|
||||
```
|
||||
Phase A: 窗口绑定截图 ← P0 核心需求,解决"操作其他界面"
|
||||
Phase B: UI Automation ← P0 核心能力,AI 理解 UI 结构
|
||||
Phase C: OCR ← P1 增值能力,AI 读屏幕文字
|
||||
Phase D: 性能优化 ← P1 体验优化,高频操作提速
|
||||
Phase E: request_access 适配 ← P1 功能完整性,权限模型适配
|
||||
Phase F: ESC 热键 ← P2 体验优化,可后做
|
||||
```
|
||||
|
||||
## 6. 每个 Phase 的改动量估算
|
||||
|
||||
| Phase | 新增文件 | 修改文件 | 新增代码行 | 风险 |
|
||||
|-------|---------|---------|-----------|------|
|
||||
| A 窗口截图 | 2 | 1 | ~200 | 低 |
|
||||
| B UI Automation | 1 | 1 | ~300 | 中 |
|
||||
| C OCR | 1 | 1 | ~150 | 低 |
|
||||
| D 性能优化 | 0 | 2 | ~50 | 低 |
|
||||
| E request_access | 0 | 3 | ~100 | 中 |
|
||||
| F ESC 热键 | 0 | 1 | ~50 | 低 |
|
||||
| **总计** | **4** | **9** | **~850** | — |
|
||||
|
||||
## 7. 不动的文件
|
||||
|
||||
- `backends/darwin.ts`(两个包都不动)
|
||||
- `backends/linux.ts`(两个包都不动)
|
||||
- `src/utils/computerUse/` 中 macOS 相关代码路径不动
|
||||
- `packages/@ant/computer-use-mcp/src/` 中已复制的参考项目代码不动(只追加 Windows 工具)
|
||||
|
||||
## 8. 与 macOS/Linux 方案的对比
|
||||
|
||||
| 能力 | macOS | Windows (增强后) | Linux |
|
||||
|------|-------|-----------------|-------|
|
||||
| 截图方式 | SCContentFilter (per-app) | **PrintWindow (per-window)** | scrot (全屏/区域) |
|
||||
| UI 结构 | Accessibility API | **UI Automation** | 无 |
|
||||
| OCR | 无内置 | **Windows.Media.Ocr** | 无内置 |
|
||||
| 键鼠 | CGEvent + enigo | SendInput + keybd_event | xdotool |
|
||||
| 窗口管理 | NSWorkspace | **EnumWindows + Win32** | wmctrl |
|
||||
| 剪贴板 | pbcopy/pbpaste | **Clipboard 直接 API** | xclip |
|
||||
| ESC 热键 | CGEventTap | RegisterHotKey | 无 |
|
||||
| 应用标识 | bundleId | exe 路径 + 窗口标题 | /proc + wmctrl |
|
||||
|
||||
**Windows 增强后将在 UI Automation 和 OCR 方面超过 macOS 方案**——这两项 macOS 原始实现也没有(Anthropic 用的是截图 + Claude 视觉理解,没有结构化 UI 数据)。
|
||||
@@ -1,197 +0,0 @@
|
||||
# Computer Use — macOS / Windows / Linux 跨平台实施计划
|
||||
|
||||
更新时间:2026-04-03
|
||||
参考项目:`E:\源码\claude-code-source-main\claude-code-source-main`
|
||||
|
||||
## 1. 现状
|
||||
|
||||
参考项目的 Computer Use **仅支持 macOS**——从入口到底层全部写死 darwin。我们的项目在 Phase 1-3 中已经完成了:
|
||||
|
||||
- ✅ `@ant/computer-use-mcp` stub 替换为完整实现(12 文件)
|
||||
- ✅ `@ant/computer-use-input` 拆为 dispatcher + backends(darwin + win32)
|
||||
- ✅ `@ant/computer-use-swift` 拆为 dispatcher + backends(darwin + win32)
|
||||
- ✅ `CHICAGO_MCP` 编译开关已开
|
||||
- ✅ `src/` 层 macOS 硬编码已移除(Phase 2 已完成)
|
||||
|
||||
## 2. 阻塞点全景
|
||||
|
||||
### 2.1 入口层
|
||||
|
||||
| # | 文件:行号 | 阻塞代码 | 影响 |
|
||||
|---|----------|---------|------|
|
||||
| 1 | `src/main.tsx:2366` | `feature("CHICAGO_MCP")` 门控 | CU 初始化入口 |
|
||||
|
||||
### 2.2 加载层
|
||||
|
||||
| # | 文件:行号 | 阻塞代码 | 影响 |
|
||||
|---|----------|---------|------|
|
||||
| 2 | `src/utils/computerUse/swiftLoader.ts` | macOS-only loader(已改为仅 darwin 加载) | 非 darwin 使用 platforms/ 替代 |
|
||||
| 3 | `src/utils/computerUse/executor.ts:302` | `process.platform !== 'darwin'` → cross-platform executor | 非 darwin 走跨平台路径 |
|
||||
|
||||
### 2.3 macOS 特有依赖
|
||||
|
||||
| # | 文件:行号 | 依赖 | macOS 实现 | 需要替代方案 |
|
||||
|---|----------|------|-----------|------------|
|
||||
| 4 | `executor.ts:72-96` | 剪贴板 | `pbcopy`/`pbpaste` / PowerShell / xclip | Win: PowerShell `Get/Set-Clipboard`;Linux: `xclip`/`wl-copy` |
|
||||
| 5 | `drainRunLoop.ts` | CFRunLoop pump | `cu._drainMainRunLoop()` | 非 darwin:直接执行 fn(),不需要 pump |
|
||||
| 6 | `escHotkey.ts` | ESC 热键 | CGEventTap | 非 darwin:返回 false(已有 Ctrl+C fallback) |
|
||||
| 7 | `hostAdapter.ts` | 系统权限 | TCC accessibility + screenRecording | Win:直接 granted;Linux:检查 xdotool |
|
||||
| 8 | `common.ts:55-58` | 平台标识 | 动态获取 | 已改为 `process.platform` 分发 |
|
||||
| 9 | `executor.ts:232` | 粘贴快捷键 | `command`/`ctrl` 分发 | 已按平台分发粘贴快捷键 |
|
||||
|
||||
### 2.4 缺失的 Linux 后端
|
||||
|
||||
| 包 | macOS | Windows | Linux |
|
||||
|---|-------|---------|-------|
|
||||
| `computer-use-input/backends/` | ✅ darwin.ts | ✅ win32.ts | ❌ 需新建 linux.ts |
|
||||
| `computer-use-swift/backends/` | ✅ darwin.ts | ✅ win32.ts | ❌ 需新建 linux.ts |
|
||||
|
||||
## 3. 每个平台的能力依赖
|
||||
|
||||
### 3.1 computer-use-input(键鼠)
|
||||
|
||||
| 功能 | macOS | Windows | Linux |
|
||||
|------|-------|---------|-------|
|
||||
| 鼠标移动 | CGEvent JXA | SetCursorPos P/Invoke | xdotool mousemove |
|
||||
| 鼠标点击 | CGEvent JXA | SendInput P/Invoke | xdotool click |
|
||||
| 鼠标滚轮 | CGEvent JXA | SendInput MOUSEEVENTF_WHEEL | xdotool scroll |
|
||||
| 键盘按键 | System Events osascript | keybd_event P/Invoke | xdotool key |
|
||||
| 组合键 | System Events osascript | keybd_event 组合 | xdotool key combo |
|
||||
| 文本输入 | System Events keystroke | SendKeys.SendWait | xdotool type |
|
||||
| 前台应用 | System Events osascript | GetForegroundWindow P/Invoke | xdotool getactivewindow + /proc |
|
||||
| 工具依赖 | osascript(内置) | powershell(内置) | xdotool(需安装) |
|
||||
|
||||
### 3.2 computer-use-swift(截图 + 应用管理)
|
||||
|
||||
| 功能 | macOS | Windows | Linux |
|
||||
|------|-------|---------|-------|
|
||||
| 全屏截图 | screencapture | CopyFromScreen | gnome-screenshot / scrot / grim |
|
||||
| 区域截图 | screencapture -R | CopyFromScreen(rect) | gnome-screenshot -a / scrot -a / grim -g |
|
||||
| 显示器列表 | CGGetActiveDisplayList JXA | Screen.AllScreens | xrandr --query |
|
||||
| 运行中应用 | System Events JXA | Get-Process | wmctrl -l / ps |
|
||||
| 打开应用 | osascript activate | Start-Process | xdg-open / gtk-launch |
|
||||
| 隐藏/显示 | System Events visibility | ShowWindow/SetForegroundWindow | wmctrl -c / xdotool |
|
||||
| 工具依赖 | screencapture + osascript | powershell | xdotool + scrot/grim + wmctrl |
|
||||
|
||||
### 3.3 executor 层
|
||||
|
||||
| 功能 | macOS | Windows | Linux |
|
||||
|------|-------|---------|-------|
|
||||
| drainRunLoop | CFRunLoop pump | 不需要 | 不需要 |
|
||||
| ESC 热键 | CGEventTap | 跳过(Ctrl+C fallback) | 跳过(Ctrl+C fallback) |
|
||||
| 剪贴板读 | pbpaste | `powershell Get-Clipboard` | xclip -o / wl-paste |
|
||||
| 剪贴板写 | pbcopy | `powershell Set-Clipboard` | xclip / wl-copy |
|
||||
| 粘贴快捷键 | command+v | ctrl+v | ctrl+v |
|
||||
| 终端检测 | __CFBundleIdentifier | WT_SESSION / TERM_PROGRAM | TERM_PROGRAM |
|
||||
| 系统权限 | TCC check | 直接 granted | 检查 xdotool 安装 |
|
||||
|
||||
## 4. 执行步骤
|
||||
|
||||
### Phase 1:已完成 ✅
|
||||
|
||||
- [x] `@ant/computer-use-mcp` stub → 完整实现
|
||||
- [x] `@ant/computer-use-input` dispatcher + darwin/win32 backends
|
||||
- [x] `@ant/computer-use-swift` dispatcher + darwin/win32 backends
|
||||
- [x] `CHICAGO_MCP` 编译开关
|
||||
|
||||
### Phase 2:移除 6 处 macOS 硬编码(解锁 macOS + Windows)
|
||||
|
||||
**改动原则:macOS 代码路径不变,只在每处 darwin 守卫后加 win32/linux 分支。**
|
||||
|
||||
| 步骤 | 文件 | 改动 |
|
||||
|------|------|------|
|
||||
| 2.1 | `src/main.tsx:2366` | `feature("CHICAGO_MCP")` → 已为跨平台入口 |
|
||||
| 2.2 | `src/utils/computerUse/swiftLoader.ts` | 已改为仅 darwin 加载,非 darwin 使用 platforms/ |
|
||||
| 2.3 | `src/utils/computerUse/executor.ts:302-309` | 已改为 cross-platform dispatch(非 darwin → createCrossPlatformExecutor) |
|
||||
| 2.4 | `src/utils/computerUse/executor.ts:72-96` | 剪贴板已按平台分发:darwin→pbcopy/pbpaste,win32→PowerShell,linux→xclip |
|
||||
| 2.5 | `src/utils/computerUse/executor.ts:232` | 粘贴快捷键已按平台分发:darwin→command,其他→ctrl |
|
||||
| 2.6 | `src/utils/computerUse/executor.ts:302-309` | 非 darwin 已改为 `createCrossPlatformExecutor()` |
|
||||
| 2.7 | `src/utils/computerUse/drainRunLoop.ts` | 非 darwin 无需 pump(直接执行 fn) |
|
||||
| 2.8 | `src/utils/computerUse/escHotkey.ts` | 非 darwin 返回 false(已有 Ctrl+C fallback) |
|
||||
| 2.9 | `src/utils/computerUse/hostAdapter.ts` | 非 darwin 权限检查逻辑已实现 |
|
||||
| 2.10 | `src/utils/computerUse/common.ts:58` | 已改为动态 `process.platform` 分发 |
|
||||
| 2.11 | `src/utils/computerUse/common.ts:55` | 已改为 darwin→'native',其他→'none' |
|
||||
| 2.12 | `src/utils/computerUse/gates.ts:55` | 已更新(需验证 enabled 默认值) |
|
||||
| 2.13 | `src/utils/computerUse/gates.ts:39` | `hasRequiredSubscription()` 已更新 |
|
||||
|
||||
### Phase 3:新增 Linux 后端
|
||||
|
||||
| 步骤 | 文件 | 内容 |
|
||||
|------|------|------|
|
||||
| 3.1 | `packages/@ant/computer-use-input/src/backends/linux.ts` | xdotool 键鼠(mousemove/click/key/type/getactivewindow) |
|
||||
| 3.2 | `packages/@ant/computer-use-swift/src/backends/linux.ts` | scrot/grim 截图 + xrandr 显示器 + wmctrl 窗口管理 |
|
||||
| 3.3 | `packages/@ant/computer-use-input/src/index.ts` | dispatcher 加 `case 'linux'` |
|
||||
| 3.4 | `packages/@ant/computer-use-swift/src/index.ts` | dispatcher 加 `case 'linux'` |
|
||||
|
||||
### Phase 4:验证
|
||||
|
||||
| 测试项 | macOS | Windows | Linux |
|
||||
|--------|-------|---------|-------|
|
||||
| build 成功 | ✅ | 验证 | 验证 |
|
||||
| MCP 工具列表非空 | 验证 | 验证 | 验证 |
|
||||
| 鼠标移动 | 验证 | ✅ 已通过 | 验证 |
|
||||
| 截图 | 验证 | ✅ 已通过 | 验证 |
|
||||
| 键盘输入 | 验证 | 验证 | 验证 |
|
||||
| 前台窗口 | 验证 | ✅ 已通过 | 验证 |
|
||||
| 剪贴板 | 验证 | 验证 | 验证 |
|
||||
|
||||
## 5. 文件改动总览
|
||||
|
||||
### 不动的文件(14 个)
|
||||
|
||||
`cleanup.ts`、`computerUseLock.ts`、`wrapper.tsx`、`toolRendering.tsx`、`mcpServer.ts`、`setup.ts`、`appNames.ts`、`inputLoader.ts`、`src/services/mcp/client.ts`、`@ant/computer-use-mcp/src/*`(Phase 1 已完成)、`backends/darwin.ts`(两个包都不动)
|
||||
|
||||
### 改 src/ 的文件(8 个)
|
||||
|
||||
| 文件 | 改动量 | 风险 |
|
||||
|------|--------|------|
|
||||
| `main.tsx` | 1 行 | 低 |
|
||||
| `swiftLoader.ts` | 2 行 | 低 |
|
||||
| `executor.ts` | ~40 行(剪贴板分发 + 平台守卫 + paste 快捷键) | **中** |
|
||||
| `drainRunLoop.ts` | 1 行 | 低 |
|
||||
| `escHotkey.ts` | 3 行 | 低 |
|
||||
| `hostAdapter.ts` | 5 行 | 低 |
|
||||
| `common.ts` | 3 行 | 低 |
|
||||
| `gates.ts` | 3 行 | 低 |
|
||||
|
||||
### 新增文件(2 个)
|
||||
|
||||
| 文件 | 行数估算 |
|
||||
|------|---------|
|
||||
| `packages/@ant/computer-use-input/src/backends/linux.ts` | ~150 行 |
|
||||
| `packages/@ant/computer-use-swift/src/backends/linux.ts` | ~200 行 |
|
||||
|
||||
## 6. Linux 依赖工具
|
||||
|
||||
| 工具 | 用途 | 安装命令(Ubuntu) |
|
||||
|------|------|-------------------|
|
||||
| `xdotool` | 键鼠模拟 + 窗口管理 | `sudo apt install xdotool` |
|
||||
| `scrot` 或 `gnome-screenshot` | 截图 | `sudo apt install scrot` |
|
||||
| `xrandr` | 显示器信息 | 通常已预装 |
|
||||
| `xclip` | 剪贴板 | `sudo apt install xclip` |
|
||||
| `wmctrl` | 窗口列表/切换 | `sudo apt install wmctrl` |
|
||||
|
||||
Wayland 环境需要替代工具:`ydotool`(替代 xdotool)、`grim`(替代 scrot)、`wl-clipboard`(替代 xclip)。初期可先只支持 X11,Wayland 标记为 todo。
|
||||
|
||||
## 7. 执行顺序建议
|
||||
|
||||
```
|
||||
Phase 2(解锁 macOS + Windows)
|
||||
├── 2.1-2.3 移除 3 处硬编码 throw/skip
|
||||
├── 2.4-2.5 剪贴板 + 粘贴快捷键平台分发
|
||||
├── 2.6 swiftLoader → 直接实例化
|
||||
├── 2.7-2.9 drainRunLoop / escHotkey / permissions 平台分支
|
||||
├── 2.10-2.11 common.ts 平台标识动态化
|
||||
├── 2.12-2.13 gates.ts 默认值
|
||||
└── 验证 Windows
|
||||
|
||||
Phase 3(Linux 后端)
|
||||
├── 3.1 input/backends/linux.ts
|
||||
├── 3.2 swift/backends/linux.ts
|
||||
├── 3.3-3.4 dispatcher 加 linux case
|
||||
└── 验证 Linux
|
||||
|
||||
Phase 4(集成验证 + PR)
|
||||
```
|
||||
|
||||
每个 Phase 可独立验证、独立提交。Phase 2 完成后 macOS + Windows 可用,Phase 3 完成后三平台全部可用。
|
||||
@@ -1,140 +0,0 @@
|
||||
# CONTEXT_COLLAPSE — 上下文折叠
|
||||
|
||||
> Feature Flag: `FEATURE_CONTEXT_COLLAPSE=1`
|
||||
> 子 Feature: `FEATURE_HISTORY_SNIP=1`
|
||||
> 实现状态:核心逻辑全部 Stub,布线完整
|
||||
> 引用数:CONTEXT_COLLAPSE 20 + HISTORY_SNIP 16 = 36
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
CONTEXT_COLLAPSE 让模型内省上下文窗口使用情况,并智能压缩旧消息。当对话接近上下文限制时,自动将旧消息折叠为压缩摘要,保留关键信息的同时释放 token 空间。
|
||||
|
||||
### 子 Feature
|
||||
|
||||
| Feature | 功能 |
|
||||
|---------|------|
|
||||
| `CONTEXT_COLLAPSE` | 上下文折叠引擎(后台 LLM 调用压缩旧消息) |
|
||||
| `HISTORY_SNIP` | SnipTool — 标记消息进行折叠/修剪 |
|
||||
|
||||
## 二、实现架构
|
||||
|
||||
### 2.1 模块状态
|
||||
|
||||
| 模块 | 文件 | 状态 |
|
||||
|------|------|------|
|
||||
| 折叠核心 | `src/services/contextCollapse/index.ts` | **Stub** — 接口完整(`ContextCollapseStats`、`CollapseResult`、`DrainResult`),函数全部空操作 |
|
||||
| 折叠操作 | `src/services/contextCollapse/operations.ts` | **Stub** — `projectView` 为恒等函数 |
|
||||
| 折叠持久化 | `src/services/contextCollapse/persist.ts` | **Stub** — `restoreFromEntries` 为空操作 |
|
||||
| CtxInspectTool | `packages/builtin-tools/src/tools/CtxInspectTool/CtxInspectTool.ts` | **实现** — 上下文内省工具 |
|
||||
| SnipTool 提示 | `src/tools/SnipTool/prompt.ts` | **Stub** — 空工具名 |
|
||||
| SnipTool 实现 | `src/tools/SnipTool/SnipTool.ts` | **缺失** |
|
||||
| force-snip 命令 | `src/commands/force-snip.js` | **缺失** |
|
||||
| 折叠读取搜索 | `src/utils/collapseReadSearch.ts` | **完整** — Snip 作为静默吸收操作 |
|
||||
| QueryEngine 集成 | `src/QueryEngine.ts` | **布线** — 导入并使用 snip 投影 |
|
||||
| Token 警告 UI | `src/components/TokenWarning.tsx` | **布线** — 折叠进度标签 |
|
||||
|
||||
### 2.2 核心接口(已定义,待实现)
|
||||
|
||||
```ts
|
||||
// contextCollapse/index.ts
|
||||
interface ContextCollapseStats {
|
||||
// 上下文使用统计
|
||||
}
|
||||
interface CollapseResult {
|
||||
// 折叠操作结果
|
||||
}
|
||||
interface DrainResult {
|
||||
// 紧急释放结果
|
||||
}
|
||||
|
||||
// 关键函数(全部 stub):
|
||||
isContextCollapseEnabled() // → false
|
||||
applyCollapsesIfNeeded(messages) // 透传
|
||||
recoverFromOverflow(messages) // 透传(413 恢复)
|
||||
initContextCollapse() // 空操作
|
||||
```
|
||||
|
||||
### 2.3 预期数据流
|
||||
|
||||
```
|
||||
对话持续增长
|
||||
│
|
||||
▼
|
||||
上下文接近限制(由 query.ts 检测)
|
||||
│
|
||||
├── 溢出检测 (query.ts:440,616,802)
|
||||
│
|
||||
▼
|
||||
applyCollapsesIfNeeded(messages) [需要实现]
|
||||
│
|
||||
├── 后台 LLM 调用压缩旧消息
|
||||
├── 保留关键信息(决策、文件路径、错误)
|
||||
└── 替换旧消息为压缩摘要
|
||||
│
|
||||
├── 413 恢复 (query.ts:1093,1179)
|
||||
│ └── recoverFromOverflow() 紧急折叠
|
||||
│
|
||||
▼
|
||||
projectView() 过滤折叠后的消息视图
|
||||
│
|
||||
▼
|
||||
模型继续工作(在压缩后的上下文中)
|
||||
```
|
||||
|
||||
### 2.4 HISTORY_SNIP 子功能
|
||||
|
||||
SnipTool 提供手动折叠能力:
|
||||
|
||||
- `/force-snip` 命令 — 强制执行折叠
|
||||
- SnipTool — 标记特定消息进行折叠/修剪
|
||||
- `collapseReadSearch.ts` 已完整实现,将 Snip 作为静默吸收操作处理
|
||||
|
||||
### 2.5 集成点
|
||||
|
||||
| 文件 | 位置 | 说明 |
|
||||
|------|------|------|
|
||||
| `src/query.ts` | 18,440,616,802,1093,1179 | 溢出检测、413 恢复、折叠应用 |
|
||||
| `src/QueryEngine.ts` | 124,127,1301 | Snip 投影使用 |
|
||||
| `src/utils/analyzeContext.ts` | 1122 | 跳过保留缓冲区显示 |
|
||||
| `src/utils/sessionRestore.ts` | 127,494 | 恢复折叠状态 |
|
||||
| `src/services/compact/autoCompact.ts` | 179,215 | 自动压缩时考虑折叠 |
|
||||
|
||||
## 三、需要补全的内容
|
||||
|
||||
| 优先级 | 模块 | 工作量 | 说明 |
|
||||
|--------|------|--------|------|
|
||||
| 1 | `services/contextCollapse/index.ts` | 大 | 折叠状态机、LLM 调用、消息压缩 |
|
||||
| 2 | `services/contextCollapse/operations.ts` | 中 | `projectView()` 消息过滤 |
|
||||
| 3 | `services/contextCollapse/persist.ts` | 小 | `restoreFromEntries()` 磁盘持久化 |
|
||||
| 4 | `tools/CtxInspectTool/` | 已完成 | 上下文内省工具已实现(`packages/builtin-tools/src/tools/CtxInspectTool/`) |
|
||||
| 5 | `tools/SnipTool/SnipTool.ts` | 中 | Snip 工具实现 |
|
||||
| 6 | `commands/force-snip.js` | 小 | `/force-snip` 命令 |
|
||||
|
||||
## 四、关键设计决策
|
||||
|
||||
1. **后台 LLM 压缩**:折叠不是简单截断,而是用 LLM 生成压缩摘要保留关键信息
|
||||
2. **413 恢复**:当 API 返回 413(请求过大)时,紧急折叠是最重要的恢复手段
|
||||
3. **与 autoCompact 协作**:折叠和自动压缩(compact)是不同的机制,折叠在消息级别,压缩在对话级别
|
||||
4. **持久化**:折叠状态持久化到磁盘,会话恢复时重载
|
||||
|
||||
## 五、使用方式
|
||||
|
||||
```bash
|
||||
# 启用 context collapse
|
||||
FEATURE_CONTEXT_COLLAPSE=1 bun run dev
|
||||
|
||||
# 启用 snip 子功能
|
||||
FEATURE_CONTEXT_COLLAPSE=1 FEATURE_HISTORY_SNIP=1 bun run dev
|
||||
```
|
||||
|
||||
## 六、文件索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/services/contextCollapse/index.ts` | 折叠核心(stub,接口已定义) |
|
||||
| `src/services/contextCollapse/operations.ts` | 投影操作(stub) |
|
||||
| `src/services/contextCollapse/persist.ts` | 持久化(stub) |
|
||||
| `src/utils/collapseReadSearch.ts` | Snip 吸收操作(完整) |
|
||||
| `src/query.ts` | 溢出检测和 413 恢复集成 |
|
||||
| `src/QueryEngine.ts` | Snip 投影使用 |
|
||||
| `src/components/TokenWarning.tsx` | 折叠进度 UI |
|
||||
@@ -1,151 +0,0 @@
|
||||
# COORDINATOR_MODE — 多 Agent 编排
|
||||
|
||||
> Feature Flag: `FEATURE_COORDINATOR_MODE=1` + 环境变量 `CLAUDE_CODE_COORDINATOR_MODE=1`
|
||||
> 实现状态:编排者完整可用,worker agent 为通用 AgentTool worker
|
||||
> 引用数:32
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
COORDINATOR_MODE 将 CLI 变为"编排者"角色。编排者不直接操作文件,而是通过 AgentTool 派发任务给多个 worker 并行执行。适用于大型任务拆分、并行研究、实现+验证分离等场景。
|
||||
|
||||
### 核心约束
|
||||
|
||||
- 编排者只能使用:`Agent`(派发 worker)、`SendMessage`(继续 worker)、`TaskStop`(停止 worker)
|
||||
- Worker 可以使用所有标准工具(Bash、Read、Edit 等)+ MCP 工具 + Skill 工具
|
||||
- 编排者的每条消息都是给用户看的;worker 结果以 `<task-notification>` XML 形式到达
|
||||
|
||||
## 二、用户交互
|
||||
|
||||
### 启用方式
|
||||
|
||||
```bash
|
||||
FEATURE_COORDINATOR_MODE=1 CLAUDE_CODE_COORDINATOR_MODE=1 bun run dev
|
||||
```
|
||||
|
||||
需要同时设置 feature flag 和环境变量。`CLAUDE_CODE_COORDINATOR_MODE` 可在会话恢复时自动切换(`matchSessionMode`)。
|
||||
|
||||
### 典型工作流
|
||||
|
||||
```
|
||||
用户: "修复 auth 模块的 null pointer"
|
||||
|
||||
编排者:
|
||||
1. 并行派发两个 worker:
|
||||
- Agent({ description: "调查 auth bug", prompt: "..." })
|
||||
- Agent({ description: "研究 auth 测试", prompt: "..." })
|
||||
|
||||
2. 收到 <task-notification>:
|
||||
- Worker A: "在 validate.ts:42 发现 null pointer"
|
||||
- Worker B: "测试覆盖情况..."
|
||||
|
||||
3. 综合发现,继续 Worker A:
|
||||
- SendMessage({ to: "agent-a1b", message: "修复 validate.ts:42..." })
|
||||
|
||||
4. 收到修复结果,派发验证:
|
||||
- Agent({ description: "验证修复", prompt: "..." })
|
||||
```
|
||||
|
||||
## 三、实现架构
|
||||
|
||||
### 3.1 模式检测
|
||||
|
||||
文件:`src/coordinator/coordinatorMode.ts:36-41`
|
||||
|
||||
```ts
|
||||
export function isCoordinatorMode(): boolean {
|
||||
return feature('COORDINATOR_MODE') &&
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE)
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 会话模式恢复
|
||||
|
||||
`matchSessionMode(sessionMode)` 在恢复旧会话时检查存储的模式,如果当前环境变量与存储不一致,自动翻转环境变量。防止在普通模式下恢复编排会话(或反之)。
|
||||
|
||||
### 3.3 Worker 工具集
|
||||
|
||||
`getCoordinatorUserContext()` 告知编排者 worker 可用的工具列表:
|
||||
|
||||
- **标准模式**:`ASYNC_AGENT_ALLOWED_TOOLS` 排除内部工具(TeamCreate、TeamDelete、SendMessage、SyntheticOutput)
|
||||
- **Simple 模式**(`CLAUDE_CODE_SIMPLE=1`):仅 Bash、Read、Edit
|
||||
- **MCP 工具**:列出已连接的 MCP 服务器名称
|
||||
- **Scratchpad**:如果 GrowthBook `tengu_scratch` 启用,提供跨 worker 共享的 scratchpad 目录
|
||||
|
||||
### 3.4 系统提示
|
||||
|
||||
文件:`src/coordinator/coordinatorMode.ts:111-369`
|
||||
|
||||
编排者系统提示(`getCoordinatorSystemPrompt()`)约 370 行,包含:
|
||||
|
||||
| 章节 | 内容 |
|
||||
|------|------|
|
||||
| 1. Your Role | 编排者职责定义 |
|
||||
| 2. Your Tools | Agent/SendMessage/TaskStop 使用说明 |
|
||||
| 3. Workers | Worker 能力和限制 |
|
||||
| 4. Task Workflow | Research → Synthesis → Implementation → Verification 流程 |
|
||||
| 5. Writing Worker Prompts | 自包含 prompt 编写指南 + 好坏示例对比 |
|
||||
| 6. Example Session | 完整示例对话 |
|
||||
|
||||
### 3.5 Worker Agent
|
||||
|
||||
文件:`src/coordinator/workerAgent.ts`
|
||||
|
||||
当前为 stub。Worker 实际使用通用 AgentTool 的 `worker` subagent_type。
|
||||
|
||||
### 3.6 数据流
|
||||
|
||||
```
|
||||
用户消息
|
||||
│
|
||||
▼
|
||||
编排者 REPL(受限工具集)
|
||||
│
|
||||
├──→ Agent({ subagent_type: "worker", prompt: "..." })
|
||||
│ │
|
||||
│ ▼
|
||||
│ Worker Agent(完整工具集)
|
||||
│ ├── 执行任务(Bash/Read/Edit/...)
|
||||
│ └── 返回 <task-notification>
|
||||
│
|
||||
├──→ SendMessage({ to: "agent-id", message: "..." })
|
||||
│ │
|
||||
│ ▼
|
||||
│ 继续已存在的 Worker
|
||||
│
|
||||
└──→ TaskStop({ task_id: "agent-id" })
|
||||
│
|
||||
▼
|
||||
停止运行中的 Worker
|
||||
```
|
||||
|
||||
## 四、关键设计决策
|
||||
|
||||
1. **双开关设计**:feature flag 控制代码可用性,环境变量控制实际激活。允许编译时包含但不默认启用
|
||||
2. **编排者受限**:只能用 Agent/SendMessage/TaskStop,确保编排者专注于派发而非执行
|
||||
3. **Worker 不可见编排者对话**:每个 worker 的 prompt 必须自包含(所有必要上下文)
|
||||
4. **并行优先**:系统提示强调"Parallelism is your superpower",鼓励并行派发独立任务
|
||||
5. **综合而非转发**:编排者必须理解 worker 发现,再写出具体的实现指令。禁止 "based on your findings" 类懒惰委托
|
||||
6. **Scratchpad 可选共享**:通过 GrowthBook 门控的共享目录,让 worker 之间持久化共享知识
|
||||
|
||||
## 五、使用方式
|
||||
|
||||
```bash
|
||||
# 基本启用
|
||||
FEATURE_COORDINATOR_MODE=1 CLAUDE_CODE_COORDINATOR_MODE=1 bun run dev
|
||||
|
||||
# 配合 Fork Subagent
|
||||
FEATURE_COORDINATOR_MODE=1 FEATURE_FORK_SUBAGENT=1 \
|
||||
CLAUDE_CODE_COORDINATOR_MODE=1 bun run dev
|
||||
|
||||
# Simple 模式(worker 只有 Bash/Read/Edit)
|
||||
FEATURE_COORDINATOR_MODE=1 CLAUDE_CODE_COORDINATOR_MODE=1 \
|
||||
CLAUDE_CODE_SIMPLE=1 bun run dev
|
||||
```
|
||||
|
||||
## 六、文件索引
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `src/coordinator/coordinatorMode.ts` | 370 | 模式检测 + 系统提示 + 用户上下文 |
|
||||
| `src/coordinator/workerAgent.ts` | — | Worker agent 定义(stub) |
|
||||
| `src/constants/tools.ts` | — | `ASYNC_AGENT_ALLOWED_TOOLS` 工具白名单 |
|
||||
@@ -1,318 +0,0 @@
|
||||
# Daemon 重构设计方案
|
||||
|
||||
> 分支: `feat/integrate-5-branches`
|
||||
> 基于: `f41745cb` (= main `11bb3f62` 内容)
|
||||
> 日期: 2026-04-13
|
||||
|
||||
## 一、问题概述
|
||||
|
||||
### 1.1 命令结构散乱
|
||||
|
||||
当前后台进程相关的命令分布在三个不同的位置,没有统一的命名空间:
|
||||
|
||||
| 命令 | 注册位置 | 入口 |
|
||||
|------|---------|------|
|
||||
| `claude daemon start/status/stop` | `cli.tsx` 快速路径 L203 | `daemon/main.ts` |
|
||||
| `claude ps` | `cli.tsx` 快速路径 L220 | `cli/bg.ts` |
|
||||
| `claude logs <x>` | `cli.tsx` 快速路径 L232 | `cli/bg.ts` |
|
||||
| `claude attach <x>` | `cli.tsx` 快速路径 L236 | `cli/bg.ts` |
|
||||
| `claude kill <x>` | `cli.tsx` 快速路径 L238 | `cli/bg.ts` |
|
||||
| `claude --bg` | `cli.tsx` 快速路径 L244 | `cli/bg.ts` |
|
||||
| `claude new/list/reply` | `cli.tsx` 快速路径 L250 | `cli/handlers/templateJobs.ts` |
|
||||
| `claude rollback` | `main.tsx` Commander.js L6525 | `cli/rollback.ts` |
|
||||
| `claude up` | `main.tsx` Commander.js L6511 | `cli/up.ts` |
|
||||
|
||||
**问题**:
|
||||
- `ps/logs/attach/kill` 与 `daemon` 逻辑上都是后台进程管理,但互不关联
|
||||
- 这些命令都**只有 CLI 入口**,REPL 里输入 `/daemon` 或 `/ps` 不存在
|
||||
- `new/list/reply` 是模板任务系统的顶级命令,容易与其他命令冲突(特别是 `list`)
|
||||
|
||||
### 1.2 Windows 不支持
|
||||
|
||||
`--bg` 和 `attach` 硬依赖 tmux:
|
||||
- `bg.ts:handleBgFlag()` 第一步就检查 tmux,不可用直接报错退出
|
||||
- `bg.ts:attachHandler()` 用 `tmux attach-session`,无 tmux 替代方案
|
||||
- Windows (包括 VS Code 终端) 完全无法使用后台会话功能
|
||||
|
||||
### 1.3 无 REPL 入口
|
||||
|
||||
对比 `/mcp` 的双注册模式:
|
||||
- **CLI**: `claude mcp serve/add/remove/list` (Commander.js, `main.tsx:5760`)
|
||||
- **REPL**: `/mcp enable/disable/reconnect` (slash command, `commands/mcp/index.ts`)
|
||||
|
||||
`daemon`/`bg`/`job` 系列只有 CLI 快速路径,REPL 中完全不可用。
|
||||
|
||||
## 二、目标
|
||||
|
||||
1. **层级化命令结构**: 参照 `/mcp` 模式,将后台管理收归 `/daemon`,模板任务收归 `/job`
|
||||
2. **跨平台后台会话**: Windows / macOS / Linux 都能启动、附着、终止后台会话
|
||||
3. **双注册**: CLI (`claude daemon ...`) + REPL (`/daemon ...`) 同时可用
|
||||
4. **向后兼容**: 旧命令保留但输出 deprecation 提示
|
||||
|
||||
## 三、命令结构设计
|
||||
|
||||
### 3.1 `/daemon` — 后台进程管理
|
||||
|
||||
合并 daemon supervisor + bg sessions 为统一命名空间:
|
||||
|
||||
```
|
||||
claude daemon <subcommand> ← CLI 入口 (cli.tsx 快速路径)
|
||||
/daemon <subcommand> ← REPL 入口 (slash command, local-jsx)
|
||||
|
||||
子命令:
|
||||
status 综合状态面板 (daemon + 所有会话)
|
||||
start [--dir <path>] 启动 daemon supervisor
|
||||
stop 停止 daemon
|
||||
bg [args...] 启动后台会话
|
||||
attach [target] 附着到后台会话
|
||||
logs [target] 查看会话日志
|
||||
kill [target] 终止会话
|
||||
(无参数) 等同于 status
|
||||
```
|
||||
|
||||
**CLI 快速路径路由** (`cli.tsx`):
|
||||
```typescript
|
||||
// 新: 统一入口
|
||||
if (feature('DAEMON') && args[0] === 'daemon') {
|
||||
const sub = args[1] || 'status'
|
||||
switch (sub) {
|
||||
case 'start': case 'stop': case 'status':
|
||||
await daemonMain([sub, ...args.slice(2)])
|
||||
break
|
||||
case 'bg':
|
||||
await bg.handleBgStart(args.slice(2))
|
||||
break
|
||||
case 'attach': case 'logs': case 'kill':
|
||||
await bg[`${sub}Handler`](args[2])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 向后兼容 (deprecated)
|
||||
if (feature('BG_SESSIONS') && ['ps','logs','attach','kill'].includes(args[0])) {
|
||||
console.warn(`[deprecated] Use: claude daemon ${args[0] === 'ps' ? 'status' : args[0]}`)
|
||||
// ... delegate to daemon subcommand
|
||||
}
|
||||
```
|
||||
|
||||
**REPL 斜杠命令** (`commands/daemon/index.ts`):
|
||||
```typescript
|
||||
const daemon = {
|
||||
type: 'local-jsx',
|
||||
name: 'daemon',
|
||||
description: 'Manage background sessions and daemon',
|
||||
argumentHint: '[status|start|stop|bg|attach|logs|kill]',
|
||||
isEnabled: () => feature('DAEMON') || feature('BG_SESSIONS'),
|
||||
load: () => import('./daemon.js'),
|
||||
} satisfies Command
|
||||
```
|
||||
|
||||
### 3.2 `/job` — 模板任务管理
|
||||
|
||||
```
|
||||
claude job <subcommand> ← CLI 入口
|
||||
/job <subcommand> ← REPL 入口
|
||||
|
||||
子命令:
|
||||
list 列出模板和活跃任务
|
||||
new <template> [args] 从模板创建任务
|
||||
reply <id> <text> 回复任务
|
||||
status <id> 查看任务状态
|
||||
(无参数) 等同于 list
|
||||
```
|
||||
|
||||
### 3.3 独立命令 (不变)
|
||||
|
||||
```
|
||||
claude up 保持顶级 (简短的 bootstrap 命令)
|
||||
claude rollback [target] 保持顶级 (低频运维命令)
|
||||
```
|
||||
|
||||
## 四、跨平台后台引擎
|
||||
|
||||
### 4.1 引擎抽象
|
||||
|
||||
```typescript
|
||||
// src/cli/bg/engine.ts
|
||||
export interface BgEngine {
|
||||
readonly name: string
|
||||
|
||||
/** 当前平台是否可用 */
|
||||
available(): Promise<boolean>
|
||||
|
||||
/** 启动后台会话 */
|
||||
start(opts: BgStartOptions): Promise<BgStartResult>
|
||||
|
||||
/** 附着到后台会话(blocking) */
|
||||
attach(session: SessionEntry): Promise<void>
|
||||
}
|
||||
|
||||
export interface BgStartOptions {
|
||||
sessionName: string
|
||||
args: string[]
|
||||
env: Record<string, string | undefined>
|
||||
logPath: string
|
||||
cwd: string
|
||||
}
|
||||
|
||||
export interface BgStartResult {
|
||||
pid: number
|
||||
sessionName: string
|
||||
logPath: string
|
||||
engineUsed: string
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 三种引擎实现
|
||||
|
||||
| 引擎 | 平台 | 启动方式 | attach 方式 |
|
||||
|------|------|---------|------------|
|
||||
| TmuxEngine | macOS/Linux (有 tmux) | `tmux new-session -d` | `tmux attach-session` |
|
||||
| DetachedEngine | Windows / 无 tmux 的 macOS/Linux | `spawn({ detached, stdio→logFile })` | `tail -f` 日志文件 |
|
||||
|
||||
#### DetachedEngine 详细设计
|
||||
|
||||
**启动 (`start`)**:
|
||||
```typescript
|
||||
// 1. 打开日志文件 fd
|
||||
const logFd = fs.openSync(logPath, 'a')
|
||||
// 2. detached spawn, stdout/stderr 重定向到日志
|
||||
const child = spawn(process.execPath, execArgs, {
|
||||
detached: true,
|
||||
stdio: ['ignore', logFd, logFd],
|
||||
env,
|
||||
cwd,
|
||||
})
|
||||
child.unref()
|
||||
fs.closeSync(logFd)
|
||||
// 3. 写 sessions/<PID>.json
|
||||
```
|
||||
|
||||
**附着 (`attach`)**:
|
||||
```typescript
|
||||
// 跨平台 tail -f 实现
|
||||
// 1. 读取已有日志内容输出到 stdout
|
||||
// 2. fs.watch(logPath) 监听变化
|
||||
// 3. 每次变化读取新增内容
|
||||
// 4. Ctrl+C 退出 tail(不杀后台进程)
|
||||
```
|
||||
|
||||
#### 引擎选择逻辑
|
||||
|
||||
```typescript
|
||||
// src/cli/bg/engines/index.ts
|
||||
export async function selectEngine(): Promise<BgEngine> {
|
||||
if (process.platform === 'win32') {
|
||||
return new DetachedEngine()
|
||||
}
|
||||
|
||||
const tmux = new TmuxEngine()
|
||||
if (await tmux.available()) {
|
||||
return tmux
|
||||
}
|
||||
|
||||
return new DetachedEngine()
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 SessionEntry 扩展
|
||||
|
||||
```typescript
|
||||
interface SessionEntry {
|
||||
// ... 现有字段
|
||||
engine: 'tmux' | 'detached' // 新增: 记录使用的引擎
|
||||
tmuxSessionName?: string // tmux 引擎才有
|
||||
logPath?: string // 两种引擎都有
|
||||
}
|
||||
```
|
||||
|
||||
`attach` 时根据 `session.engine` 选择对应的 attach 策略。
|
||||
|
||||
## 五、文件变更清单
|
||||
|
||||
### 新增文件 (10 个)
|
||||
|
||||
```
|
||||
src/cli/bg/engine.ts BgEngine 接口定义
|
||||
src/cli/bg/engines/tmux.ts TmuxEngine (从 bg.ts 提取)
|
||||
src/cli/bg/engines/detached.ts DetachedEngine (新实现)
|
||||
src/cli/bg/engines/index.ts 引擎选择 + re-export
|
||||
src/cli/bg/tail.ts 跨平台日志 tail (用于 detached attach)
|
||||
src/commands/daemon/index.ts /daemon REPL 斜杠命令注册
|
||||
src/commands/daemon/daemon.tsx /daemon 子命令路由 + status UI
|
||||
src/commands/job/index.ts /job REPL 斜杠命令注册
|
||||
src/commands/job/job.tsx /job 子命令路由 + UI
|
||||
docs/features/daemon-restructure-design.md 本设计文档
|
||||
```
|
||||
|
||||
### 修改文件 (6 个)
|
||||
|
||||
```
|
||||
src/cli/bg.ts 重构: handler 函数改为调用 BgEngine
|
||||
src/entrypoints/cli.tsx 快速路径: daemon 统一入口 + 向后兼容
|
||||
src/commands.ts 注册 /daemon 和 /job 斜杠命令
|
||||
src/daemon/main.ts daemonMain() 增加 bg/ps/logs 子命令分发
|
||||
src/main.tsx Commander.js: 可选注册 daemon/job 子命令
|
||||
src/cli/handlers/templateJobs.ts 适配 /job 入口 (可能不需改)
|
||||
```
|
||||
|
||||
### 不动的文件
|
||||
|
||||
```
|
||||
src/daemon/state.ts daemon PID 状态管理 (无需改)
|
||||
src/jobs/state.ts job 状态管理 (无需改)
|
||||
src/jobs/templates.ts 模板发现 (无需改)
|
||||
src/jobs/classifier.ts 任务分类器 (无需改)
|
||||
src/cli/rollback.ts 保持顶级命令 (无需改)
|
||||
src/cli/up.ts 保持顶级命令 (无需改)
|
||||
```
|
||||
|
||||
## 六、可行性分析
|
||||
|
||||
### 6.1 风险评估
|
||||
|
||||
| 风险 | 级别 | 缓解措施 |
|
||||
|------|------|---------|
|
||||
| cli.tsx 快速路径修改影响启动性能 | 低 | 仅改路由逻辑,import 仍然 lazy |
|
||||
| DetachedEngine 的 attach 在 Windows 上 fs.watch 不可靠 | 中 | 使用轮询 fallback (setInterval + fs.stat) |
|
||||
| 向后兼容的 deprecation 可能破坏脚本 | 低 | 旧命令保持可用,仅输出 stderr 警告 |
|
||||
| REPL 中 /daemon bg 需要 spawn 子进程 | 中 | 参考 /assistant 的 NewInstallWizard (已有 spawn 先例) |
|
||||
| tsc 类型兼容 | 低 | 接口定义清晰,不引入 any |
|
||||
|
||||
### 6.2 工作量估计
|
||||
|
||||
| Task | 文件数 | 复杂度 |
|
||||
|------|--------|--------|
|
||||
| Task 013: BgEngine 抽象 + 引擎实现 | 5 新增 + 1 修改 | 中 |
|
||||
| Task 014: /daemon 命令层级化 | 3 新增 + 3 修改 | 中 |
|
||||
| Task 015: /job 命令层级化 | 2 新增 + 2 修改 | 低 |
|
||||
| Task 016: 向后兼容 + 测试 | 0 新增 + 2 修改 | 低 |
|
||||
|
||||
### 6.3 依赖关系
|
||||
|
||||
```
|
||||
Task 013 (BgEngine) ← 无依赖,可独立开发
|
||||
Task 014 (/daemon) ← 依赖 Task 013 (引擎选择)
|
||||
Task 015 (/job) ← 无依赖,可与 013 并行
|
||||
Task 016 (兼容) ← 依赖 Task 014 + 015
|
||||
```
|
||||
|
||||
## 七、设计决策记录
|
||||
|
||||
### D1: 为什么 daemon + bg sessions 合为一个命名空间?
|
||||
|
||||
用户视角:都是"后台运行的东西"。分开会导致 `claude daemon status` 看 supervisor + `claude ps` 看会话,割裂感强。合并后 `claude daemon status` 一次性展示 supervisor 状态 + 所有会话列表。
|
||||
|
||||
### D2: 为什么 rollback/up 不收入 daemon?
|
||||
|
||||
它们本质是**版本管理/环境初始化**,不是后台进程管理。`claude up` 是同步阻塞的 setup 脚本,不涉及 daemon 或后台会话。保持顶级更直观。
|
||||
|
||||
### D3: 为什么 DetachedEngine 的 attach 用 tail 而不是 IPC?
|
||||
|
||||
1. 日志文件是最简单的跨平台方案,无需额外依赖
|
||||
2. UDS Pipe IPC 系统 (usePipeIpc) 设计用于实例间通信,不是终端附着
|
||||
3. tmux attach 的体验(完整 PTY)无法在纯 detached 模式下复制,tail 是最诚实的替代
|
||||
|
||||
### D4: 为什么不用 Windows Terminal 的 tab/pane API?
|
||||
|
||||
Windows Terminal 的 `wt.exe` 新窗口/标签功能不够通用——用户可能在 VS Code、ConEmu、cmder 等终端中。detached + log 是唯一跨终端方案。
|
||||
@@ -1,117 +0,0 @@
|
||||
# DAEMON — 后台守护进程
|
||||
|
||||
> Feature Flag: `FEATURE_DAEMON=1`
|
||||
> 实现状态:Supervisor 和 remoteControl Worker 已实现
|
||||
> 引用数:3
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
DAEMON 将 Claude Code 变为后台守护进程。主进程(supervisor)管理多个 worker 子进程的生命周期,通过文件系统状态文件进行通信。适用于持续运行的后台服务场景(如配合 BRIDGE_MODE 提供远程控制服务)。
|
||||
|
||||
## 二、实现架构
|
||||
|
||||
### 2.1 模块状态
|
||||
|
||||
| 模块 | 文件 | 状态 |
|
||||
|------|------|------|
|
||||
| 守护主进程 | `src/daemon/main.ts` | **已实现** — Supervisor 含子命令、Worker 生命周期管理、指数退避重启 |
|
||||
| Worker 注册 | `src/daemon/workerRegistry.ts` | **已实现** — remoteControl Worker(headless bridge) |
|
||||
| Daemon 状态 | `src/daemon/state.ts` | **已实现** — PID/状态文件的读写与查询 |
|
||||
| CLI 路由 | `src/entrypoints/cli.tsx` | **布线** — `--daemon-worker` 和 `daemon` 子命令 |
|
||||
| 命令注册 | `src/commands.ts` | **布线** — DAEMON + BRIDGE_MODE 门控 |
|
||||
|
||||
### 2.2 CLI 入口
|
||||
|
||||
```
|
||||
# 启动守护进程
|
||||
claude daemon start
|
||||
|
||||
# 查看状态(默认子命令)
|
||||
claude daemon status
|
||||
claude daemon ps
|
||||
|
||||
# 停止守护进程
|
||||
claude daemon stop
|
||||
|
||||
# 以 worker 身份启动(由 supervisor 自动调用)
|
||||
claude --daemon-worker=remoteControl
|
||||
|
||||
# 后台会话管理
|
||||
claude daemon bg
|
||||
claude daemon attach <session>
|
||||
claude daemon logs <session>
|
||||
claude daemon kill <session>
|
||||
```
|
||||
|
||||
### 2.3 架构
|
||||
|
||||
```
|
||||
Supervisor (daemonMain)
|
||||
│
|
||||
├── Worker: remoteControl
|
||||
│ └── runBridgeHeadless() — 远程控制 headless 模式
|
||||
│ 接收远程会话、处理消息、权限审批
|
||||
│
|
||||
▼
|
||||
文件系统状态文件 (daemon-state.json)
|
||||
- PID、CWD、启动时间、Worker 类型
|
||||
- queryDaemonStatus() / stopDaemonByPid()
|
||||
```
|
||||
|
||||
### 2.4 Worker 生命周期管理
|
||||
|
||||
Supervisor 为每个 worker 实现:
|
||||
- **指数退避重启**:初始 2s,上限 120s,倍数 ×2
|
||||
- **快速失败检测**:10s 内连续崩溃 5 次则 parking(不再重启)
|
||||
- **永久错误退出码**:78 (EXIT_CODE_PERMANENT) 导致直接 parking
|
||||
- **优雅关闭**:SIGTERM/SIGINT → abort signal → 30s 强制 SIGKILL
|
||||
|
||||
### 2.5 与 BRIDGE_MODE 的关系
|
||||
|
||||
DAEMON 和 BRIDGE_MODE 常组合使用:
|
||||
|
||||
```ts
|
||||
// src/commands.ts
|
||||
if (feature('DAEMON') && feature('BRIDGE_MODE')) {
|
||||
// 加载 remoteControlServer 命令
|
||||
}
|
||||
```
|
||||
|
||||
双重门控:两个 feature 都需要开启才能使用远程控制服务器。
|
||||
|
||||
## 三、关键设计决策
|
||||
|
||||
1. **多进程架构**:一个 supervisor + 多个 worker,进程隔离
|
||||
2. **文件系统状态通信**:通过 `daemon-state.json` 文件进行状态共享(非 Unix 域套接字)
|
||||
3. **与 BRIDGE_MODE 强绑定**:守护进程最常见的用途是提供远程控制服务
|
||||
4. **CLI 子命令路由**:`daemon` 子命令和 `--daemon-worker` 参数在 `cli.tsx` 中路由
|
||||
5. **Worker 环境变量**:supervisor 通过环境变量(`DAEMON_WORKER_*`)向 worker 传递配置
|
||||
|
||||
## 四、使用方式
|
||||
|
||||
```bash
|
||||
# 启用守护进程模式
|
||||
FEATURE_DAEMON=1 FEATURE_BRIDGE_MODE=1 bun run dev
|
||||
|
||||
# 启动守护进程
|
||||
claude daemon start
|
||||
|
||||
# 查看状态
|
||||
claude daemon status
|
||||
|
||||
# 停止守护进程
|
||||
claude daemon stop
|
||||
|
||||
# 以特定 worker 启动(通常由 supervisor 自动调用)
|
||||
claude --daemon-worker=remoteControl
|
||||
```
|
||||
|
||||
## 五、文件索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/daemon/main.ts` | Supervisor 主进程:子命令分发、Worker 生命周期管理、退避重启 |
|
||||
| `src/daemon/workerRegistry.ts` | Worker 入口:remoteControl worker 实现 |
|
||||
| `src/daemon/state.ts` | Daemon 状态管理:PID 文件读写、状态查询 |
|
||||
| `src/entrypoints/cli.tsx` | CLI 路由 |
|
||||
| `src/commands.ts` | 命令注册(双重门控) |
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
title: "Debug 模式"
|
||||
description: "通过 VS Code attach 模式调试 CLI 运行时,支持断点、单步执行和变量查看。"
|
||||
keywords: ["debug", "调试", "VS Code", "inspect", "断点"]
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
TUI (REPL) 模式需要真实终端,无法直接通过 VS Code launch 启动调试。使用 **attach 模式**连接到正在运行的 Bun 进程。
|
||||
|
||||
## 步骤
|
||||
|
||||
### 1. 终端启动 inspect 服务
|
||||
|
||||
```bash
|
||||
bun run dev:inspect
|
||||
```
|
||||
|
||||
会输出类似 `ws://localhost:8888/xxxxxxxx` 的地址。
|
||||
|
||||
### 2. VS Code 附着调试器
|
||||
|
||||
1. 在 `src/` 文件中打断点
|
||||
2. F5 → 选择 **"Attach to Bun (TUI debug)"**
|
||||
|
||||
> **注意**:`dev:inspect` 和 `launch.json` 中的 WebSocket 地址会在每次启动时变化,需要同步更新两处。
|
||||
|
||||
## 原理
|
||||
|
||||
`dev:inspect` 脚本实际执行的是 `scripts/dev-debug.ts`:
|
||||
|
||||
```typescript
|
||||
// scripts/dev-debug.ts
|
||||
process.env.BUN_INSPECT = "localhost:8888/<token>"
|
||||
await import("./dev")
|
||||
```
|
||||
|
||||
通过设置 `BUN_INSPECT` 环境变量启动一个 Chrome DevTools Protocol 兼容的 inspect 服务,然后导入 dev 模式入口。VS Code 的 `bun` 扩展通过 WebSocket 连接到输出的地址实现 attach。
|
||||
|
||||
## JetBrains IDE
|
||||
|
||||
理论上 JetBrains 系列(WebStorm / IntelliJ 等)也支持 attach 到 Bun inspect 服务(Run → Attach to Process),但尚未实际验证过。如果你验证成功,欢迎补充文档。
|
||||
|
||||
## 相关文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `package.json` → `dev:inspect` | 启动 inspect 服务的 npm script |
|
||||
| `.vscode/launch.json` | VS Code attach 调试配置 |
|
||||
| `scripts/dev.ts` | dev 模式入口,注入 MACRO defines |
|
||||
@@ -1,99 +0,0 @@
|
||||
# EXPERIMENTAL_SKILL_SEARCH — 技能语义搜索
|
||||
|
||||
> Feature Flag: `FEATURE_EXPERIMENTAL_SKILL_SEARCH=1`
|
||||
> 实现状态:全部 Stub(8 个文件),布线完整
|
||||
> 引用数:21
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
EXPERIMENTAL_SKILL_SEARCH 提供 DiscoverSkills 工具,根据当前任务语义搜索可用技能。目标是让模型在执行任务时自动发现和推荐相关的技能(包括本地和远程),无需用户手动查找。
|
||||
|
||||
## 二、实现架构
|
||||
|
||||
### 2.1 模块状态
|
||||
|
||||
| 模块 | 文件 | 状态 | 说明 |
|
||||
|------|------|------|------|
|
||||
| DiscoverSkillsTool | `src/tools/DiscoverSkillsTool/prompt.ts` | **Stub** | 空工具名 |
|
||||
| 预取 | `src/services/skillSearch/prefetch.ts` | **Stub** | 3 个函数全部空操作 |
|
||||
| 远程加载 | `src/services/skillSearch/remoteSkillLoader.ts` | **Stub** | 返回空结果 |
|
||||
| 远程状态 | `src/services/skillSearch/remoteSkillState.ts` | **Stub** | 返回 null/undefined |
|
||||
| 信号 | `src/services/skillSearch/signals.ts` | **Stub** | `DiscoverySignal = any` |
|
||||
| 遥测 | `src/services/skillSearch/telemetry.ts` | **Stub** | 空操作日志 |
|
||||
| 本地搜索 | `src/services/skillSearch/localSearch.ts` | **Stub** | 空操作缓存 |
|
||||
| 功能检查 | `src/services/skillSearch/featureCheck.ts` | **Stub** | `isSkillSearchEnabled => false` |
|
||||
| SkillTool 集成 | `src/tools/SkillTool/SkillTool.ts` | **布线** | 动态加载所有远程技能模块 |
|
||||
| 提示集成 | `src/constants/prompts.ts` | **布线** | DiscoverSkills schema 注入 |
|
||||
|
||||
### 2.2 预期数据流
|
||||
|
||||
```
|
||||
模型处理用户任务
|
||||
│
|
||||
▼
|
||||
DiscoverSkills 工具触发 [需要实现]
|
||||
│
|
||||
├── 本地搜索:索引已安装技能元数据
|
||||
│ └── localSearch.ts → 技能名称/描述/关键字匹配
|
||||
│
|
||||
└── 远程搜索:查询技能市场/注册表
|
||||
└── remoteSkillLoader.ts → fetch + 解析
|
||||
│
|
||||
▼
|
||||
结果排序和过滤
|
||||
│
|
||||
▼
|
||||
返回推荐技能列表
|
||||
│
|
||||
▼
|
||||
模型使用 SkillTool 调用推荐技能
|
||||
```
|
||||
|
||||
### 2.3 预取机制
|
||||
|
||||
`prefetch.ts` 预期在用户提交输入前分析消息内容,提前搜索相关技能:
|
||||
|
||||
- `startSkillDiscoveryPrefetch()` — 开始预取
|
||||
- `collectSkillDiscoveryPrefetch()` — 收集预取结果
|
||||
- `getTurnZeroSkillDiscovery()` — 获取 turn 0 的技能发现结果
|
||||
|
||||
## 三、需要补全的内容
|
||||
|
||||
| 优先级 | 模块 | 工作量 | 说明 |
|
||||
|--------|------|--------|------|
|
||||
| 1 | `DiscoverSkillsTool` | 大 | 语义搜索工具 schema + 执行 |
|
||||
| 2 | `skillSearch/prefetch.ts` | 中 | 用户输入分析和预取逻辑 |
|
||||
| 3 | `skillSearch/remoteSkillLoader.ts` | 大 | 远程市场/注册表获取 |
|
||||
| 4 | `skillSearch/remoteSkillState.ts` | 小 | 已发现技能状态管理 |
|
||||
| 5 | `skillSearch/localSearch.ts` | 中 | 本地索引构建/查询 |
|
||||
| 6 | `skillSearch/featureCheck.ts` | 小 | GrowthBook/配置门控 |
|
||||
| 7 | `skillSearch/signals.ts` | 小 | `DiscoverySignal` 类型定义 |
|
||||
|
||||
## 四、关键设计决策
|
||||
|
||||
1. **预取优化**:在用户提交前就开始搜索,减少首次响应延迟
|
||||
2. **本地+远程双搜索**:本地索引快速匹配 + 远程市场深度搜索
|
||||
3. **SkillTool 集成**:发现的技能通过 SkillTool 调用,不需要新的调用机制
|
||||
4. **独立于 MCP_SKILLS**:MCP_SKILLS 从 MCP 服务器发现,EXPERIMENTAL_SKILL_SEARCH 从技能市场发现
|
||||
|
||||
## 五、使用方式
|
||||
|
||||
```bash
|
||||
# 启用 feature(需要补全后才能真正使用)
|
||||
FEATURE_EXPERIMENTAL_SKILL_SEARCH=1 bun run dev
|
||||
```
|
||||
|
||||
## 六、文件索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/tools/DiscoverSkillsTool/prompt.ts` | 工具 schema(stub) |
|
||||
| `src/services/skillSearch/prefetch.ts` | 预取逻辑(stub) |
|
||||
| `src/services/skillSearch/remoteSkillLoader.ts` | 远程加载(stub) |
|
||||
| `src/services/skillSearch/remoteSkillState.ts` | 远程状态(stub) |
|
||||
| `src/services/skillSearch/signals.ts` | 信号类型(stub) |
|
||||
| `src/services/skillSearch/telemetry.ts` | 遥测(stub) |
|
||||
| `src/services/skillSearch/localSearch.ts` | 本地搜索(stub) |
|
||||
| `src/services/skillSearch/featureCheck.ts` | 功能检查(stub) |
|
||||
| `src/tools/SkillTool/SkillTool.ts` | SkillTool 集成点 |
|
||||
| `src/constants/prompts.ts:95,335,778` | 提示增强 |
|
||||
@@ -1,3 +1,9 @@
|
||||
---
|
||||
title: "频道消息推送(Channels)"
|
||||
description: "MCP 服务器把飞书 / Slack / Discord / 微信等外部消息推到会话,`--channels plugin:name@marketplace` 启用。"
|
||||
keywords: ["Channels", "频道消息", "微信 channel", "飞书 channel", "MCP 事件推送"]
|
||||
---
|
||||
|
||||
# Channels — 外部频道消息接入
|
||||
|
||||
> 启动参数:`--channels` / `--dangerously-load-development-channels`
|
||||
189
docs/features/external/chrome-control.md
vendored
Normal file
189
docs/features/external/chrome-control.md
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
---
|
||||
title: "Chrome 浏览器控制"
|
||||
description: "让 AI 用自然语言操作 Chrome 浏览器:导航、表单、数据抓取。两种实现方案对比:自托管 MCP(chrome-use-mcp)与 Chrome 原生集成(claude-in-chrome-mcp)。"
|
||||
keywords: ["Chrome 浏览器控制", "MCP", "浏览器自动化", "Claude in Chrome", "网页抓取"]
|
||||
---
|
||||
|
||||
# Chrome 浏览器控制
|
||||
|
||||
让 Claude Code 用自然语言直接操作 Chrome 浏览器,完成网页导航、表单填写、数据抓取、截图录制等任务。
|
||||
|
||||
Claude Code 提供两种浏览器控制方案:
|
||||
|
||||
| 方案 | 简介 | 适用场景 |
|
||||
|------|------|---------|
|
||||
| **Chrome Use MCP**(自托管 MCP) | 通过社区开源 MCP 扩展(`mcp-chrome`)接入,Claude Code 以 MCP 客户端方式调用 | 想自托管、可定制、不依赖 Anthropic 订阅 |
|
||||
| **Claude in Chrome**(Chrome 原生集成) | Anthropic 官方扩展 + 内建工具集,通过 `--chrome` 启动参数加载 | 需要完整能力(截图/GIF/网络监控/JS 执行等),有 Claude Pro/Max/Team 订阅 |
|
||||
|
||||
两种方案可以独立使用,也可按需切换。下面先讲快速上手,再分别给出详细说明。
|
||||
|
||||
## 快速上手
|
||||
|
||||
### 方案一:Chrome Use MCP(3 分钟)
|
||||
|
||||
**第一步:安装 Chrome 扩展**
|
||||
|
||||
1. 下载扩展:https://github.com/hangwin/mcp-chrome/releases
|
||||
2. 解压 zip 文件
|
||||
3. 打开 Chrome 访问 `chrome://extensions/`
|
||||
4. 开启右上角「开发者模式」
|
||||
5. 点击「加载已解压的扩展程序」,选择解压后的文件夹
|
||||
|
||||
**第二步:启动 Claude Code**
|
||||
|
||||
```bash
|
||||
bun run dev
|
||||
ccb # 或者 ccb 安装版也行
|
||||
```
|
||||
|
||||
**第三步:启用 Chrome MCP**
|
||||
|
||||
1. 在 REPL 中输入 `/mcp` 打开 MCP 面板
|
||||
2. 找到 `mcp-chrome`,按空格键启用
|
||||
3. 按 Enter 确认
|
||||
|
||||
### 方案二:Claude in Chrome
|
||||
|
||||
**前置条件**
|
||||
|
||||
| 条件 | 说明 |
|
||||
|------|------|
|
||||
| Claude Code 订阅 | 需要 Claude Pro、Max 或 Team 订阅,浏览器插件功能不向免费用户开放 |
|
||||
| Chrome 浏览器 | 需已安装 Google Chrome |
|
||||
| Claude in Chrome 扩展 | 从 Chrome Web Store 安装(`claude.ai/chrome`) |
|
||||
| Claude Code CLI | 已通过 `bun run dev` 或构建产物运行 |
|
||||
|
||||
**启动 CLI**
|
||||
|
||||
```bash
|
||||
# Dev 模式
|
||||
bun run dev -- --chrome
|
||||
|
||||
# 构建产物
|
||||
node dist/cli.js --chrome
|
||||
```
|
||||
|
||||
启动后 Claude 会自动检测 Chrome 扩展是否已安装,并注册浏览器控制工具。
|
||||
|
||||
**确认连接**:REPL 中输入 `/chrome`,查看扩展状态是否显示 "Installed / Connected"。
|
||||
|
||||
**开始对话**:正常与 Claude 对话,当需要操作浏览器时直接说,例如:
|
||||
|
||||
- "打开 https://example.com 并截图"
|
||||
- "在当前页面搜索关键词 xxx"
|
||||
- "填写登录表单,用户名 admin"
|
||||
- "帮我录制当前操作的 GIF"
|
||||
|
||||
**权限审批**:首次执行浏览器操作时,Claude 会请求你的确认;操作完成后返回结果(截图、文本、执行结果等)。
|
||||
|
||||
## 详细说明:Chrome Use MCP
|
||||
|
||||
Chrome Use MCP 是基于社区开源项目 [`mcp-chrome`](https://github.com/hangwin/mcp-chrome) 的自托管方案。Claude Code 以标准 MCP 客户端身份接入,由扩展提供浏览器侧能力。
|
||||
|
||||
特点:
|
||||
|
||||
- 完全开源、可自托管,不依赖 Anthropic 账户体系
|
||||
- 在 MCP 面板里启用/禁用,不占用启动参数
|
||||
- 能力由扩展决定,适合做定制化浏览器自动化
|
||||
|
||||
相关文档:
|
||||
|
||||
- GitHub 仓库:https://github.com/hangwin/mcp-chrome
|
||||
|
||||
## 详细说明:Claude in Chrome
|
||||
|
||||
Claude in Chrome 是 Anthropic 官方扩展 + 内建工具集,提供更完整的浏览器操控能力。
|
||||
|
||||
### 可用操作
|
||||
|
||||
#### 页面交互
|
||||
|
||||
| 操作 | 说明 |
|
||||
|------|------|
|
||||
| `navigate` | 导航到指定 URL,或前进/后退 |
|
||||
| `computer` | 鼠标点击、移动、拖拽、键盘输入、截图等(13 种 action) |
|
||||
| `form_input` | 填写表单字段 |
|
||||
| `upload_image` | 上传图片到文件输入框或拖拽区域 |
|
||||
| `javascript_tool` | 在页面上下文执行 JavaScript |
|
||||
|
||||
#### 页面读取
|
||||
|
||||
| 操作 | 说明 |
|
||||
|------|------|
|
||||
| `read_page` | 获取页面可访问性树(DOM 结构) |
|
||||
| `get_page_text` | 提取页面纯文本内容 |
|
||||
| `find` | 用自然语言搜索页面元素 |
|
||||
|
||||
#### 标签页管理
|
||||
|
||||
| 操作 | 说明 |
|
||||
|------|------|
|
||||
| `tabs_context_mcp` | 获取当前标签组信息 |
|
||||
| `tabs_create_mcp` | 创建新标签页 |
|
||||
|
||||
#### 监控与调试
|
||||
|
||||
| 操作 | 说明 |
|
||||
|------|------|
|
||||
| `read_console_messages` | 读取浏览器控制台日志 |
|
||||
| `read_network_requests` | 读取网络请求记录 |
|
||||
|
||||
#### 其他
|
||||
|
||||
| 操作 | 说明 |
|
||||
|------|------|
|
||||
| `resize_window` | 调整浏览器窗口尺寸 |
|
||||
| `gif_creator` | 录制 GIF 并导出 |
|
||||
| `shortcuts_list` | 列出可用快捷方式 |
|
||||
| `shortcuts_execute` | 执行快捷方式 |
|
||||
| `update_plan` | 向你提交操作计划供审批 |
|
||||
| `switch_browser` | 切换到其他 Chrome 浏览器(仅 Bridge 模式) |
|
||||
|
||||
### 通信模式
|
||||
|
||||
Claude in Chrome 支持两种与浏览器通信的方式:
|
||||
|
||||
**本地 Socket(默认)**:Chrome 扩展通过 Native Messaging Host 与 CLI 建立 Unix socket 连接。适用于本地开发,无需额外配置。
|
||||
|
||||
**Bridge WebSocket**:通过 Anthropic 的 bridge 服务中转,支持远程操控浏览器。需要 claude.ai OAuth 登录。
|
||||
|
||||
## 进阶与参考
|
||||
|
||||
### 配置
|
||||
|
||||
#### 启用 / 禁用(Claude in Chrome)
|
||||
|
||||
```bash
|
||||
# 显式禁用
|
||||
bun run dev -- --no-chrome
|
||||
```
|
||||
|
||||
或在 REPL 中通过 `/chrome` 命令切换启用/禁用状态。
|
||||
|
||||
#### 通过配置默认启用
|
||||
|
||||
在 Claude Code 设置中将 `claudeInChromeDefaultEnabled` 设为 `true`,以后启动无需加 `--chrome` 参数。
|
||||
|
||||
#### Feature Flag 提示
|
||||
|
||||
- Chrome Use MCP:依赖标准 MCP 加载机制,通过 `/mcp` 面板启用。
|
||||
- Claude in Chrome:构建/运行时通过 `--chrome` 参数(对应内部 feature 开关)加载浏览器相关模块;不带该参数启动时不会加载任何浏览器相关模块,不影响其他功能。
|
||||
|
||||
### 常见问题
|
||||
|
||||
**扩展显示未安装**
|
||||
|
||||
确认已从 Chrome Web Store 安装 "Claude in Chrome" 扩展,安装后重启浏览器。Chrome Use MCP 用户则需确认已按上面"加载已解压的扩展程序"步骤加载本地扩展。
|
||||
|
||||
**工具未出现在工具列表**
|
||||
|
||||
- Claude in Chrome:检查启动时是否加了 `--chrome` 参数,或通过 `/chrome` 命令确认状态。
|
||||
- Chrome Use MCP:在 `/mcp` 面板里确认 `mcp-chrome` 已启用。
|
||||
|
||||
**连接超时**
|
||||
|
||||
确保 Chrome 浏览器正在运行且扩展已启用。Native Messaging Host 在扩展安装时自动注册,如果重装过扩展需要重启浏览器。
|
||||
|
||||
**不使用 Chrome 功能时**
|
||||
|
||||
不带 `--chrome` 参数正常启动即可,不会加载任何浏览器相关模块,不影响其他功能。
|
||||
@@ -1,29 +1,170 @@
|
||||
# Computer Use 工具参考文档
|
||||
---
|
||||
title: "屏幕控制(Computer Use)"
|
||||
description: "截屏、键鼠控制,跨 macOS / Windows / Linux。本文包含快速上手、平台差异说明和工具参考。"
|
||||
keywords: [屏幕控制, 截屏, 键鼠模拟, 跨平台自动化, Computer Use]
|
||||
---
|
||||
|
||||
## 概览
|
||||
# 屏幕控制(Computer Use)
|
||||
|
||||
Computer Use 提供 38 个工具,分为三类:
|
||||
Computer Use 提供截屏、键鼠控制和应用管理能力,支持 macOS / Windows / Linux 三大桌面平台。Windows 平台额外提供窗口绑定模式(不干扰真实键鼠),全平台共 38 个工具。
|
||||
|
||||
本文包含三部分:
|
||||
|
||||
- **快速上手** — 启用方式与典型操作流程
|
||||
- **平台差异说明** — 三平台的实现、依赖与能力差异
|
||||
- **工具参考** — 全部工具的参数、用法和进阶场景
|
||||
|
||||
## 概述
|
||||
|
||||
Computer Use 由三个 workspace 包组成:
|
||||
|
||||
| 包 | 职责 |
|
||||
|----|------|
|
||||
| `@ant/computer-use-mcp` | MCP server 入口与工具注册(12 文件) |
|
||||
| `@ant/computer-use-input` | 键鼠模拟(dispatcher + 各平台 backend) |
|
||||
| `@ant/computer-use-swift` | 截图与应用管理(dispatcher + 各平台 backend) |
|
||||
|
||||
工具共 38 个,分三类:
|
||||
|
||||
| 分类 | 平台 | 工具数 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| 通用工具 | 全平台 | 24 | 官方 Computer Use 标准能力 |
|
||||
| Windows 专属工具 | Win32 | 11 | 绑定窗口模式下的增强能力 |
|
||||
| 教学工具 | 全平台 | 3 | 分步引导模式(需 teachMode 开启) |
|
||||
| 教学工具 | 全平台 | 3 | 分步引导模式(需 `teachMode` 开启) |
|
||||
|
||||
---
|
||||
## 快速上手
|
||||
|
||||
## 一、通用工具(24 个)
|
||||
### 启用方式
|
||||
|
||||
在启动 Claude Code 时附加 `--computer-use-mcp`,或在运行时通过 `feature("CHICAGO_MCP")` 控制入口初始化。
|
||||
|
||||
```bash
|
||||
claude --computer-use-mcp
|
||||
```
|
||||
|
||||
Linux 平台需要先安装依赖工具(详见下文「Linux 依赖工具」)。macOS / Windows 通常无需额外安装。
|
||||
|
||||
### 典型操作流程
|
||||
|
||||
#### 流程 1:全屏操作(未绑定窗口)
|
||||
|
||||
```
|
||||
request_access(apps=["Notepad"])
|
||||
open_application(app="Notepad") ← 自动绑定窗口
|
||||
screenshot ← PrintWindow 截图 + GUI 元素列表
|
||||
left_click(coordinate=[500, 300]) ← 全局 SendInput
|
||||
type(text="hello world") ← 全局 SendInput
|
||||
key(text="ctrl+s") ← 全局 SendInput
|
||||
```
|
||||
|
||||
#### 流程 2:绑定窗口操作(Windows 推荐,不干扰用户)
|
||||
|
||||
```
|
||||
request_access(apps=["Notepad"])
|
||||
bind_window(action="list") ← 列出所有窗口
|
||||
bind_window(action="bind", title="记事本") ← 绑定 + 绿色边框 + 虚拟光标
|
||||
screenshot ← PrintWindow 截取绑定窗口
|
||||
virtual_mouse(action="click", coordinate=[500, 300]) ← SendMessageW,不动真实鼠标
|
||||
virtual_keyboard(action="type", text="hello world") ← SendMessageW,不动物理键盘
|
||||
virtual_keyboard(action="combo", text="ctrl+s") ← 保存
|
||||
mouse_wheel(coordinate=[500, 400], delta=-5) ← 向下滚动
|
||||
bind_window(action="unbind") ← 解除绑定
|
||||
```
|
||||
|
||||
#### 流程 3:按元素名称操作
|
||||
|
||||
```
|
||||
bind_window(action="bind", title="记事本")
|
||||
screenshot ← 返回截图 + GUI elements 列表
|
||||
click_element(name="保存", role="Button") ← UI Automation 查找并点击
|
||||
type_into_element(role="Edit", text="new content")
|
||||
```
|
||||
|
||||
#### 流程 4:终端交互
|
||||
|
||||
```
|
||||
bind_window(action="bind", title="PowerShell")
|
||||
screenshot
|
||||
prompt_respond(response_type="yes") ← 回答 y + Enter
|
||||
prompt_respond(response_type="select", arrow_direction="down", arrow_count=2) ← 选第3项
|
||||
```
|
||||
|
||||
#### 流程 5:Excel/浏览器滚动
|
||||
|
||||
```
|
||||
bind_window(action="bind", title="Excel")
|
||||
screenshot
|
||||
mouse_wheel(coordinate=[600, 400], delta=-10) ← 向下滚动 10 格
|
||||
mouse_wheel(coordinate=[600, 400], delta=5, direction="horizontal") ← 向右滚动
|
||||
```
|
||||
|
||||
## 平台差异说明
|
||||
|
||||
### 各平台能力依赖
|
||||
|
||||
#### computer-use-input(键鼠)
|
||||
|
||||
| 功能 | macOS | Windows | Linux |
|
||||
|------|-------|---------|-------|
|
||||
| 鼠标移动 | CGEvent JXA | SetCursorPos P/Invoke | xdotool mousemove |
|
||||
| 鼠标点击 | CGEvent JXA | SendInput P/Invoke | xdotool click |
|
||||
| 鼠标滚轮 | CGEvent JXA | SendInput MOUSEEVENTF_WHEEL | xdotool scroll |
|
||||
| 键盘按键 | System Events osascript | keybd_event P/Invoke | xdotool key |
|
||||
| 组合键 | System Events osascript | keybd_event 组合 | xdotool key combo |
|
||||
| 文本输入 | System Events keystroke | SendKeys.SendWait | xdotool type |
|
||||
| 前台应用 | System Events osascript | GetForegroundWindow P/Invoke | xdotool getactivewindow + /proc |
|
||||
| 工具依赖 | osascript(内置) | powershell(内置) | xdotool(需安装) |
|
||||
|
||||
#### computer-use-swift(截图 + 应用管理)
|
||||
|
||||
| 功能 | macOS | Windows | Linux |
|
||||
|------|-------|---------|-------|
|
||||
| 全屏截图 | screencapture | CopyFromScreen | gnome-screenshot / scrot / grim |
|
||||
| 区域截图 | screencapture -R | CopyFromScreen(rect) | gnome-screenshot -a / scrot -a / grim -g |
|
||||
| 显示器列表 | CGGetActiveDisplayList JXA | Screen.AllScreens | xrandr --query |
|
||||
| 运行中应用 | System Events JXA | Get-Process | wmctrl -l / ps |
|
||||
| 打开应用 | osascript activate | Start-Process | xdg-open / gtk-launch |
|
||||
| 隐藏/显示 | System Events visibility | ShowWindow/SetForegroundWindow | wmctrl -c / xdotool |
|
||||
| 工具依赖 | screencapture + osascript | powershell | xdotool + scrot/grim + wmctrl |
|
||||
|
||||
#### executor 层
|
||||
|
||||
| 功能 | macOS | Windows | Linux |
|
||||
|------|-------|---------|-------|
|
||||
| drainRunLoop | CFRunLoop pump | 不需要 | 不需要 |
|
||||
| ESC 热键 | CGEventTap | 跳过(Ctrl+C fallback) | 跳过(Ctrl+C fallback) |
|
||||
| 剪贴板读 | pbpaste | `powershell Get-Clipboard` | xclip -o / wl-paste |
|
||||
| 剪贴板写 | pbcopy | `powershell Set-Clipboard` | xclip / wl-copy |
|
||||
| 粘贴快捷键 | command+v | ctrl+v | ctrl+v |
|
||||
| 终端检测 | __CFBundleIdentifier | WT_SESSION / TERM_PROGRAM | TERM_PROGRAM |
|
||||
| 系统权限 | TCC check | 直接 granted | 检查 xdotool 安装 |
|
||||
|
||||
### Linux 依赖工具
|
||||
|
||||
| 工具 | 用途 | 安装命令(Ubuntu) |
|
||||
|------|------|-------------------|
|
||||
| `xdotool` | 键鼠模拟 + 窗口管理 | `sudo apt install xdotool` |
|
||||
| `scrot` 或 `gnome-screenshot` | 截图 | `sudo apt install scrot` |
|
||||
| `xrandr` | 显示器信息 | 通常已预装 |
|
||||
| `xclip` | 剪贴板 | `sudo apt install xclip` |
|
||||
| `wmctrl` | 窗口列表/切换 | `sudo apt install wmctrl` |
|
||||
|
||||
Wayland 环境需要替代工具:`ydotool`(替代 xdotool)、`grim`(替代 scrot)、`wl-clipboard`(替代 xclip)。初期可先只支持 X11,Wayland 标记为 todo。
|
||||
|
||||
## 工具参考
|
||||
|
||||
### 通用工具(24 个)
|
||||
|
||||
全平台可用。未绑定窗口时,操作对象是整个屏幕。
|
||||
|
||||
### 权限与会话
|
||||
#### 权限与会话
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| `request_access` | `apps[]`, `reason`, `clipboardRead?`, `clipboardWrite?`, `systemKeyCombos?` | 请求操作应用的权限。所有其他工具的前置条件 |
|
||||
| `list_granted_applications` | — | 列出当前会话已授权的应用 |
|
||||
|
||||
### 截图与显示
|
||||
#### 截图与显示
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
@@ -31,7 +172,7 @@ Computer Use 提供 38 个工具,分为三类:
|
||||
| `zoom` | `region: [x1,y1,x2,y2]` | 截取指定区域的高分辨率图片。坐标基于最近一次全屏截图 |
|
||||
| `switch_display` | `display` | 切换截图的目标显示器 |
|
||||
|
||||
### 鼠标操作
|
||||
#### 鼠标操作
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
@@ -46,7 +187,7 @@ Computer Use 提供 38 个工具,分为三类:
|
||||
| `left_mouse_up` | — | 松开左键 |
|
||||
| `cursor_position` | — | 获取当前鼠标位置 |
|
||||
|
||||
### 键盘操作
|
||||
#### 键盘操作
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
@@ -54,39 +195,37 @@ Computer Use 提供 38 个工具,分为三类:
|
||||
| `key` | `text` (如 "ctrl+s"), `repeat?` | 按键/组合键 |
|
||||
| `hold_key` | `text`, `duration` (秒) | 按住键指定时长 |
|
||||
|
||||
### 滚动
|
||||
#### 滚动
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| `scroll` | `coordinate`, `scroll_direction`, `scroll_amount` | 滚动。方向: up/down/left/right |
|
||||
|
||||
### 应用管理
|
||||
#### 应用管理
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| `open_application` | `app` | 打开应用。Windows 上自动绑定窗口 |
|
||||
|
||||
### 剪贴板
|
||||
#### 剪贴板
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| `read_clipboard` | — | 读取剪贴板文字 |
|
||||
| `write_clipboard` | `text` | 写入剪贴板 |
|
||||
|
||||
### 其他
|
||||
#### 其他
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| `wait` | `duration` (秒) | 等待 |
|
||||
| `computer_batch` | `actions[]` | 批量执行多个动作(减少 API 往返) |
|
||||
|
||||
---
|
||||
|
||||
## 二、Windows 专属工具(12 个)
|
||||
### Windows 专属工具(12 个)
|
||||
|
||||
仅 Windows 平台可见。核心能力:**绑定窗口后的独立操作——不抢占用户鼠标键盘**。
|
||||
|
||||
### 工作模式
|
||||
#### 工作模式
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
@@ -107,7 +246,7 @@ Computer Use 提供 38 个工具,分为三类:
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 窗口绑定
|
||||
#### 窗口绑定
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
@@ -122,7 +261,7 @@ Computer Use 提供 38 个工具,分为三类:
|
||||
| `unbind` | — | 解除绑定,恢复全屏模式 |
|
||||
| `status` | — | 查看当前绑定状态(hwnd、title、pid、窗口矩形) |
|
||||
|
||||
### 窗口管理
|
||||
#### 窗口管理
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
@@ -141,7 +280,7 @@ Computer Use 提供 38 个工具,分为三类:
|
||||
| `move_resize` | SetWindowPos — 移动/缩放到指定位置和大小 |
|
||||
| `get_rect` | GetWindowRect — 获取当前位置和大小 |
|
||||
|
||||
### 虚拟鼠标
|
||||
#### 虚拟鼠标
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
@@ -168,7 +307,7 @@ Computer Use 提供 38 个工具,分为三类:
|
||||
| 用户干扰 | 有 | **无** |
|
||||
| 适用场景 | 未绑定时 | **绑定后** |
|
||||
|
||||
### 虚拟键盘
|
||||
#### 虚拟键盘
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
@@ -194,13 +333,14 @@ Computer Use 提供 38 个工具,分为三类:
|
||||
|
||||
**注意:** SendMessageW 对 Windows Terminal (ConPTY) 等现代应用无效。这些应用需要使用通用工具 + 窗口激活方式操作。
|
||||
|
||||
### 鼠标滚轮
|
||||
#### 鼠标滚轮
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| `mouse_wheel` | `coordinate: [x,y]`, `delta`, `direction?` | WM_MOUSEWHEEL 鼠标中键滚轮 |
|
||||
|
||||
**参数说明:**
|
||||
|
||||
- `delta`: 正值=向上,负值=向下。每 1 单位 ≈ 3 行
|
||||
- `direction`: "vertical"(默认)或 "horizontal"
|
||||
- `coordinate`: 滚轮作用点——决定哪个面板/区域接收滚动
|
||||
@@ -210,11 +350,11 @@ Computer Use 提供 38 个工具,分为三类:
|
||||
| | `scroll` | `mouse_wheel` |
|
||||
|---|---|---|
|
||||
| 原理 | WM_VSCROLL/WM_HSCROLL | **WM_MOUSEWHEEL** |
|
||||
| Excel | ❌ | ✅ |
|
||||
| 浏览器 | ❌ | ✅ |
|
||||
| 代码编辑器 | ❌ | ✅ |
|
||||
| Excel | 否 | 是 |
|
||||
| 浏览器 | 否 | 是 |
|
||||
| 代码编辑器 | 否 | 是 |
|
||||
|
||||
### 元素级操作
|
||||
#### 元素级操作
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
@@ -222,16 +362,18 @@ Computer Use 提供 38 个工具,分为三类:
|
||||
| `type_into_element` | `name?`, `role?`, `automationId?`, `text` | 按名称向元素输入文字 |
|
||||
|
||||
**工作原理:**
|
||||
|
||||
1. 通过 UI Automation 在绑定窗口中查找匹配元素
|
||||
2. `click_element`: 先尝试 InvokePattern(按钮/菜单),失败则 SendMessage 点击 BoundingRect 中心
|
||||
3. `type_into_element`: 先尝试 ValuePattern 直接设值,失败则点击聚焦 + WM_CHAR 输入
|
||||
|
||||
**适用场景:**
|
||||
|
||||
- 截图中看到元素名称但坐标不精确时
|
||||
- Accessibility Snapshot 列出了元素的 name/automationId 时
|
||||
- 比坐标点击更可靠(不受窗口缩放/DPI 影响)
|
||||
|
||||
### 终端交互
|
||||
#### 终端交互
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
@@ -259,15 +401,13 @@ Computer Use 提供 38 个工具,分为三类:
|
||||
| `select` | ↑/↓ 箭头 × N + Enter | inquirer 选择菜单 |
|
||||
| `type` | 输入文字 + Enter | 文本输入提示 |
|
||||
|
||||
### 状态指示器
|
||||
#### 状态指示器
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| `status_indicator` | `action`: show/hide/status, `message?` | 控制绑定窗口底部的浮动状态标签 |
|
||||
|
||||
---
|
||||
|
||||
## 三、教学工具(3 个)
|
||||
### 教学工具(3 个)
|
||||
|
||||
需要 `teachMode` 开启。
|
||||
|
||||
@@ -277,80 +417,22 @@ Computer Use 提供 38 个工具,分为三类:
|
||||
| `teach_step` | 显示一步引导提示,等用户点 Next |
|
||||
| `teach_batch` | 批量排队多步引导 |
|
||||
|
||||
---
|
||||
## 进阶
|
||||
|
||||
## 操作流程
|
||||
|
||||
### 流程 1:全屏操作(未绑定)
|
||||
|
||||
```
|
||||
request_access(apps=["Notepad"])
|
||||
open_application(app="Notepad") ← 自动绑定窗口
|
||||
screenshot ← PrintWindow 截图 + GUI 元素列表
|
||||
left_click(coordinate=[500, 300]) ← 全局 SendInput
|
||||
type(text="hello world") ← 全局 SendInput
|
||||
key(text="ctrl+s") ← 全局 SendInput
|
||||
```
|
||||
|
||||
### 流程 2:绑定窗口操作(推荐,不干扰用户)
|
||||
|
||||
```
|
||||
request_access(apps=["Notepad"])
|
||||
bind_window(action="list") ← 列出所有窗口
|
||||
bind_window(action="bind", title="记事本") ← 绑定 + 绿色边框 + 虚拟光标
|
||||
screenshot ← PrintWindow 截取绑定窗口
|
||||
virtual_mouse(action="click", coordinate=[500, 300]) ← SendMessageW,不动真实鼠标
|
||||
virtual_keyboard(action="type", text="hello world") ← SendMessageW,不动物理键盘
|
||||
virtual_keyboard(action="combo", text="ctrl+s") ← 保存
|
||||
mouse_wheel(coordinate=[500, 400], delta=-5) ← 向下滚动
|
||||
bind_window(action="unbind") ← 解除绑定
|
||||
```
|
||||
|
||||
### 流程 3:按元素名称操作
|
||||
|
||||
```
|
||||
bind_window(action="bind", title="记事本")
|
||||
screenshot ← 返回截图 + GUI elements 列表
|
||||
click_element(name="保存", role="Button") ← UI Automation 查找并点击
|
||||
type_into_element(role="Edit", text="new content")
|
||||
```
|
||||
|
||||
### 流程 4:终端交互
|
||||
|
||||
```
|
||||
bind_window(action="bind", title="PowerShell")
|
||||
screenshot
|
||||
prompt_respond(response_type="yes") ← 回答 y + Enter
|
||||
prompt_respond(response_type="select", arrow_direction="down", arrow_count=2) ← 选第3项
|
||||
```
|
||||
|
||||
### 流程 5:Excel/浏览器滚动
|
||||
|
||||
```
|
||||
bind_window(action="bind", title="Excel")
|
||||
screenshot
|
||||
mouse_wheel(coordinate=[600, 400], delta=-10) ← 向下滚动 10 格
|
||||
mouse_wheel(coordinate=[600, 400], delta=5, direction="horizontal") ← 向右滚动
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 应用兼容性
|
||||
### 应用兼容性
|
||||
|
||||
| 应用类型 | SendMessageW (virtual_*) | 元素操作 (click_element) | 注意 |
|
||||
|---------|--------------------------|------------------------|------|
|
||||
| 传统 Win32 (记事本/写字板) | ✅ | ✅ | 完美支持 |
|
||||
| Office (Excel/Word) | ✅ (COM 自动化) | ✅ | 通过 COM API |
|
||||
| WPF 应用 | ✅ | ✅ | 标准 UIA 支持 |
|
||||
| Electron/Chrome | ⚠️ 部分 | ⚠️ 部分 | 内部渲染不走 Win32 消息 |
|
||||
| UWP/WinUI (Windows Terminal) | ❌ | ❌ | ConPTY 不接受 SendMessageW |
|
||||
| 浏览器网页内容 | ❌ | ❌ | 需要全局 SendInput |
|
||||
| 传统 Win32 (记事本/写字板) | 完美支持 | 完美支持 | 完美支持 |
|
||||
| Office (Excel/Word) | 支持(COM 自动化) | 支持 | 通过 COM API |
|
||||
| WPF 应用 | 支持 | 支持 | 标准 UIA 支持 |
|
||||
| Electron/Chrome | 部分支持 | 部分支持 | 内部渲染不走 Win32 消息 |
|
||||
| UWP/WinUI (Windows Terminal) | 不支持 | 不支持 | ConPTY 不接受 SendMessageW |
|
||||
| 浏览器网页内容 | 不支持 | 不支持 | 需要全局 SendInput |
|
||||
|
||||
**对于不支持 SendMessageW 的应用**,使用通用工具 (`left_click`/`type`/`key`) + `window_management(action="focus")` 先激活窗口。
|
||||
|
||||
---
|
||||
|
||||
## 绑定窗口时的可视化
|
||||
### 绑定窗口时的可视化
|
||||
|
||||
绑定窗口后自动启动三层可视化:
|
||||
|
||||
@@ -358,9 +440,7 @@ mouse_wheel(coordinate=[600, 400], delta=5, direction="horizontal") ← 向右
|
||||
2. **虚拟鼠标光标** — 红色箭头图标,跟随 virtual_mouse 操作移动,点击时闪烁
|
||||
3. **状态指示器** — 窗口底部浮动标签,显示当前操作(通过 status_indicator 控制)
|
||||
|
||||
---
|
||||
|
||||
## Accessibility Snapshot
|
||||
### Accessibility Snapshot
|
||||
|
||||
每次 `screenshot` 时,如果窗口已绑定,会自动附带 GUI 元素列表:
|
||||
|
||||
@@ -374,76 +454,32 @@ GUI elements in this window:
|
||||
```
|
||||
|
||||
模型同时收到 **截图图片 + 结构化元素列表**,可以选择:
|
||||
|
||||
- 用坐标操作:`virtual_mouse(action="click", coordinate=[120, 50])`
|
||||
- 用名称操作:`click_element(name="Save")`
|
||||
|
||||
---
|
||||
|
||||
## UI Automation Control Patterns 参考
|
||||
### UI Automation Control Patterns 参考
|
||||
|
||||
`click_element` / `type_into_element` 底层使用 UI Automation Control Patterns。当前已实现的和可扩展的:
|
||||
|
||||
| Pattern | 用途 | 当前状态 | 可用于 |
|
||||
|---------|------|---------|--------|
|
||||
| `InvokePattern` | 触发点击 | ✅ 已实现 (`click_element`) | 按钮、菜单项、链接 |
|
||||
| `ValuePattern` | 读写文本值 | ✅ 已实现 (`type_into_element`) | 文本框、组合框 |
|
||||
| `TogglePattern` | 切换状态 | ❌ 未实现 | 复选框、开关 |
|
||||
| `SelectionPattern` | 选择项目 | ❌ 未实现 | 下拉菜单、列表 |
|
||||
| `ScrollPattern` | 编程滚动 | ❌ 未实现(用 `mouse_wheel` 替代) | 列表、树、面板 |
|
||||
| `ExpandCollapsePattern` | 展开/折叠 | ❌ 未实现 | 树节点、折叠面板 |
|
||||
| `WindowPattern` | 窗口操作 | ❌ 未实现(用 `window_management` 替代) | 窗口最大化/关闭 |
|
||||
| `TextPattern` | 读取文档文本 | ❌ 未实现 | 文档、富文本 |
|
||||
| `GridPattern` | 表格操作 | ❌ 未实现 | Excel 单元格、数据网格 |
|
||||
| `TablePattern` | 表格结构 | ❌ 未实现 | 表头、行列关系 |
|
||||
| `RangeValuePattern` | 范围值操作 | ❌ 未实现 | 滑块、进度条 |
|
||||
| `TransformPattern` | 移动/缩放 | ❌ 未实现 | 可拖拽元素 |
|
||||
| `InvokePattern` | 触发点击 | 已实现 (`click_element`) | 按钮、菜单项、链接 |
|
||||
| `ValuePattern` | 读写文本值 | 已实现 (`type_into_element`) | 文本框、组合框 |
|
||||
| `TogglePattern` | 切换状态 | 未实现 | 复选框、开关 |
|
||||
| `SelectionPattern` | 选择项目 | 未实现 | 下拉菜单、列表 |
|
||||
| `ScrollPattern` | 编程滚动 | 未实现(用 `mouse_wheel` 替代) | 列表、树、面板 |
|
||||
| `ExpandCollapsePattern` | 展开/折叠 | 未实现 | 树节点、折叠面板 |
|
||||
| `WindowPattern` | 窗口操作 | 未实现(用 `window_management` 替代) | 窗口最大化/关闭 |
|
||||
| `TextPattern` | 读取文档文本 | 未实现 | 文档、富文本 |
|
||||
| `GridPattern` | 表格操作 | 未实现 | Excel 单元格、数据网格 |
|
||||
| `TablePattern` | 表格结构 | 未实现 | 表头、行列关系 |
|
||||
| `RangeValuePattern` | 范围值操作 | 未实现 | 滑块、进度条 |
|
||||
| `TransformPattern` | 移动/缩放 | 未实现 | 可拖拽元素 |
|
||||
|
||||
**扩展路线:** 优先实现 `TogglePattern`(复选框)和 `SelectionPattern`(下拉菜单),这两个在表单自动化中最常用。
|
||||
|
||||
---
|
||||
|
||||
## 屏幕截取技术方案对比
|
||||
|
||||
当前使用 Python Bridge (mss) 进行截图,底层是 GDI BitBlt。三种方案对比:
|
||||
|
||||
| 方案 | API | 当前状态 | 性能 | 优势 | 限制 |
|
||||
|------|-----|---------|------|------|------|
|
||||
| **GDI BitBlt** | `BitBlt` / `PrintWindow` | ✅ 当前使用 (mss/bridge.py) | ~300ms | 简单稳定,支持后台窗口 (PrintWindow) | 不支持硬件加速内容、DPI 处理复杂 |
|
||||
| **DXGI Desktop Duplication** | `IDXGIOutputDuplication` | ❌ 未实现 | ~16ms (60fps) | 硬件加速,支持 HDR,GPU 直接读取 | 不支持单窗口截取,需 D3D11 |
|
||||
| **Windows.Graphics.Capture** | `GraphicsCaptureItem` | ❌ 未实现 | ~16ms | 最新 API,支持单窗口/单显示器,系统级权限管理 | Win10 1903+,首次需用户确认 |
|
||||
|
||||
### 推荐升级路径
|
||||
|
||||
```
|
||||
当前: GDI BitBlt (mss) ─── 全屏 ~300ms, 窗口 ~300ms (PrintWindow)
|
||||
│
|
||||
├─ 近期: DXGI Desktop Duplication ─── 全屏 ~16ms, 但不支持单窗口
|
||||
│
|
||||
└─ 远期: Windows.Graphics.Capture ─── 全屏 + 单窗口都 ~16ms
|
||||
```
|
||||
|
||||
### DXGI Desktop Duplication 实现要点
|
||||
|
||||
```python
|
||||
# bridge.py 中可添加 DXGI 截图(通过 d3dshot 或 dxcam 库)
|
||||
import dxcam # pip install dxcam
|
||||
|
||||
camera = dxcam.create()
|
||||
frame = camera.grab() # numpy array, ~5ms
|
||||
# 转为 JPEG base64 发送
|
||||
```
|
||||
|
||||
### Windows.Graphics.Capture 实现要点
|
||||
|
||||
```python
|
||||
# 需要 WinRT Python 绑定
|
||||
# pip install winrt-Windows.Graphics.Capture winrt-Windows.Graphics.DirectX
|
||||
# 限制:首次调用需要用户在系统弹窗中确认权限
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 输入方式技术矩阵
|
||||
### 输入方式技术矩阵
|
||||
|
||||
不同应用类型需要不同的输入方式:
|
||||
|
||||
@@ -456,7 +492,7 @@ frame = camera.grab() # numpy array, ~5ms
|
||||
| **COM Automation** | Excel/Word COM | 完全编程控制 | 仅 Office 应用 | Excel / Word |
|
||||
| **剪贴板 + 粘贴** | `SetClipboardData` + `Ctrl+V` | 绕过输入限制 | 会覆盖用户剪贴板 | 通用后备 |
|
||||
|
||||
### 按应用类型的推荐输入策略
|
||||
**按应用类型的推荐输入策略:**
|
||||
|
||||
| 应用类型 | 首选 | 后备 | 说明 |
|
||||
|---------|------|------|------|
|
||||
@@ -467,9 +503,46 @@ frame = camera.grab() # numpy array, ~5ms
|
||||
| Windows Terminal (ConPTY) | SendInput (需前台) | 剪贴板粘贴 | ConPTY 不接受外部消息 |
|
||||
| UWP/WinUI 应用 | SendInput (需前台) | UIA | XAML 渲染不走 Win32 消息 |
|
||||
|
||||
---
|
||||
### 屏幕截取技术方案对比
|
||||
|
||||
## 已知限制与待解决
|
||||
当前使用 Python Bridge (mss) 进行截图,底层是 GDI BitBlt。三种方案对比:
|
||||
|
||||
| 方案 | API | 当前状态 | 性能 | 优势 | 限制 |
|
||||
|------|-----|---------|------|------|------|
|
||||
| **GDI BitBlt** | `BitBlt` / `PrintWindow` | 当前使用 (mss/bridge.py) | ~300ms | 简单稳定,支持后台窗口 (PrintWindow) | 不支持硬件加速内容、DPI 处理复杂 |
|
||||
| **DXGI Desktop Duplication** | `IDXGIOutputDuplication` | 未实现 | ~16ms (60fps) | 硬件加速,支持 HDR,GPU 直接读取 | 不支持单窗口截取,需 D3D11 |
|
||||
| **Windows.Graphics.Capture** | `GraphicsCaptureItem` | 未实现 | ~16ms | 最新 API,支持单窗口/单显示器,系统级权限管理 | Win10 1903+,首次需用户确认 |
|
||||
|
||||
**推荐升级路径:**
|
||||
|
||||
```
|
||||
当前: GDI BitBlt (mss) ─── 全屏 ~300ms, 窗口 ~300ms (PrintWindow)
|
||||
│
|
||||
├─ 近期: DXGI Desktop Duplication ─── 全屏 ~16ms, 但不支持单窗口
|
||||
│
|
||||
└─ 远期: Windows.Graphics.Capture ─── 全屏 + 单窗口都 ~16ms
|
||||
```
|
||||
|
||||
**DXGI Desktop Duplication 实现要点:**
|
||||
|
||||
```python
|
||||
# bridge.py 中可添加 DXGI 截图(通过 d3dshot 或 dxcam 库)
|
||||
import dxcam # pip install dxcam
|
||||
|
||||
camera = dxcam.create()
|
||||
frame = camera.grab() # numpy array, ~5ms
|
||||
# 转为 JPEG base64 发送
|
||||
```
|
||||
|
||||
**Windows.Graphics.Capture 实现要点:**
|
||||
|
||||
```python
|
||||
# 需要 WinRT Python 绑定
|
||||
# pip install winrt-Windows.Graphics.Capture winrt-Windows.Graphics.DirectX
|
||||
# 限制:首次调用需要用户在系统弹窗中确认权限
|
||||
```
|
||||
|
||||
### 已知限制与待解决
|
||||
|
||||
| 限制 | 影响 | 计划 |
|
||||
|------|------|------|
|
||||
@@ -479,29 +552,70 @@ frame = camera.grab() # numpy array, ~5ms
|
||||
| DWM 边框对自定义标题栏应用可能无效 | 某些 Electron 应用看不到边框 | 检测并回退到叠加窗口方案 |
|
||||
| 虚拟光标是 PowerShell WinForms 进程 | 启动慢 (~1s),资源占用 | 考虑用 Win32 原生窗口替代 |
|
||||
|
||||
---
|
||||
### 技术路线图
|
||||
|
||||
## 技术路线图
|
||||
#### Phase 1(当前)— 基础功能
|
||||
|
||||
### Phase 1(当前)— 基础功能
|
||||
- ✅ SendMessageW 虚拟输入
|
||||
- ✅ PrintWindow/mss 截图
|
||||
- ✅ UI Automation (InvokePattern + ValuePattern)
|
||||
- ✅ Accessibility Snapshot
|
||||
- ✅ DWM 边框指示
|
||||
- ✅ Python Bridge
|
||||
- SendMessageW 虚拟输入
|
||||
- PrintWindow/mss 截图
|
||||
- UI Automation (InvokePattern + ValuePattern)
|
||||
- Accessibility Snapshot
|
||||
- DWM 边框指示
|
||||
- Python Bridge
|
||||
|
||||
### Phase 2(近期)— 兼容性增强
|
||||
- ⬜ 应用类型自动检测(Win32 vs Terminal vs UWP)
|
||||
- ⬜ 终端类应用自动切换 SendInput + 短暂激活
|
||||
- ⬜ TogglePattern / SelectionPattern 支持
|
||||
- ⬜ DXGI Desktop Duplication 高速截图
|
||||
- ⬜ Accessibility Snapshot 超时保护
|
||||
#### Phase 2(近期)— 兼容性增强
|
||||
|
||||
### Phase 3(远期)— 高级能力
|
||||
- ⬜ Windows.Graphics.Capture(单窗口实时截图)
|
||||
- ⬜ 截图元素标注(在截图上标记 ID 数字)
|
||||
- ⬜ 浏览器 DOM 提取(绑定浏览器时提取网页结构)
|
||||
- ⬜ GridPattern / TablePattern(Excel 单元格级操作)
|
||||
- ⬜ TextPattern(文档内容读取)
|
||||
- ⬜ 多窗口协同操作
|
||||
- 应用类型自动检测(Win32 vs Terminal vs UWP)
|
||||
- 终端类应用自动切换 SendInput + 短暂激活
|
||||
- TogglePattern / SelectionPattern 支持
|
||||
- DXGI Desktop Duplication 高速截图
|
||||
- Accessibility Snapshot 超时保护
|
||||
|
||||
#### Phase 3(远期)— 高级能力
|
||||
|
||||
- Windows.Graphics.Capture(单窗口实时截图)
|
||||
- 截图元素标注(在截图上标记 ID 数字)
|
||||
- 浏览器 DOM 提取(绑定浏览器时提取网页结构)
|
||||
- GridPattern / TablePattern(Excel 单元格级操作)
|
||||
- TextPattern(文档内容读取)
|
||||
- 多窗口协同操作
|
||||
|
||||
## 配置
|
||||
|
||||
### Feature Flag
|
||||
|
||||
Computer Use 入口由 `CHICAGO_MCP` feature flag 控制。
|
||||
|
||||
- **Dev mode**:默认启用(`scripts/dev.ts` 全部启用)
|
||||
- **Build mode**:默认启用(在 `DEFAULT_BUILD_FEATURES` 列表中)
|
||||
- **运行时**:通过环境变量 `FEATURE_CHICAGO_MCP=1` 启用
|
||||
|
||||
入口位置:`src/main.tsx` 中 `feature("CHICAGO_MCP")` 门控,初始化 Computer Use MCP server。
|
||||
|
||||
### 跨平台架构要点
|
||||
|
||||
各平台由 dispatcher + backend 模式分发:
|
||||
|
||||
| 层 | macOS | Windows | Linux |
|
||||
|----|-------|---------|-------|
|
||||
| `computer-use-input/backends/` | darwin.ts | win32.ts | linux.ts |
|
||||
| `computer-use-swift/backends/` | darwin.ts | win32.ts | linux.ts |
|
||||
| `src/utils/computerUse/executor.ts` | darwin 路径 | 跨平台 executor | 跨平台 executor |
|
||||
| `src/utils/computerUse/swiftLoader.ts` | darwin 加载 | platforms/ | platforms/ |
|
||||
|
||||
非 darwin 平台的关键差异:
|
||||
|
||||
- `drainRunLoop.ts` — 非 darwin 无需 CFRunLoop pump(直接执行 fn)
|
||||
- `escHotkey.ts` — 非 darwin 返回 false(已有 Ctrl+C fallback)
|
||||
- `hostAdapter.ts` — 非 darwin 权限检查逻辑:Windows 直接 granted,Linux 检查 xdotool 安装
|
||||
- `common.ts` — 平台标识按 `process.platform` 动态分发:darwin→'native',其他→'none'
|
||||
- `gates.ts` — `hasRequiredSubscription()` 已按平台更新默认值
|
||||
|
||||
### 新增 Linux 后端的要点
|
||||
|
||||
| 步骤 | 文件 | 内容 |
|
||||
|------|------|------|
|
||||
| 1 | `packages/@ant/computer-use-input/src/backends/linux.ts` | xdotool 键鼠(mousemove/click/key/type/getactivewindow) |
|
||||
| 2 | `packages/@ant/computer-use-swift/src/backends/linux.ts` | scrot/grim 截图 + xrandr 显示器 + wmctrl 窗口管理 |
|
||||
| 3 | `packages/@ant/computer-use-input/src/index.ts` | dispatcher 加 `case 'linux'` |
|
||||
| 4 | `packages/@ant/computer-use-swift/src/index.ts` | dispatcher 加 `case 'linux'` |
|
||||
@@ -1,3 +1,9 @@
|
||||
---
|
||||
title: "语音输入(Voice Mode)"
|
||||
description: "Push-to-talk 语音输入,支持豆包语言模型。需 Anthropic OAuth 或本地语音后端。"
|
||||
keywords: ["语音输入", "Push-to-Talk", "豆包 ASR", "STT", "语音转录"]
|
||||
---
|
||||
|
||||
# VOICE_MODE — 语音输入
|
||||
|
||||
> Feature Flag: `FEATURE_VOICE_MODE=1`
|
||||
@@ -1,3 +1,9 @@
|
||||
---
|
||||
title: "浏览器操作工具"
|
||||
description: "让 AI 控制 Chrome 完成网页操作:导航、点击、输入、抓取。"
|
||||
keywords: ["浏览器工具", "Chrome 控制", "网页操作", "Bun WebView", "WEB_BROWSER_TOOL"]
|
||||
---
|
||||
|
||||
# WEB_BROWSER_TOOL — 浏览器工具
|
||||
|
||||
> Feature Flag: `FEATURE_WEB_BROWSER_TOOL=1`
|
||||
@@ -1,195 +0,0 @@
|
||||
# FORK_SUBAGENT — 上下文继承子 Agent
|
||||
|
||||
> Feature Flag: `FEATURE_FORK_SUBAGENT=1`
|
||||
> 实现状态:完整可用
|
||||
> 引用数:4
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
FORK_SUBAGENT 让 AgentTool 生成"fork 子 agent",继承父级完整对话上下文。子 agent 看到父级的所有历史消息、工具集和系统提示,并且与父级共享 API 请求前缀以最大化 prompt cache 命中率。
|
||||
|
||||
### 核心优势
|
||||
|
||||
- **Prompt Cache 最大化**:多个并行 fork 共享相同的 API 请求前缀,只有最后的 directive 文本块不同
|
||||
- **上下文完整性**:子 agent 继承父级的完整对话历史(包括 thinking config)
|
||||
- **权限冒泡**:子 agent 的权限提示上浮到父级终端显示
|
||||
- **Worktree 隔离**:支持 git worktree 隔离,子 agent 在独立分支工作
|
||||
|
||||
## 二、用户交互
|
||||
|
||||
### 触发方式
|
||||
|
||||
当 `FORK_SUBAGENT` 启用时,AgentTool 调用不指定 `subagent_type` 时自动走 fork 路径:
|
||||
|
||||
```
|
||||
// Fork 路径(继承上下文)
|
||||
Agent({ prompt: "修复这个 bug" }) // 无 subagent_type
|
||||
|
||||
// 普通 agent 路径(全新上下文)
|
||||
Agent({ subagent_type: "general-purpose", prompt: "..." })
|
||||
```
|
||||
|
||||
### /fork 命令
|
||||
|
||||
注册了 `/fork` 斜杠命令(当前为 stub)。当 FORK_SUBAGENT 开启时,`/branch` 命令失去 `fork` 别名,避免冲突。
|
||||
|
||||
## 三、实现架构
|
||||
|
||||
### 3.1 门控与互斥
|
||||
|
||||
文件:`packages/builtin-tools/src/tools/AgentTool/forkSubagent.ts:32-39`
|
||||
|
||||
```ts
|
||||
export function isForkSubagentEnabled(): boolean {
|
||||
if (feature('FORK_SUBAGENT')) {
|
||||
if (isCoordinatorMode()) return false // Coordinator 有自己的委派模型
|
||||
if (getIsNonInteractiveSession()) return false // pipe/SDK 模式禁用
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 FORK_AGENT 定义
|
||||
|
||||
```ts
|
||||
export const FORK_AGENT = {
|
||||
agentType: 'fork',
|
||||
tools: ['*'], // 通配符:使用父级完整工具集
|
||||
maxTurns: 200,
|
||||
model: 'inherit', // 继承父级模型
|
||||
permissionMode: 'bubble', // 权限冒泡到父级终端
|
||||
getSystemPrompt: () => '', // 不使用:直接传递父级已渲染 prompt
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 核心调用流程
|
||||
|
||||
```
|
||||
AgentTool.call({ prompt, name })
|
||||
│
|
||||
▼
|
||||
isForkSubagentEnabled() && !subagent_type?
|
||||
│
|
||||
├── No → 普通 agent 路径
|
||||
│
|
||||
└── Yes → Fork 路径
|
||||
│
|
||||
▼
|
||||
递归防护检查
|
||||
├── querySource === 'agent:builtin:fork' → 拒绝
|
||||
└── isInForkChild(messages) → 拒绝
|
||||
│
|
||||
▼
|
||||
获取父级 system prompt
|
||||
├── toolUseContext.renderedSystemPrompt(首选)
|
||||
└── buildEffectiveSystemPrompt(回退)
|
||||
│
|
||||
▼
|
||||
buildForkedMessages(prompt, assistantMessage)
|
||||
├── 克隆父级 assistant 消息
|
||||
├── 生成占位符 tool_result
|
||||
└── 附加 directive 文本块
|
||||
│
|
||||
▼
|
||||
[可选] buildWorktreeNotice()
|
||||
│
|
||||
▼
|
||||
runAgent({
|
||||
useExactTools: true,
|
||||
override.systemPrompt: 父级,
|
||||
forkContextMessages: 父级消息,
|
||||
availableTools: 父级工具,
|
||||
})
|
||||
```
|
||||
|
||||
### 3.4 消息构建:buildForkedMessages
|
||||
|
||||
文件:`packages/builtin-tools/src/tools/AgentTool/forkSubagent.ts:107-169`
|
||||
|
||||
构建的消息结构:
|
||||
|
||||
```
|
||||
[
|
||||
...history (filterIncompleteToolCalls), // 父级完整历史
|
||||
assistant(所有 tool_use 块), // 父级当前 turn 的 assistant 消息
|
||||
user(
|
||||
占位符 tool_result × N + // 相同占位符文本
|
||||
<fork-boilerplate> directive // 每个 fork 不同
|
||||
)
|
||||
]
|
||||
```
|
||||
|
||||
**所有 fork 使用相同的占位符文本**:`"Fork started — processing in background"`。这确保多个并行 fork 的 API 请求前缀完全一致,最大化 prompt cache 命中。
|
||||
|
||||
### 3.5 递归防护
|
||||
|
||||
两层检查防止 fork 嵌套:
|
||||
|
||||
1. **querySource 检查**:`toolUseContext.options.querySource === 'agent:builtin:fork'`。在 `context.options` 上设置,抗自动压缩(autocompact 只重写消息不改 options)
|
||||
2. **消息扫描**:`isInForkChild()` 扫描消息历史中的 `<fork-boilerplate>` 标签
|
||||
|
||||
### 3.6 Worktree 隔离通知
|
||||
|
||||
当 fork + worktree 组合时,追加通知告知子 agent:
|
||||
|
||||
> "你继承了父 agent 在 `{parentCwd}` 的对话上下文,但你在独立的 git worktree `{worktreeCwd}` 中操作。路径需要转换,编辑前重新读取。"
|
||||
|
||||
### 3.7 强制异步
|
||||
|
||||
当 `isForkSubagentEnabled()` 为 true 时,所有 agent 启动都强制异步。`run_in_background` 参数从 schema 中移除。统一通过 `<task-notification>` XML 消息交互。
|
||||
|
||||
## 四、Prompt Cache 优化
|
||||
|
||||
这是整个 fork 设计的核心优化目标:
|
||||
|
||||
| 优化点 | 实现 |
|
||||
|--------|------|
|
||||
| **相同 system prompt** | 直传 `renderedSystemPrompt`,避免重新渲染(GrowthBook 状态可能不一致) |
|
||||
| **相同工具集** | `useExactTools: true` 直接使用父级工具,不经过 `resolveAgentTools` 过滤 |
|
||||
| **相同 thinking config** | 继承父级 thinking 配置(非 fork agent 默认禁用 thinking) |
|
||||
| **相同占位符结果** | 所有 fork 使用 `FORK_PLACEHOLDER_RESULT` 相同文本 |
|
||||
| **ContentReplacementState 克隆** | 默认克隆父级替换状态,保持 wire prefix 一致 |
|
||||
|
||||
## 五、子 Agent 指令
|
||||
|
||||
`buildChildMessage()` 生成 `<fork-boilerplate>` 包裹的指令:
|
||||
|
||||
- 你是 fork worker,不是主 agent
|
||||
- 禁止再次 spawn sub-agent(直接执行)
|
||||
- 不要闲聊、不要元评论
|
||||
- 直接使用工具
|
||||
- 修改文件后要 commit,报告 commit hash
|
||||
- 报告格式:`Scope:` / `Result:` / `Key files:` / `Files changed:` / `Issues:`
|
||||
|
||||
## 六、关键设计决策
|
||||
|
||||
1. **Fork ≠ 普通 agent**:fork 继承完整上下文,普通 agent 从零开始。选择依据是 `subagent_type` 是否存在
|
||||
2. **renderedSystemPrompt 直传**:避免 fork 时重新调用 `getSystemPrompt()`。父级在 turn 开始时冻结 prompt 字节
|
||||
3. **占位符结果共享**:多个并行 fork 使用完全相同的占位符,只有 directive 不同
|
||||
4. **Coordinator 互斥**:Coordinator 模式下禁用 fork,两者有不兼容的委派模型
|
||||
5. **非交互式禁用**:pipe 模式和 SDK 模式下禁用,避免不可见的 fork 嵌套
|
||||
|
||||
## 七、使用方式
|
||||
|
||||
```bash
|
||||
# 启用 feature
|
||||
FEATURE_FORK_SUBAGENT=1 bun run dev
|
||||
|
||||
# 在 REPL 中使用(不指定 subagent_type 即走 fork)
|
||||
# Agent({ prompt: "研究这个模块的结构" })
|
||||
# Agent({ prompt: "实现这个功能" })
|
||||
```
|
||||
|
||||
## 八、文件索引
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `packages/builtin-tools/src/tools/AgentTool/forkSubagent.ts` | ~210 | 核心定义 + 消息构建 + 递归防护 |
|
||||
| `packages/builtin-tools/src/tools/AgentTool/AgentTool.tsx` | — | Fork 路由 + 强制异步 |
|
||||
| `packages/builtin-tools/src/tools/AgentTool/prompt.ts` | — | "When to Fork" 提示词段落 |
|
||||
| `packages/builtin-tools/src/tools/AgentTool/runAgent.ts` | — | useExactTools 路径 |
|
||||
| `packages/builtin-tools/src/tools/AgentTool/resumeAgent.ts` | — | Fork agent 恢复 |
|
||||
| `src/constants/xml.ts` | — | XML 标签常量 |
|
||||
| `src/utils/forkedAgent.ts` | — | CacheSafeParams + ContentReplacementState 克隆 |
|
||||
| `src/commands/fork/index.ts` | — | /fork 命令(stub) |
|
||||
@@ -1,334 +0,0 @@
|
||||
# GrowthBook 功能启用计划
|
||||
|
||||
> 编制日期: 2026-04-06
|
||||
> 基于: feature-flags-codex-review.md + 4 个并行研究代理的深度分析
|
||||
> 前提: 我们是付费订阅用户,拥有有效的 Anthropic API key
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
Claude Code 使用三层门控系统:
|
||||
1. **编译时 feature flag** — `feature('FLAG_NAME')` from `bun:bundle`
|
||||
2. **GrowthBook 远程开关** — `tengu_*` 前缀,通过 SDK 连接 Anthropic 服务端
|
||||
3. **运行时环境变量** — `USER_TYPE`、`CLAUDE_CODE_*` 等
|
||||
|
||||
在我们的反编译版本中,GrowthBook 不启动(analytics 链空实现),导致所有 `tengu_*` 检查默认返回 `false`。
|
||||
|
||||
**核心发现:所有被 GrowthBook 门控的功能代码都是真实现,没有 stub。**
|
||||
|
||||
---
|
||||
|
||||
## 启用方式说明
|
||||
|
||||
### 方式 1:硬编码绕过(推荐先用)
|
||||
在 `src/services/analytics/growthbook.ts` 的 `getFeatureValueInternal()` 函数中添加默认值映射。
|
||||
|
||||
### 方式 2:自建 GrowthBook 服务器
|
||||
```bash
|
||||
docker run -p 3100:3100 growthbook/growthbook
|
||||
# 设置环境变量
|
||||
CLAUDE_GB_ADAPTER_URL=http://localhost:3100
|
||||
CLAUDE_GB_ADAPTER_KEY=sdk-xxx
|
||||
```
|
||||
|
||||
### 方式 3:恢复原生 1P 连接
|
||||
让 `is1PEventLoggingEnabled()` 返回 `true`,连接 Anthropic 的 GrowthBook 服务端。
|
||||
注意:会发送使用统计(不含代码/对话内容)。
|
||||
|
||||
---
|
||||
|
||||
## 优先级 P0:纯本地功能(零外部依赖,立即可用)
|
||||
|
||||
这些功能不需要 API 调用,开启 gate 即可工作。
|
||||
|
||||
### P0-1. 自定义快捷键
|
||||
- **Gate**: `tengu_keybinding_customization_release` → `true`
|
||||
- **编译 flag**: 无(已内置)
|
||||
- **代码量**: 473 行,完整实现
|
||||
- **功能**: 加载 `~/.claude/keybindings.json`,支持热重载、重复键检测、结构验证
|
||||
- **效果**: 用户可自定义所有快捷键
|
||||
- **风险**: 无
|
||||
|
||||
### P0-2. 流式工具执行
|
||||
- **Gate**: `tengu_streaming_tool_execution2` → `true`
|
||||
- **编译 flag**: 无(已内置)
|
||||
- **代码量**: 577 行(StreamingToolExecutor),完整实现
|
||||
- **功能**: API 响应还在流式返回时就开始执行工具,减少等待时间
|
||||
- **效果**: 显著提升交互速度
|
||||
- **风险**: 低(生产级代码,有错误处理)
|
||||
|
||||
### P0-3. 定时任务系统
|
||||
- **Gate**: `tengu_kairos_cron` → `true`(额外:`tengu_kairos_cron_durable` 默认 `true`)
|
||||
- **编译 flag**: `AGENT_TRIGGERS`(需新增)或 `AGENT_TRIGGERS_REMOTE`(已启用)
|
||||
- **代码量**: 1025 行(cronTasks + cronScheduler),完整实现
|
||||
- **功能**: 本地 cron 调度,支持一次性/周期性任务、防雷群效应 jitter、自动过期
|
||||
- **效果**: 可设置定时执行的 Claude 任务
|
||||
- **风险**: 低
|
||||
|
||||
### P0-4. Agent 团队 / Swarm
|
||||
- **Gate**: `tengu_amber_flint` → `true`(这是 kill switch,默认已 `true`)
|
||||
- **编译 flag**: 无(已内置)
|
||||
- **代码量**: 45 行(gate 层),实际 swarm 实现在 teammate tools 中
|
||||
- **功能**: 多 agent 协作,需额外设置 `--agent-teams` 或 `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`
|
||||
- **效果**: 允许创建和管理 agent 团队
|
||||
- **风险**: 无(kill switch 默认就是 true)
|
||||
|
||||
### P0-5. Token 高效 JSON 工具格式
|
||||
- **Gate**: `tengu_amber_json_tools` → `true`
|
||||
- **编译 flag**: 无(已内置)
|
||||
- **代码量**: betas.ts 中几行 gate 检查
|
||||
- **功能**: 启用 FC v3 格式,减少约 4.5% 的输出 token
|
||||
- **效果**: 省钱
|
||||
- **风险**: 低(需要模型支持该 beta header)
|
||||
|
||||
### P0-6. Ultrathink 扩展思考
|
||||
- **Gate**: `tengu_turtle_carbon` → `true`(默认已 `true`,kill switch)
|
||||
- **编译 flag**: 无
|
||||
- **功能**: 通过关键词触发扩展思考模式
|
||||
- **效果**: 已默认启用,确保不被远程关闭即可
|
||||
- **风险**: 无
|
||||
|
||||
### P0-7. 即时模型切换
|
||||
- **Gate**: `tengu_immediate_model_command` → `true`
|
||||
- **编译 flag**: 无
|
||||
- **功能**: 在 query 运行过程中即时执行 `/model`、`/fast`、`/effort` 命令
|
||||
- **效果**: 无需等当前任务完成就能切换
|
||||
- **风险**: 低
|
||||
|
||||
---
|
||||
|
||||
## 优先级 P1:需要 Claude API 的功能(有 API key 即可用)
|
||||
|
||||
这些功能需要调用 Claude API(使用 forked subagent 或 queryModel),有订阅即可。
|
||||
|
||||
### P1-1. 会话记忆
|
||||
- **Gate**: `tengu_session_memory` → `true`(配置:`tengu_sm_config` → `{}`)
|
||||
- **编译 flag**: 无(已内置)
|
||||
- **代码量**: 1127 行,完整实现
|
||||
- **功能**: 跨会话上下文持久化。用 forked agent 定期提取会话笔记到 markdown 文件
|
||||
- **效果**: Claude 记住跨会话的工作上下文
|
||||
- **依赖**: Claude API(forked subagent)
|
||||
- **风险**: 低(额外 API token 消耗)
|
||||
|
||||
### P1-2. 自动记忆提取
|
||||
- **Gate**: `tengu_passport_quail` → `true`(相关:`tengu_moth_copse`、`tengu_coral_fern`)
|
||||
- **编译 flag**: `EXTRACT_MEMORIES`(需新增)
|
||||
- **代码量**: 616 行,完整实现
|
||||
- **功能**: 对话中自动提取持久记忆到 `~/.claude/projects/<path>/memory/`
|
||||
- **效果**: 自动构建项目知识库
|
||||
- **依赖**: Claude API(forked subagent)
|
||||
- **风险**: 低
|
||||
|
||||
### P1-3. 提示建议
|
||||
- **Gate**: `tengu_chomp_inflection` → `true`
|
||||
- **编译 flag**: 无(已内置)
|
||||
- **代码量**: 525 行,完整实现
|
||||
- **功能**: 自动生成下一步操作建议,带投机预取(speculation prefetch)
|
||||
- **效果**: 更流畅的交互体验
|
||||
- **依赖**: Claude API(forked subagent)
|
||||
- **风险**: 低(额外 API 消耗,但有缓存感知)
|
||||
|
||||
### P1-4. 验证代理
|
||||
- **Gate**: `tengu_hive_evidence` → `true`
|
||||
- **编译 flag**: `VERIFICATION_AGENT`(需新增)
|
||||
- **代码量**: 153 行(agent 定义),完整实现
|
||||
- **功能**: 对抗性验证 agent,主动尝试打破你的实现(只读模式)
|
||||
- **效果**: 自动化代码验证
|
||||
- **依赖**: Claude API(subagent)
|
||||
- **风险**: 低(只读,不修改代码)
|
||||
|
||||
### P1-5. Brief 模式
|
||||
- **Gate**: `tengu_kairos_brief` → `true`
|
||||
- **编译 flag**: `KAIROS` 或 `KAIROS_BRIEF`(需新增)
|
||||
- **代码量**: 335 行,完整实现
|
||||
- **功能**: `/brief` 命令切换精简输出模式
|
||||
- **效果**: 减少冗余输出
|
||||
- **依赖**: Claude API
|
||||
- **风险**: 低
|
||||
|
||||
### P1-6. 离开摘要
|
||||
- **Gate**: `tengu_sedge_lantern` → `true`
|
||||
- **编译 flag**: `AWAY_SUMMARY`(需新增)
|
||||
- **代码量**: 176 行,完整实现
|
||||
- **功能**: 离开终端 5 分钟后返回时自动总结期间发生了什么
|
||||
- **效果**: 快速恢复上下文
|
||||
- **依赖**: Claude API + 终端焦点事件支持
|
||||
- **风险**: 低
|
||||
|
||||
### P1-7. 自动梦境
|
||||
- **Gate**: `tengu_onyx_plover` → `{"enabled": true}`
|
||||
- **编译 flag**: 无(已内置,但检查 auto-memory 是否启用)
|
||||
- **代码量**: 349 行,完整实现
|
||||
- **功能**: 后台自动整理/巩固记忆(等同于自动执行 `/dream`)
|
||||
- **效果**: 记忆自动保持整洁有序
|
||||
- **依赖**: Claude API(forked subagent)+ auto-memory 启用
|
||||
- **风险**: 低
|
||||
|
||||
### P1-8. 空闲返回提示
|
||||
- **Gate**: `tengu_willow_mode` → `"dialog"` 或 `"hint"`
|
||||
- **编译 flag**: 无
|
||||
- **功能**: 对话太大且缓存过期时,提示用户开新会话
|
||||
- **效果**: 避免在过期缓存上浪费 token
|
||||
- **风险**: 无
|
||||
|
||||
---
|
||||
|
||||
## 优先级 P2:增强型功能(提升体验但非必须)
|
||||
|
||||
### P2-1. MCP 指令增量传输
|
||||
- **Gate**: `tengu_basalt_3kr` → `true`
|
||||
- **功能**: 只发送变化的 MCP 指令而非全量
|
||||
- **效果**: 减少 token 消耗
|
||||
- **风险**: 低
|
||||
|
||||
### P2-2. 叶剪枝优化
|
||||
- **Gate**: `tengu_pebble_leaf_prune` → `true`
|
||||
- **功能**: 会话存储中移除死胡同消息分支
|
||||
- **效果**: 减少存储和加载时间
|
||||
- **风险**: 低
|
||||
|
||||
### P2-3. 消息合并
|
||||
- **Gate**: `tengu_chair_sermon` → `true`
|
||||
- **功能**: 合并相邻的 tool_result + text 块
|
||||
- **效果**: 减少 token 消耗
|
||||
- **风险**: 低
|
||||
|
||||
### P2-4. 深度链接
|
||||
- **Gate**: `tengu_lodestone_enabled` → `true`
|
||||
- **功能**: 注册 `claude://` URL 协议处理器
|
||||
- **效果**: 可从浏览器直接打开 Claude Code
|
||||
- **风险**: 低
|
||||
|
||||
### P2-5. Agent 自动转后台
|
||||
- **Gate**: `tengu_auto_background_agents` → `true`
|
||||
- **功能**: Agent 任务运行 120s 后自动转为后台
|
||||
- **效果**: 不再阻塞主交互
|
||||
- **风险**: 低
|
||||
|
||||
### P2-6. 细粒度工具状态
|
||||
- **Gate**: `tengu_fgts` → `true`
|
||||
- **功能**: 系统提示中包含细粒度工具状态信息
|
||||
- **效果**: 模型更好地理解工具可用性
|
||||
- **风险**: 低
|
||||
|
||||
### P2-7. 文件操作 git diff
|
||||
- **Gate**: `tengu_quartz_lantern` → `true`
|
||||
- **功能**: 文件写入/编辑时计算 git diff(仅远程会话)
|
||||
- **效果**: 更好的变更追踪
|
||||
- **风险**: 低
|
||||
|
||||
---
|
||||
|
||||
## 优先级 P3:需要自建服务或 Anthropic OAuth
|
||||
|
||||
### P3-1. 团队记忆
|
||||
- **Gate**: `tengu_herring_clock` → `true`
|
||||
- **编译 flag**: `TEAMMEM`(需新增)
|
||||
- **代码量**: 1180+ 行,完整实现
|
||||
- **功能**: 跨 agent 共享记忆,同步到 Anthropic API
|
||||
- **依赖**: Anthropic OAuth + GitHub remote
|
||||
- **状态**: 需要 Anthropic 的 `/api/claude_code/team_memory` 端点
|
||||
- **可行性**: 除非自建兼容 API,否则无法使用
|
||||
|
||||
### P3-2. 设置同步
|
||||
- **Gate**: `tengu_enable_settings_sync_push` + `tengu_strap_foyer` → `true`
|
||||
- **编译 flag**: `UPLOAD_USER_SETTINGS` / `DOWNLOAD_USER_SETTINGS`(需新增)
|
||||
- **代码量**: 582 行,完整实现
|
||||
- **功能**: 跨设备设置同步
|
||||
- **依赖**: Anthropic OAuth + `/api/claude_code/user_settings`
|
||||
- **可行性**: 同上
|
||||
|
||||
### P3-3. Bridge 远程控制
|
||||
- **Gate**: `tengu_ccr_bridge` → `true`(已有编译 flag `BRIDGE_MODE` dev 模式启用)
|
||||
- **代码量**: 12,619 行,完整实现
|
||||
- **功能**: claude.ai 网页端远程控制 CLI
|
||||
- **依赖**: claude.ai 订阅 + WebSocket 后端
|
||||
- **可行性**: 需要 Anthropic 的 CCR 后端
|
||||
|
||||
### P3-4. 远程定时 Agent
|
||||
- **Gate**: `tengu_surreal_dali` → `true`
|
||||
- **功能**: 创建在远程执行的定时 agent
|
||||
- **依赖**: Anthropic CCR 基础设施
|
||||
- **可行性**: 需要远程服务
|
||||
|
||||
---
|
||||
|
||||
## Kill Switch 清单(确保不被远程关闭)
|
||||
|
||||
这些 gate 默认为 `true`,是 kill switch。应确保它们保持 `true`:
|
||||
|
||||
| Gate | 默认 | 控制什么 |
|
||||
|---|---|---|
|
||||
| `tengu_turtle_carbon` | `true` | Ultrathink 扩展思考 |
|
||||
| `tengu_amber_stoat` | `true` | 内置 Explore/Plan agent |
|
||||
| `tengu_amber_flint` | `true` | Agent 团队/Swarm |
|
||||
| `tengu_slim_subagent_claudemd` | `true` | 子 agent 精简 CLAUDE.md |
|
||||
| `tengu_birch_trellis` | `true` | tree-sitter bash 安全分析 |
|
||||
| `tengu_collage_kaleidoscope` | `true` | macOS 剪贴板图片读取 |
|
||||
| `tengu_compact_cache_prefix` | `true` | 压缩时复用 prompt cache |
|
||||
| `tengu_kairos_cron_durable` | `true` | 持久化 cron 任务 |
|
||||
| `tengu_attribution_header` | `true` | API 请求署名 |
|
||||
| `tengu_slate_prism` | `true` | Agent 进度摘要 |
|
||||
|
||||
---
|
||||
|
||||
## 需要新增的编译 flag
|
||||
|
||||
以下编译时 flag 尚未在 `build.ts` / `scripts/dev.ts` 中启用,但功能代码完整:
|
||||
|
||||
| Flag | 用于 | 优先级 |
|
||||
|---|---|---|
|
||||
| `AGENT_TRIGGERS` | 定时任务系统(P0-3) | P0 |
|
||||
| `EXTRACT_MEMORIES` | 自动记忆提取(P1-2) | P1 |
|
||||
| `VERIFICATION_AGENT` | 验证代理(P1-4) | P1 |
|
||||
| `KAIROS` 或 `KAIROS_BRIEF` | Brief 模式(P1-5) | P1 |
|
||||
| `AWAY_SUMMARY` | 离开摘要(P1-6) | P1 |
|
||||
| `TEAMMEM` | 团队记忆(P3-1) | P3 |
|
||||
|
||||
---
|
||||
|
||||
## 实施路线图
|
||||
|
||||
### Phase 1:硬编码 P0 纯本地 gate(最快见效)
|
||||
1. 在 growthbook.ts 添加默认值映射
|
||||
2. 在 build.ts / dev.ts 添加 `AGENT_TRIGGERS` 编译 flag
|
||||
3. 验证 7 个 P0 功能正常工作
|
||||
4. 预计工作量:1-2 小时
|
||||
|
||||
### Phase 2:启用 P1 API 依赖功能
|
||||
1. 添加编译 flag:`EXTRACT_MEMORIES`、`VERIFICATION_AGENT`、`KAIROS_BRIEF`、`AWAY_SUMMARY`
|
||||
2. 添加 P1 gate 默认值
|
||||
3. 验证 8 个 P1 功能正常工作
|
||||
4. 预计工作量:2-3 小时
|
||||
|
||||
### Phase 3:评估自建 GrowthBook(可选)
|
||||
1. Docker 部署 GrowthBook 服务器
|
||||
2. 迁移硬编码值到 GrowthBook 后台管理
|
||||
3. 获得 Web UI 管理所有 flag 的能力
|
||||
4. 预计工作量:半天
|
||||
|
||||
### Phase 4:评估远程功能(可选)
|
||||
1. 研究是否可以使用 Anthropic OAuth
|
||||
2. 评估团队记忆、设置同步的自建可行性
|
||||
3. 预计工作量:待评估
|
||||
|
||||
---
|
||||
|
||||
## 隐私说明
|
||||
|
||||
### 硬编码绕过(方案 A)
|
||||
- **零数据外发**
|
||||
- GrowthBook SDK 不启动
|
||||
- 完全离线运行
|
||||
|
||||
### 自建 GrowthBook(方案 B)
|
||||
- 数据仅发送到你自己的服务器
|
||||
- Anthropic 无法获取任何数据
|
||||
- 可通过 Web UI 实时管理所有 flag
|
||||
|
||||
### 恢复原生 1P(方案 C)
|
||||
- 会发送使用统计到 `api.anthropic.com`
|
||||
- **不发送**:代码、对话内容、API key
|
||||
- **会发送**:邮箱、设备 ID、机器指纹、仓库哈希、订阅类型
|
||||
- 可用 `DISABLE_TELEMETRY=1` 关闭遥测(但同时关闭 GrowthBook)
|
||||
@@ -1,182 +0,0 @@
|
||||
# KAIROS — 常驻助手模式
|
||||
|
||||
> Feature Flag: `FEATURE_KAIROS=1`(及子 Feature)
|
||||
> 实现状态:核心框架完整,部分子模块为 stub;proactive/sleep 节奏控制已可用
|
||||
> 引用数:154(全库最大)
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
KAIROS 将 Claude Code CLI 从"问答工具"转变为"常驻助手"。开启后,CLI 持续运行在后台,支持:
|
||||
|
||||
- **持久化 bridge 会话**:跨终端重启复用 session,通过 Anthropic OAuth 连接 claude.ai
|
||||
- **后台执行任务**:用户离开终端时继续工作(配合 PROACTIVE feature)
|
||||
- **推送通知到移动端**:任务完成或需要输入时推送(配合 `KAIROS_PUSH_NOTIFICATION`)
|
||||
- **每日记忆日志**:自动记录和回顾工作内容(配合 `KAIROS_DREAM`)
|
||||
- **外部频道消息接入**:Slack/Discord/Telegram 消息转发到 CLI(配合 `KAIROS_CHANNELS`)
|
||||
- **结构化 Brief 输出**:通过 BriefTool 输出结构化消息(配合 `KAIROS_BRIEF`)
|
||||
|
||||
### 子 Feature 依赖关系
|
||||
|
||||
```
|
||||
KAIROS (主开关)
|
||||
├── KAIROS_BRIEF (BriefTool, 结构化输出)
|
||||
├── KAIROS_CHANNELS (外部频道消息)
|
||||
├── KAIROS_PUSH_NOTIFICATION (移动端推送)
|
||||
├── KAIROS_GITHUB_WEBHOOKS (GitHub PR webhook)
|
||||
└── KAIROS_DREAM (记忆蒸馏)
|
||||
```
|
||||
|
||||
**注意**:PROACTIVE 与 KAIROS 强绑定。所有代码检查都是 `feature('PROACTIVE') || feature('KAIROS')`,即 KAIROS 开启时自动获得 proactive 能力。
|
||||
|
||||
## 二、系统提示
|
||||
|
||||
KAIROS 在系统提示中注入两大段落:
|
||||
|
||||
### 2.1 Brief 段落 (`getBriefSection`)
|
||||
|
||||
文件:`src/constants/prompts.ts:847-858`
|
||||
|
||||
当 `feature('KAIROS') || feature('KAIROS_BRIEF')` 时注入。Brief 工具(`SendUserMessage`)的结构化消息输出指令。`/brief` toggle 和 `--brief` flag 只控制显示过滤,不影响模型行为。
|
||||
|
||||
### 2.2 Proactive/Autonomous Work 段落 (`getProactiveSection`)
|
||||
|
||||
文件:`src/constants/prompts.ts:864-918`
|
||||
|
||||
当 `feature('PROACTIVE') || feature('KAIROS')` 且 `isProactiveActive()` 时注入。核心行为指令:
|
||||
|
||||
- **Tick 驱动**:通过 `<tick_tag>` prompt 保持存活,每个 tick 包含用户当前本地时间
|
||||
- **节奏控制**:使用 `SleepTool` 控制等待间隔(prompt cache 5 分钟过期)
|
||||
- **空操作时必须 Sleep**:禁止输出 "still waiting" 类文本(浪费 turn 和 token)
|
||||
- **偏向行动**:读文件、搜索代码、修改文件、commit — 都不需询问
|
||||
- **终端焦点感知**:`terminalFocus` 字段指示用户是否在看终端
|
||||
- Unfocused → 高度自主行动
|
||||
- Focused → 更协作,展示选择
|
||||
|
||||
## 三、实现架构
|
||||
|
||||
### 3.1 核心模块
|
||||
|
||||
| 模块 | 文件 | 状态 | 职责 |
|
||||
|------|------|------|------|
|
||||
| Assistant 入口 | `src/assistant/index.ts` | Stub | `isAssistantMode()`、`initializeAssistantTeam()` |
|
||||
| Session 发现 | `src/assistant/sessionDiscovery.ts` | Stub | 发现可用 bridge session |
|
||||
| Session 历史 | `src/assistant/sessionHistory.ts` | Stub | 持久化 session 历史 |
|
||||
| Gate 控制 | `src/assistant/gate.ts` | Stub | GrowthBook 门控检查 |
|
||||
| Session 选择器 | `src/assistant/AssistantSessionChooser.ts` | Stub | UI 选择 session |
|
||||
| BriefTool | `src/tools/BriefTool/` | Stub | 结构化消息输出工具 |
|
||||
| Channel Notification | `src/services/mcp/channelNotification.ts` | Stub | 外部频道消息接入 |
|
||||
| Dream Task | `src/components/tasks/src/tasks/DreamTask/` | Stub | 记忆蒸馏任务 |
|
||||
| Memory Directory | `src/memdir/memdir.ts` | Stub | 记忆目录管理 |
|
||||
|
||||
### 3.2 SleepTool(与 Proactive 共享)
|
||||
|
||||
文件:`src/tools/SleepTool/prompt.ts`
|
||||
|
||||
SleepTool 是 KAIROS/Proactive 的节奏控制核心。工具描述让模型理解"休眠"概念:
|
||||
- 工具名:`Sleep`
|
||||
- 功能:等待指定时间后响应 tick prompt;若队列出现新工作或 proactive 被关闭,会提前唤醒
|
||||
- 与 `<tick_tag>` 配合实现心跳式自主工作
|
||||
- 远程控制 surfaces 可通过 `automation_state` 看到 `standby` / `sleeping` 两种状态
|
||||
|
||||
### 3.3 Bridge 集成
|
||||
|
||||
KAIROS 通过 Bridge Mode(`src/bridge/`)连接到 claude.ai 服务器:
|
||||
|
||||
```
|
||||
claude.ai web/app
|
||||
│
|
||||
▼ (HTTPS long-poll)
|
||||
┌──────────────────────┐
|
||||
│ Bridge API Client │ src/bridge/bridgeApi.ts
|
||||
│ (register/poll/ │
|
||||
│ acknowledge) │
|
||||
└──────────┬───────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ Session Runner │ src/bridge/sessionRunner.ts
|
||||
│ (创建/恢复 REPL) │
|
||||
└──────────┬───────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ REPL + Proactive │ Tick 驱动自主工作
|
||||
│ Tick Loop │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
### 3.4 数据流
|
||||
|
||||
```
|
||||
用户从 claude.ai 发送消息
|
||||
│
|
||||
▼
|
||||
Bridge pollForWork() 收到 WorkResponse
|
||||
│
|
||||
▼
|
||||
acknowledgeWork() 确认接收
|
||||
│
|
||||
▼
|
||||
sessionRunner 创建/恢复 REPL session
|
||||
│
|
||||
▼
|
||||
用户消息注入到 REPL 对话
|
||||
│
|
||||
▼
|
||||
模型处理 → 工具调用 → BriefTool 结构化输出
|
||||
│
|
||||
▼
|
||||
结果通过 Bridge API 回传到 claude.ai
|
||||
```
|
||||
|
||||
## 四、关键设计决策
|
||||
|
||||
1. **Tick 驱动而非事件驱动**:模型通过 SleepTool 自行控制唤醒频率,而非外部事件推送。简化架构但增加 API 调用开销
|
||||
2. **KAIROS ⊃ PROACTIVE**:所有 proactive 检查都包含 KAIROS,无需同时开启两个 flag
|
||||
3. **Brief 显示/行为分离**:`/brief` toggle 只控制 UI 过滤,模型始终可以使用 BriefTool
|
||||
4. **Terminal Focus 感知**:模型根据用户是否在看终端自动调节自主程度
|
||||
5. **GrowthBook 门控**:部分功能(如推送通知)即使 feature flag 开启还需要服务端 GrowthBook 开关
|
||||
|
||||
## 五、使用方式
|
||||
|
||||
```bash
|
||||
# 最小启用(常驻助手 + Brief)
|
||||
FEATURE_KAIROS=1 FEATURE_KAIROS_BRIEF=1 bun run dev
|
||||
|
||||
# 全功能启用
|
||||
FEATURE_KAIROS=1 \
|
||||
FEATURE_KAIROS_BRIEF=1 \
|
||||
FEATURE_KAIROS_CHANNELS=1 \
|
||||
FEATURE_KAIROS_PUSH_NOTIFICATION=1 \
|
||||
FEATURE_KAIROS_GITHUB_WEBHOOKS=1 \
|
||||
FEATURE_PROACTIVE=1 \
|
||||
bun run dev
|
||||
|
||||
# 配合 Token Budget 使用
|
||||
FEATURE_KAIROS=1 FEATURE_TOKEN_BUDGET=1 bun run dev
|
||||
```
|
||||
|
||||
## 六、外部依赖
|
||||
|
||||
- **Anthropic OAuth**:必须使用 claude.ai 订阅登录(非 API key)
|
||||
- **GrowthBook**:服务端特性门控(`tengu_ccr_bridge` 等)
|
||||
- **Bridge API**:`/v1/environments/bridge` 系列端点
|
||||
|
||||
## 七、文件索引
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `src/assistant/index.ts` | 9 | Assistant 模块入口(stub) |
|
||||
| `src/assistant/gate.ts` | — | GrowthBook 门控(stub) |
|
||||
| `src/assistant/sessionDiscovery.ts` | — | Session 发现(stub) |
|
||||
| `src/assistant/sessionHistory.ts` | — | Session 历史(stub) |
|
||||
| `src/assistant/AssistantSessionChooser.ts` | — | Session 选择 UI(stub) |
|
||||
| `src/tools/BriefTool/` | — | BriefTool 实现(stub) |
|
||||
| `src/tools/SleepTool/prompt.ts` | ~30 | SleepTool 工具提示 |
|
||||
| `src/tools/SleepTool/SleepTool.ts` | ~200 | 休眠/唤醒与 automation metadata |
|
||||
| `src/services/mcp/channelNotification.ts` | 5 | 频道消息接入(stub) |
|
||||
| `src/memdir/memdir.ts` | — | 记忆目录管理(stub) |
|
||||
| `src/constants/prompts.ts:557,847-918` | 72 | 系统提示注入 |
|
||||
| `src/components/tasks/src/tasks/DreamTask/` | 3 | Dream 任务(stub) |
|
||||
| `src/proactive/index.ts` | — | Proactive 核心(KAIROS 共享) |
|
||||
| `src/utils/sessionState.ts` | — | 向 bridge/CCR 暴露 automation 状态 |
|
||||
@@ -1,321 +0,0 @@
|
||||
# LAN Pipes — 技术实现文档
|
||||
|
||||
面向开发者的实现细节。用户指南见 [lan-pipes.md](./lan-pipes.md)。
|
||||
|
||||
---
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
Machine A (192.168.50.22) Machine B (192.168.50.27)
|
||||
┌───────────────────────────┐ ┌───────────────────────────┐
|
||||
│ PipeServer │ │ PipeServer │
|
||||
│ UDS: ~/.claude/pipes/ │ │ UDS: ~/.claude/pipes/ │
|
||||
│ cli-abc.sock │ │ cli-def.sock │
|
||||
│ TCP: 0.0.0.0:<random> │◄──TCP───►│ TCP: 0.0.0.0:<random> │
|
||||
├───────────────────────────┤ ├───────────────────────────┤
|
||||
│ LanBeacon │ │ LanBeacon │
|
||||
│ UDP 224.0.71.67:7101 │◄──UDP───►│ UDP 224.0.71.67:7101 │
|
||||
├───────────────────────────┤ ├───────────────────────────┤
|
||||
│ usePipeIpc (hook) │ │ usePipeIpc (hook) │
|
||||
│ initPipeServer │ │ initPipeServer │
|
||||
│ registerMessageHandlers │ │ registerMessageHandlers │
|
||||
│ runMainHeartbeat │ │ runSubHeartbeat │
|
||||
│ cleanupPipeIpc │ │ cleanupPipeIpc │
|
||||
└───────────────────────────┘ └───────────────────────────┘
|
||||
```
|
||||
|
||||
## Feature Flag
|
||||
|
||||
`LAN_PIPES` — 在 `scripts/dev.ts` 和 `build.ts` 的 `DEFAULT_FEATURES` 中启用。
|
||||
|
||||
所有 LAN 代码路径通过 `feature('LAN_PIPES')` 编译时门控。`feature()` 只能在 `if` 或三元中使用(Bun 编译时常量约束)。
|
||||
|
||||
---
|
||||
|
||||
## 核心文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `src/utils/pipeTransport.ts` | PipeServer/PipeClient(UDS + TCP 双模式) |
|
||||
| `src/utils/lanBeacon.ts` | UDP multicast beacon + module singleton |
|
||||
| `src/utils/ndjsonFramer.ts` | 共享 NDJSON socket 帧解析 |
|
||||
| `src/utils/pipeRegistry.ts` | 文件注册表 + `mergeWithLanPeers()` |
|
||||
| `src/utils/peerAddress.ts` | 地址解析(uds/bridge/tcp scheme) |
|
||||
| `src/utils/pipePermissionRelay.ts` | 权限转发 + `setPipeRelay`/`getPipeRelay` singleton |
|
||||
| `src/hooks/usePipeIpc.ts` | 生命周期 hook(从 REPL.tsx 提取) |
|
||||
| `src/hooks/usePipeRelay.ts` | 消息回传 hook |
|
||||
| `src/hooks/usePipePermissionForward.ts` | 权限转发 hook |
|
||||
| `src/hooks/usePipeRouter.ts` | 输入路由 hook |
|
||||
| `src/hooks/useMasterMonitor.ts` | slave 注册表 + 消息订阅 |
|
||||
|
||||
---
|
||||
|
||||
## PipeServer TCP 扩展
|
||||
|
||||
`src/utils/pipeTransport.ts`
|
||||
|
||||
### 类型
|
||||
|
||||
```typescript
|
||||
export type PipeTransportMode = 'uds' | 'tcp'
|
||||
export type TcpEndpoint = { host: string; port: number }
|
||||
export type PipeServerOptions = { enableTcp?: boolean; tcpPort?: number }
|
||||
```
|
||||
|
||||
### PipeServer 变更
|
||||
|
||||
- `setupSocket(socket)` — 从 start() 提取的共享方法,UDS 和 TCP 共用
|
||||
- `start(options?)` — 可选启用 TCP,port=0 让 OS 分配
|
||||
- 内部维护两个 `net.Server`,共享同一组 `clients: Set<Socket>` 和 `handlers`
|
||||
- `tcpAddress` getter 暴露 TCP 端口
|
||||
- `close()` 同时关闭两个 server
|
||||
|
||||
socket 帧解析使用 `attachNdjsonFramer()` from `ndjsonFramer.ts`(替代原先 3 份重复代码)。
|
||||
|
||||
### PipeClient 变更
|
||||
|
||||
- 构造函数新增可选 `TcpEndpoint` 参数
|
||||
- `connect()` 根据 tcpEndpoint 分派到 `connectTcp()` 或 `connectUds()`
|
||||
- TCP 不需要文件存在轮询,直接建连
|
||||
|
||||
---
|
||||
|
||||
## LAN Beacon
|
||||
|
||||
`src/utils/lanBeacon.ts`
|
||||
|
||||
### 协议参数
|
||||
|
||||
| 参数 | 值 |
|
||||
|------|-----|
|
||||
| Multicast 组 | `224.0.71.67` |
|
||||
| 端口 | `7101` |
|
||||
| 广播间隔 | `3000ms` |
|
||||
| Peer 超时 | `15000ms` |
|
||||
| TTL | `1` |
|
||||
|
||||
### Announce 包
|
||||
|
||||
```typescript
|
||||
type LanAnnounce = {
|
||||
proto: 'claude-pipe-v1'
|
||||
pipeName: string
|
||||
machineId: string
|
||||
hostname: string
|
||||
ip: string
|
||||
tcpPort: number
|
||||
role: 'main' | 'sub'
|
||||
ts: number
|
||||
}
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
class LanBeacon extends EventEmitter {
|
||||
constructor(announce: Omit<LanAnnounce, 'proto' | 'ts'>)
|
||||
start(): void
|
||||
stop(): void
|
||||
getPeers(): Map<string, LanAnnounce> // 防御性拷贝
|
||||
updateAnnounce(partial): void // 使用 spread(不可变更新)
|
||||
|
||||
on('peer-discovered', (peer: LanAnnounce) => void)
|
||||
on('peer-lost', (pipeName: string) => void)
|
||||
}
|
||||
```
|
||||
|
||||
### 存储
|
||||
|
||||
module-level singleton:`getLanBeacon()` / `setLanBeacon()`。不挂在 Zustand state 上(避免 `setState` 展开时丢失引用)。
|
||||
|
||||
### 网卡绑定
|
||||
|
||||
`addMembership(group, localIp)` + `setMulticastInterface(localIp)` 指定 LAN 网卡。解决 Windows 上 WSL/Docker 虚拟网卡劫持 multicast 的问题。
|
||||
|
||||
---
|
||||
|
||||
## Hook 架构
|
||||
|
||||
从 REPL.tsx 提取的 ~830 行 Pipe IPC 代码:
|
||||
|
||||
### usePipeIpc(生命周期)
|
||||
|
||||
`src/hooks/usePipeIpc.ts`(623 行)
|
||||
|
||||
在 REPL.tsx 顶层通过 feature-gated require 加载:
|
||||
|
||||
```typescript
|
||||
const usePipeIpc = feature('UDS_INBOX')
|
||||
? require('../hooks/usePipeIpc.js').usePipeIpc
|
||||
: () => undefined;
|
||||
|
||||
// 组件内
|
||||
usePipeIpc({ store, handleIncomingPrompt });
|
||||
```
|
||||
|
||||
内部使用 **lazy getter** 函数加载依赖(避免循环依赖导致 Bun 运行时崩溃):
|
||||
|
||||
```typescript
|
||||
const pt = () => require('../utils/pipeTransport.js')
|
||||
const pr = () => require('../utils/pipeRegistry.js')
|
||||
const mm = () => require('./useMasterMonitor.js')
|
||||
// ...
|
||||
```
|
||||
|
||||
`import type` 用于静态类型(不会触发模块加载)。
|
||||
|
||||
### 四个阶段函数
|
||||
|
||||
| 函数 | 职责 |
|
||||
|------|------|
|
||||
| `initPipeServer` | 角色判定 + server 创建 + beacon 启动 |
|
||||
| `registerMessageHandlers` | ping、attach、prompt、permission、detach 五个 handler |
|
||||
| `runMainHeartbeat` | cleanup + 发现 + auto-attach + 清理死连接 |
|
||||
| `runSubHeartbeat` | 检测 main 是否存活,死亡则接管或独立 |
|
||||
|
||||
### usePipeRelay(消息回传)
|
||||
|
||||
`src/hooks/usePipeRelay.ts`(38 行)
|
||||
|
||||
提供 `relayPipeMessage()` 和 `pipeReturnHadErrorRef`。relay 函数通过 `getPipeRelay()` module singleton 读取(替代 `globalThis.__pipeSendToMaster`)。
|
||||
|
||||
### usePipePermissionForward(权限转发)
|
||||
|
||||
`src/hooks/usePipePermissionForward.ts`(159 行)
|
||||
|
||||
订阅 `subscribePipeEntries()`,处理:
|
||||
- `permission_request` → 解析 payload → 查找 tool → 加入确认队列
|
||||
- `permission_cancel` → 从队列移除
|
||||
- `stream/error/done` → 转为系统消息显示(含 role + IP 标签)
|
||||
|
||||
### usePipeRouter(输入路由)
|
||||
|
||||
`src/hooks/usePipeRouter.ts`(130 行)
|
||||
|
||||
提供 `routeToSelectedPipes(input): boolean`。读取 `selectedPipes` + `routeMode`,逐个发送到已连接目标。通知显示 `[role] hostname/ip`(LAN peer)或 `[role]`(本机)。
|
||||
|
||||
---
|
||||
|
||||
## Registry 并行探测
|
||||
|
||||
`src/utils/pipeRegistry.ts`
|
||||
|
||||
### getAliveSubs()
|
||||
|
||||
```typescript
|
||||
export async function getAliveSubs(): Promise<PipeRegistrySub[]> {
|
||||
const registry = await readRegistry()
|
||||
const results = await Promise.all(
|
||||
registry.subs.map(sub =>
|
||||
isPipeAlive(sub.pipeName, 1000).then(alive => alive ? sub : null)
|
||||
)
|
||||
)
|
||||
return results.filter(Boolean)
|
||||
}
|
||||
```
|
||||
|
||||
### cleanupStaleEntries()
|
||||
|
||||
两阶段:
|
||||
1. **无锁并行探测**:`Promise.all` 探测 main + 所有 subs
|
||||
2. **短暂持锁写入**:`acquireLock()` → 重新读取 → 应用变更 → 写入 → `releaseLock()`
|
||||
|
||||
持锁时间从 N 秒降至 ~10ms。
|
||||
|
||||
### getMachineId()
|
||||
|
||||
Windows/macOS 使用 `execFile`(异步),不阻塞主线程。结果缓存,仅首次调用执行。
|
||||
|
||||
---
|
||||
|
||||
## NDJSON 协议
|
||||
|
||||
### 消息类型
|
||||
|
||||
| 类型 | 方向 | 数据 |
|
||||
|------|------|------|
|
||||
| `ping` / `pong` | 双向 | 无 |
|
||||
| `attach_request` | M→S | `meta: { machineId }` |
|
||||
| `attach_accept` / `attach_reject` | S→M | `data: reason` |
|
||||
| `detach` | M→S | 无 |
|
||||
| `prompt` | M→S | `data: prompt_text` |
|
||||
| `prompt_ack` | S→M | `data: 'accepted'` |
|
||||
| `stream` | S→M | `data: partial_text` |
|
||||
| `done` | S→M | 无 |
|
||||
| `error` | 双向 | `data: error_message` |
|
||||
| `permission_request` | S→M | `data: JSON(PipePermissionRequestPayload)` |
|
||||
| `permission_response` | M→S | `data: JSON(PipePermissionResponsePayload)` |
|
||||
| `permission_cancel` | M→S | `data: JSON({ requestId, reason })` |
|
||||
|
||||
### 帧格式
|
||||
|
||||
每行一个 JSON 对象,`\n` 分隔:
|
||||
```
|
||||
{"type":"ping","from":"cli-abc","ts":"2026-04-11T00:00:00.000Z"}\n
|
||||
{"type":"prompt","data":"检查 git status","from":"cli-abc"}\n
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 跨机器 Attach 流程
|
||||
|
||||
```
|
||||
CLI-B (192.168.50.27) 心跳循环
|
||||
→ beacon.getPeers() 发现 CLI-A (192.168.50.22)
|
||||
→ connectToPipe(pName, myName, 3000, { host: '192.168.50.22', port: 58853 })
|
||||
→ PipeClient.connectTcp() → net.createConnection({ host, port })
|
||||
→ client.send({ type: 'attach_request', meta: { machineId } })
|
||||
→ CLI-A 收到:
|
||||
isLanPeer = (msg.meta.machineId !== myMachineId) → true
|
||||
→ 不检查 role,直接 reply({ type: 'attach_accept' })
|
||||
→ setPipeRelay(socket.write)
|
||||
→ CLI-B 收到 attach_accept
|
||||
→ addSlaveClient(pName, client)
|
||||
→ store.setState: role='master', slaves[pName] = { status: 'idle' }
|
||||
```
|
||||
|
||||
关键:跨机器 attach 不要求对方是 sub 角色。通过 `machineId` 区分 LAN peer。
|
||||
|
||||
---
|
||||
|
||||
## SendMessageTool TCP 支持
|
||||
|
||||
`packages/builtin-tools/src/tools/SendMessageTool/SendMessageTool.ts`
|
||||
|
||||
- `to` 字段支持 `tcp:host:port` 格式
|
||||
- `checkPermissions`:`tcp:` scheme 返回 `behavior: 'ask'`,`classifierApprovable: false`
|
||||
- `call()`:创建临时 `PipeClient` → connect → send → disconnect
|
||||
|
||||
---
|
||||
|
||||
## 测试
|
||||
|
||||
| 文件 | 测试数 | 覆盖 |
|
||||
|------|--------|------|
|
||||
| `lanBeacon.test.ts` | 7 | socket 初始化、announce、peer 发现/过滤/清理 |
|
||||
| `peerAddress.test.ts` | 8 | scheme 解析、parseTcpTarget、端口范围验证 |
|
||||
| `pipePermissionRelay.test.ts` | 2 | setPipeRelay singleton、权限请求/响应 |
|
||||
| `pipeTransport.test.ts` | 2 | UDS 基础行为 |
|
||||
| `useMasterMonitor.test.ts` | 5 | slave 注册/移除、事件发射 |
|
||||
|
||||
全量:2190 pass / 0 fail
|
||||
|
||||
---
|
||||
|
||||
## 已知限制
|
||||
|
||||
1. **TCP 无认证** — 同 LAN 内知道端口号即可连接
|
||||
2. **Beacon 明文广播** — IP/hostname/machineId 未 hash
|
||||
3. **单网卡选择** — `getLocalIp()` 返回首个非内部 IPv4,可能选到 VPN
|
||||
4. **端口随机** — 每次启动不同端口,依赖 beacon 发现
|
||||
5. **SendMessageTool 每次创建新连接** — 未复用已有 slave client
|
||||
|
||||
## 后续改进方向
|
||||
|
||||
1. HMAC-SHA256 TCP 握手认证
|
||||
2. machineId hash 后再广播
|
||||
3. 多网卡选择(优先 RFC 1918 地址)
|
||||
4. 固定端口范围配置
|
||||
5. TLS 加密传输
|
||||
6. SendMessageTool 复用已连接的 slave client
|
||||
@@ -1,193 +0,0 @@
|
||||
# LAN Pipes — 局域网多机器群控指南
|
||||
|
||||
## 什么是 LAN Pipes
|
||||
|
||||
LAN Pipes 让多台机器上的 Claude Code 实例通过局域网自动发现并协作。你可以在一台机器(main)上操控其他机器(sub)上的 Claude Code,发送 prompt、查看执行结果、审批权限请求——全程零配置。
|
||||
|
||||
基于本机 Pipe IPC(`UDS_INBOX`)扩展,新增 TCP 传输层 + UDP Multicast 发现。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 两台或以上机器在同一局域网
|
||||
- 每台机器安装了 CCB 并能 `bun run dev`
|
||||
- Feature flag `LAN_PIPES`(dev/build 默认开启)
|
||||
- 防火墙允许 UDP 7101 + TCP 动态端口(见下方配置)
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 第一步:配置防火墙
|
||||
|
||||
**每台机器都需要执行。**
|
||||
|
||||
**Windows**(管理员 PowerShell):
|
||||
```powershell
|
||||
New-NetFirewallRule -DisplayName "CCB LAN Beacon (UDP)" -Direction Inbound -Protocol UDP -LocalPort 7101 -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "CCB LAN Pipes (TCP)" -Direction Inbound -Protocol TCP -LocalPort 1024-65535 -Program (Get-Command bun).Source -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "CCB LAN Beacon Out (UDP)" -Direction Outbound -Protocol UDP -RemotePort 7101 -Action Allow -Profile Private
|
||||
```
|
||||
|
||||
验证网络为"专用"(非公共):`Get-NetConnectionProfile`
|
||||
|
||||
**macOS**:
|
||||
首次运行时系统弹出"允许接受传入连接"对话框,点击"允许"。
|
||||
|
||||
如果使用 pf 防火墙:
|
||||
```bash
|
||||
echo "pass in proto udp from any to any port 7101" | sudo pfctl -ef -
|
||||
```
|
||||
|
||||
**Linux**(firewalld):
|
||||
```bash
|
||||
sudo firewall-cmd --zone=trusted --add-port=7101/udp --permanent
|
||||
sudo firewall-cmd --zone=trusted --add-port=1024-65535/tcp --permanent
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
**Linux**(iptables):
|
||||
```bash
|
||||
sudo iptables -A INPUT -p udp --dport 7101 -j ACCEPT
|
||||
sudo iptables -A INPUT -p tcp --dport 1024:65535 -m owner --uid-owner $(id -u) -j ACCEPT
|
||||
```
|
||||
|
||||
### 第二步:启动
|
||||
|
||||
```bash
|
||||
# 机器 A(例如 192.168.50.22)
|
||||
bun run dev
|
||||
|
||||
# 机器 B(例如 192.168.50.27)
|
||||
bun run dev
|
||||
```
|
||||
|
||||
启动后等待 3-5 秒(beacon 广播间隔),两边自动发现并连接。
|
||||
|
||||
### 第三步:查看和操作
|
||||
|
||||
在任一台机器上:
|
||||
```
|
||||
/pipes
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
pipe: cli-a91bad56 (main) 192.168.50.22 2/3 selected
|
||||
|
||||
Main machine: 205d6c3a... (this machine)
|
||||
[main] cli-a91bad56 XC/192.168.50.22 [alive] (you)
|
||||
☑ [sub-1] cli-da029538 XC/192.168.50.22 [alive] [connected]
|
||||
|
||||
LAN Peers:
|
||||
☐ [main] cli-04d67950 vmwin11/192.168.50.27 tcp:192.168.50.27:58853 [LAN]
|
||||
```
|
||||
|
||||
### 第四步:选中目标并发送任务
|
||||
|
||||
1. 按 `Shift+↓` 展开选择面板
|
||||
2. `↑↓` 移动到 LAN peer
|
||||
3. `Space` 选中
|
||||
4. `Enter` 确认
|
||||
5. 输入 prompt,自动路由到远端执行
|
||||
|
||||
远端执行结果会流式回传到你的消息列表:
|
||||
```
|
||||
[main vmwin11/192.168.50.27 / cli-04d67950] 正在检查 git status...
|
||||
[main vmwin11/192.168.50.27 / cli-04d67950] Completed
|
||||
```
|
||||
|
||||
## 完整命令参考
|
||||
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
| `/pipes` | 显示所有实例(本机 + LAN),Shift+↓ 展开选择面板 |
|
||||
| `/pipes select <name>` | 选中某实例 |
|
||||
| `/pipes all` | 全选 |
|
||||
| `/pipes none` | 取消全选 |
|
||||
| `/attach <name>` | 手动 attach(自动识别 LAN peer 并通过 TCP 连接) |
|
||||
| `/detach <name>` | 断开连接 |
|
||||
| `/send <name> <msg>` | 向指定 pipe 发送消息 |
|
||||
| `/send tcp:host:port <msg>` | 直接通过 TCP 地址发送 |
|
||||
| `/claim-main` | 强制声明为 main |
|
||||
| `/pipe-status` | 显示详细状态 |
|
||||
| `/peers` | 列出所有已发现的 peer |
|
||||
|
||||
## 快捷键
|
||||
|
||||
| 快捷键 | 场景 | 作用 |
|
||||
|--------|------|------|
|
||||
| `Shift+↓` | 状态栏可见时 | 展开/收起选择面板 |
|
||||
| `↑ / ↓` | 面板展开时 | 移动光标 |
|
||||
| `Space` | 面板展开时 | 选中/取消 |
|
||||
| `Enter` | 面板展开时 | 确认关闭 |
|
||||
| `Esc` | 面板展开时 | 取消关闭 |
|
||||
| `← / →` | 有选中 pipe 时 | 切换路由模式 |
|
||||
| `M` | 面板展开时 | 同 ←/→ 切换路由模式 |
|
||||
|
||||
## 路由模式
|
||||
|
||||
| 模式 | 显示 | 行为 |
|
||||
|------|------|------|
|
||||
| `selected pipes only` | 绿色 | prompt 仅发送到选中的 pipe,本地不执行 |
|
||||
| `local main` | 灰色 | prompt 仅在本地执行,不转发 |
|
||||
|
||||
切换路由模式不会清空选择。
|
||||
|
||||
## 权限转发
|
||||
|
||||
当远端 slave 执行需要权限的工具(如 BashTool)时:
|
||||
1. slave 发送 `permission_request` 到 main
|
||||
2. main 弹出权限确认对话框,显示 `[role hostname/ip / pipeName]`
|
||||
3. 用户确认/拒绝
|
||||
4. 结果发回 slave,继续或中断
|
||||
|
||||
## 工作原理
|
||||
|
||||
### 发现机制
|
||||
|
||||
- 每台机器启动时创建 UDP multicast beacon
|
||||
- 组地址 `224.0.71.67`,端口 `7101`,TTL=1(不跨路由器)
|
||||
- 每 3 秒广播一次自身信息(pipeName、IP、TCP 端口、角色)
|
||||
- 15 秒未收到广播则标记 peer 丢失
|
||||
|
||||
### 通信机制
|
||||
|
||||
- 本机实例:UDS(Unix Domain Socket / Named Pipe)
|
||||
- 跨机器:TCP(动态端口,通过 beacon 发现)
|
||||
- 协议:NDJSON(每行一个 JSON 对象)
|
||||
- 消息类型:ping/pong、attach/detach、prompt/stream/done/error、permission
|
||||
|
||||
### 角色模型
|
||||
|
||||
| 角色 | 说明 |
|
||||
|------|------|
|
||||
| `main` | 首个启动的实例 |
|
||||
| `sub` | 同机后续启动的实例 |
|
||||
| `master` | attach 了至少一个 slave 的实例 |
|
||||
| `slave` | 被 master attach 的实例 |
|
||||
|
||||
跨机器 attach 时,两边都可以是 main——不要求对方必须是 sub。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 看不到 LAN peer
|
||||
|
||||
1. 检查防火墙是否放行 UDP 7101
|
||||
2. `Get-NetConnectionProfile`(Windows)确认网络为"专用"
|
||||
3. 确认两台机器在同一子网(`ping` 能通)
|
||||
4. 路由器未开启 AP 隔离
|
||||
|
||||
### 连接超时
|
||||
|
||||
1. 检查 TCP 入站防火墙规则
|
||||
2. 确认没有 VPN 劫持流量
|
||||
3. 尝试 `/send tcp:ip:port hello` 直接测试
|
||||
|
||||
### beacon 绑到了错误网卡
|
||||
|
||||
Windows 上 WSL/Docker 虚拟网卡可能劫持 multicast。beacon 会自动选择非内部 IPv4 接口。如果选错,检查 `getLocalIp()` 返回值。
|
||||
|
||||
## 安全说明
|
||||
|
||||
- TCP 连接当前**无认证**——同 LAN 内知道端口号即可连接
|
||||
- Multicast TTL=1,不跨路由器
|
||||
- AI 通过 `SendMessageTool` 发送 `tcp:` 消息时需**用户显式确认**
|
||||
- 建议仅在信任的局域网中使用
|
||||
@@ -1,118 +0,0 @@
|
||||
# MCP_SKILLS — MCP 技能发现
|
||||
|
||||
> Feature Flag: `FEATURE_MCP_SKILLS=1`
|
||||
> 实现状态:功能性实现(config 门控筛选器完整,核心 fetcher 为 stub)
|
||||
> 引用数:9
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
MCP_SKILLS 将 MCP 服务器暴露的资源(`skill://` URI 方案)发现并转换为可调用的技能命令。MCP 服务器可以同时提供 tools、prompts 和 resources;启用此 feature 后,带有 `skill://` URI 的资源被识别为技能。
|
||||
|
||||
### 核心特性
|
||||
|
||||
- **自动发现**:MCP 服务器连接时自动获取 `skill://` 资源
|
||||
- **命令转换**:将 MCP 资源转换为 `prompt` 类型的 Command 对象
|
||||
- **实时刷新**:prompts/resources 列表变化时重新获取技能
|
||||
- **缓存一致性**:连接关闭时清除技能缓存
|
||||
|
||||
## 二、实现架构
|
||||
|
||||
### 2.1 数据流
|
||||
|
||||
```
|
||||
MCP Server 连接
|
||||
│
|
||||
▼
|
||||
client.ts: connectToServer / setupMcpClientConnections
|
||||
├── fetchToolsForClient (MCP tools)
|
||||
├── fetchCommandsForClient (MCP prompts → Command 对象)
|
||||
├── fetchMcpSkillsForClient (MCP skill:// 资源 → Command 对象) [MCP_SKILLS]
|
||||
└── fetchResourcesForClient (MCP resources)
|
||||
│
|
||||
▼
|
||||
commands = [...mcpPrompts, ...mcpSkills]
|
||||
│
|
||||
▼
|
||||
AppState.mcp.commands 更新
|
||||
│
|
||||
▼
|
||||
getMcpSkillCommands() 过滤 → SkillTool 调用
|
||||
```
|
||||
|
||||
### 2.2 技能筛选
|
||||
|
||||
文件:`src/commands.ts:604-616`
|
||||
|
||||
`getMcpSkillCommands(mcpCommands)` 过滤条件:
|
||||
|
||||
```ts
|
||||
cmd.type === 'prompt' // 必须是 prompt 类型
|
||||
cmd.loadedFrom === 'mcp' // 必须来自 MCP 服务器
|
||||
!cmd.disableModelInvocation // 必须可由模型调用
|
||||
feature('MCP_SKILLS') // feature flag 必须开启
|
||||
```
|
||||
|
||||
### 2.3 条件加载
|
||||
|
||||
文件:`src/services/mcp/client.ts:129-133`
|
||||
|
||||
`fetchMcpSkillsForClient` 通过 `require()` 条件加载,feature flag 关闭时不加载任何模块:
|
||||
|
||||
```ts
|
||||
const fetchMcpSkillsForClient = feature('MCP_SKILLS')
|
||||
? require('../../skills/mcpSkills.js').fetchMcpSkillsForClient
|
||||
: null
|
||||
```
|
||||
|
||||
### 2.4 缓存管理
|
||||
|
||||
技能获取函数维护 `.cache`(Map),在以下时机清除:
|
||||
|
||||
| 事件 | 行为 |
|
||||
|------|------|
|
||||
| 连接关闭 | 清除该 client 的技能缓存 |
|
||||
| `disconnectMcpServer()` | 清除技能缓存 |
|
||||
| `prompts/list_changed` 通知 | 刷新 prompts + 并行获取技能 |
|
||||
| `resources/list_changed` 通知 | 刷新 resources + prompts + 技能 |
|
||||
|
||||
### 2.5 集成点
|
||||
|
||||
| 文件 | 行 | 说明 |
|
||||
|------|------|------|
|
||||
| `src/commands.ts` | 604-616, 620-633 | 命令过滤和 SkillTool 命令收集 |
|
||||
| `src/services/mcp/client.ts` | 129-133, 1394, 1672, 2176 | 技能获取、缓存清除、连接时获取 |
|
||||
| `src/services/mcp/useManageMCPConnections.ts` | 22-26, 682-740 | 实时刷新(prompts/resources 变化) |
|
||||
|
||||
## 三、关键设计决策
|
||||
|
||||
1. **Feature gate 隔离**:`feature('MCP_SKILLS')` 守护条件 `require()` 和所有调用点。关闭时无模块加载、无获取操作
|
||||
2. **资源到技能映射**:技能从 MCP 服务器的 `skill://` URI 资源中发现。`fetchMcpSkillsForClient` 负责转换(当前为 stub)
|
||||
3. **循环依赖避免**:`mcpSkillBuilders.ts` 作为依赖图叶节点,避免 `client.ts ↔ mcpSkills.ts ↔ loadSkillsDir.ts` 循环
|
||||
4. **服务器能力检查**:技能获取还需要 MCP 服务器支持 resources (`!!client.capabilities?.resources`)
|
||||
|
||||
## 四、使用方式
|
||||
|
||||
```bash
|
||||
# 启用 feature
|
||||
FEATURE_MCP_SKILLS=1 bun run dev
|
||||
|
||||
# 前提条件:
|
||||
# 1. 配置了支持 skill:// 资源的 MCP 服务器
|
||||
# 2. MCP 服务器声明了 resources 能力
|
||||
```
|
||||
|
||||
## 五、需要补全的内容
|
||||
|
||||
| 文件 | 状态 | 需要实现 |
|
||||
|------|------|---------|
|
||||
| `src/skills/mcpSkills.ts` | Stub | `fetchMcpSkillsForClient()` — 从 MCP 资源列表中筛选 `skill://` URI 并转换为 Command 对象 |
|
||||
| `src/skills/mcpSkillBuilders.ts` | Stub | 技能构建器注册(避免循环依赖) |
|
||||
|
||||
## 六、文件索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/commands.ts:547-608` | 技能命令过滤 |
|
||||
| `src/services/mcp/client.ts:117-2358` | 技能获取 + 缓存管理 |
|
||||
| `src/services/mcp/useManageMCPConnections.ts` | 实时刷新 |
|
||||
| `src/skills/mcpSkills.ts` | 核心转换逻辑(stub) |
|
||||
@@ -1,3 +1,9 @@
|
||||
---
|
||||
title: "后台记忆整理(Auto Dream)"
|
||||
description: "会话间自动审查、组织和修剪持久化记忆,确保未来会话快速获得准确上下文。"
|
||||
keywords: ["Auto Dream", "记忆整合", "后台任务", "MEMORY.md", "/dream 命令"]
|
||||
---
|
||||
|
||||
# Auto Dream — 自动记忆整理
|
||||
|
||||
## 概述
|
||||
@@ -1,3 +1,9 @@
|
||||
---
|
||||
title: "Remote Control 私有化部署"
|
||||
description: "Docker 自托管 RCS,含 Web UI 控制面板、ACP agent 接入、JWT 认证。"
|
||||
keywords: ["Remote Control Server", "Docker 部署", "ACP agent", "JWT 认证", "Web UI 控制面板"]
|
||||
---
|
||||
|
||||
# Remote Control Server 私有化部署指南
|
||||
|
||||
本指南说明如何将 Remote Control Server (RCS) 部署到私有环境,并通过 Claude Code CLI 连接使用。
|
||||
@@ -232,7 +238,7 @@ API key。浏览器 `EventSource` 不能发送 `Authorization` header,外部
|
||||
|
||||
### acp-link 连接
|
||||
|
||||
详见 [acp-link 文档](./acp-link.md)。
|
||||
详见 [acp-link 文档](../agents/acp-link.md)。
|
||||
|
||||
```bash
|
||||
# 在 RCS 环境中启动 acp-link
|
||||
@@ -1,342 +0,0 @@
|
||||
# Pipes + LAN Pipes 完整功能指南
|
||||
|
||||
## 概述
|
||||
|
||||
Pipes 系统提供 Claude Code CLI 实例之间的通讯能力,分两层:
|
||||
|
||||
1. **Pipes(本机)**:同一台机器上的多个 CLI 实例通过 UDS(Unix Domain Socket / Windows Named Pipe)协作
|
||||
2. **LAN Pipes(局域网)**:不同机器上的 CLI 实例通过 TCP + UDP Multicast 协作
|
||||
|
||||
两层使用同一套协议(NDJSON)和同一套命令(`/pipes`、`/attach`、`/send` 等),对用户透明。
|
||||
|
||||
## Feature Flags
|
||||
|
||||
| Flag | 控制范围 | 默认 |
|
||||
|------|----------|------|
|
||||
| `UDS_INBOX` | 本机 Pipe IPC 全部功能 | dev/build 启用 |
|
||||
| `LAN_PIPES` | 局域网 TCP + beacon 扩展 | dev/build 启用 |
|
||||
|
||||
手动启用:`FEATURE_UDS_INBOX=1 FEATURE_LAN_PIPES=1 bun run dev`
|
||||
|
||||
## 快速上手
|
||||
|
||||
### 本机多实例
|
||||
|
||||
```bash
|
||||
# 终端 1
|
||||
bun run dev
|
||||
# 启动后自动注册为 main
|
||||
|
||||
# 终端 2
|
||||
bun run dev
|
||||
# 自动注册为 sub-1,被 main 自动 attach
|
||||
```
|
||||
|
||||
在终端 1 中输入 `/pipes`,可以看到两个实例。选中 sub-1 后,输入的消息会自动转发到 sub-1 执行。
|
||||
|
||||
### 局域网多机器
|
||||
|
||||
```bash
|
||||
# 机器 A (192.168.50.22)
|
||||
bun run dev
|
||||
|
||||
# 机器 B (192.168.50.27)
|
||||
bun run dev
|
||||
```
|
||||
|
||||
两边启动后等 3-5 秒(beacon 广播间隔),LAN peers 会自动发现并 attach。输入 `/pipes` 可看到标记 `[LAN]` 的远端实例。
|
||||
|
||||
### 防火墙配置(两台机器都需要)
|
||||
|
||||
**Windows**(管理员 PowerShell):
|
||||
```powershell
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Beacon (UDP)" -Direction Inbound -Protocol UDP -LocalPort 7101 -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Pipes (TCP)" -Direction Inbound -Protocol TCP -LocalPort 1024-65535 -Program (Get-Command bun).Source -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Beacon Out (UDP)" -Direction Outbound -Protocol UDP -RemotePort 7101 -Action Allow -Profile Private
|
||||
# 确认网络为"专用":Get-NetConnectionProfile
|
||||
```
|
||||
|
||||
**macOS**(首次运行时系统弹出对话框,点击"允许"即可):
|
||||
```bash
|
||||
# 如果需要手动放行 pf 防火墙:
|
||||
echo "pass in proto udp from any to any port 7101" | sudo pfctl -ef -
|
||||
```
|
||||
|
||||
**Linux**(firewalld / iptables):
|
||||
```bash
|
||||
# firewalld
|
||||
sudo firewall-cmd --zone=trusted --add-port=7101/udp --permanent
|
||||
sudo firewall-cmd --zone=trusted --add-port=1024-65535/tcp --permanent
|
||||
sudo firewall-cmd --reload
|
||||
|
||||
# 或 iptables
|
||||
sudo iptables -A INPUT -p udp --dport 7101 -j ACCEPT
|
||||
sudo iptables -A INPUT -p tcp --dport 1024:65535 -m owner --uid-owner $(id -u) -j ACCEPT
|
||||
```
|
||||
|
||||
确认:网络为局域网(非公共 WiFi),路由器未开启 AP 隔离。
|
||||
|
||||
## 交互面板与快捷键
|
||||
|
||||
### 状态栏
|
||||
|
||||
执行 `/pipes` 后,输入框底部出现 pipe 状态栏(单行):
|
||||
|
||||
```
|
||||
pipe: cli-a91bad56 (main) 192.168.50.22 2/3 selected selected pipes only · ←/→ or m switch · Shift+↓ edit
|
||||
```
|
||||
|
||||
状态栏始终可见(直到会话结束),显示:当前 pipe 名、角色、IP、已选数/总数、路由模式。
|
||||
|
||||
### 展开选择面板
|
||||
|
||||
按 **Shift+↓**(Shift + 下箭头)展开选择面板:
|
||||
|
||||
```
|
||||
pipe: cli-a91bad56 (main) 192.168.50.22 ↑↓ move Space select ←/→ or m route Enter/Esc close Shift+↓ toggle
|
||||
当前普通 prompt 走 已选 sub;切换不会清空选择
|
||||
☑ cli-da029538 (sub-1 XC/192.168.50.22)
|
||||
☐ cli-04d67950 (main vmwin11/192.168.50.27)
|
||||
☑ cli-893747d3 [offline] (sub-2 vmwin11/192.168.50.27)
|
||||
```
|
||||
|
||||
### 面板内快捷键
|
||||
|
||||
| 快捷键 | 场景 | 作用 |
|
||||
|--------|------|------|
|
||||
| **Shift+↓** | 状态栏可见时 | 展开/收起选择面板 |
|
||||
| **↑ / ↓** | 面板展开时 | 上下移动光标 |
|
||||
| **Space** | 面板展开时 | 切换当前光标所在 pipe 的选中状态(☑ ↔ ☐) |
|
||||
| **Enter** | 面板展开时 | 确认并关闭面板 |
|
||||
| **Esc** | 面板展开时 | 取消并关闭面板 |
|
||||
| **← / → 或 M** | 状态栏可见且有选中 pipe 时 | 切换路由模式(`selected pipes only` ↔ `local main`) |
|
||||
|
||||
### M 键 — 路由模式切换
|
||||
|
||||
M 键(或 ← / →)用于在两种路由模式之间切换,**无需展开面板**:
|
||||
|
||||
| 模式 | 状态栏显示 | 行为 |
|
||||
|------|-----------|------|
|
||||
| `selected pipes only` | 绿色高亮 | 输入的 prompt **仅**发送到选中的 pipe,本地不执行 |
|
||||
| `local main` | 灰色 | 输入的 prompt 在**本地 main** 执行,不转发到任何 pipe |
|
||||
|
||||
切换路由模式**不会清空选择**。你可以在 `local main` 模式下保持选择,随时按 M 切回 `selected pipes only` 继续向远端发送。
|
||||
|
||||
### 完整操作流程示例
|
||||
|
||||
```
|
||||
1. 输入 /pipes → 状态栏出现,显示发现的实例
|
||||
2. 按 Shift+↓ → 展开选择面板
|
||||
3. 按 ↓ 移动到目标 pipe → 光标移到 cli-04d67950
|
||||
4. 按 Space → 选中 ☑ cli-04d67950
|
||||
5. 按 Enter → 确认,面板收起
|
||||
6. 输入 "帮我检查 git status" → prompt 自动发送到 cli-04d67950 执行
|
||||
7. 按 M → 切换到 local main 模式
|
||||
8. 输入 "本地做点什么" → 仅在本地执行
|
||||
9. 按 M → 切回 selected pipes only
|
||||
10. 输入 "继续远端任务" → 又发送到 cli-04d67950
|
||||
```
|
||||
|
||||
## 命令参考
|
||||
|
||||
### /pipes
|
||||
|
||||
显示所有发现的实例,管理选择状态。再次执行 `/pipes` 切换面板展开/收起。
|
||||
|
||||
```
|
||||
/pipes — 显示所有实例 + 切换选择面板
|
||||
/pipes select <name> — 选中某实例(消息会广播到它)
|
||||
/pipes deselect <name> — 取消选中
|
||||
/pipes all — 全选
|
||||
/pipes none — 全部取消
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
Your pipe: cli-a91bad56
|
||||
Role: main
|
||||
Machine ID: 205d6c3a...
|
||||
IP: 192.168.50.22
|
||||
Host: XC
|
||||
|
||||
Main machine: 205d6c3a... (this machine)
|
||||
[main] cli-a91bad56 XC/192.168.50.22 [alive] (you)
|
||||
☑ [sub-1] cli-da029538 XC/192.168.50.22 [alive] [connected]
|
||||
|
||||
LAN Peers:
|
||||
☐ [main] cli-04d67950 vmwin11/192.168.50.27 tcp:192.168.50.27:58853 [LAN]
|
||||
|
||||
Selected: cli-da029538
|
||||
```
|
||||
|
||||
### /attach <name>
|
||||
|
||||
手动 attach 到一个实例,使其成为你的 slave。
|
||||
|
||||
```
|
||||
/attach cli-04d67950 — 连接到指定 pipe(自动解析 LAN TCP 端点)
|
||||
```
|
||||
|
||||
attach 后,对方变为 slave,你变为 master。可以向它发送 prompt。通常不需要手动 attach——heartbeat 会自动发现并连接。
|
||||
|
||||
### /detach <name>
|
||||
|
||||
断开与某个 slave 的连接。
|
||||
|
||||
```
|
||||
/detach cli-04d67950
|
||||
```
|
||||
|
||||
### /send <name> <message>
|
||||
|
||||
向指定 pipe 发送消息(不依赖选择状态,直接指定目标)。
|
||||
|
||||
```
|
||||
/send cli-04d67950 请帮我检查一下日志
|
||||
/send tcp:192.168.50.27:58853 hello — 直接通过 TCP 地址发送
|
||||
```
|
||||
|
||||
### /claim-main
|
||||
|
||||
强制声明当前机器为 main(用于 main 意外退出后的恢复)。
|
||||
|
||||
## 消息路由
|
||||
|
||||
### 选中 pipe 后的自动路由
|
||||
|
||||
1. 通过 `/pipes select` 或 Shift+Down 面板选中一个或多个 pipe
|
||||
2. 在输入框中正常输入消息
|
||||
3. 消息自动发送到所有选中的已连接 pipe
|
||||
4. 每个 pipe 独立执行,结果流式回传到 main 的消息列表
|
||||
|
||||
### 路由模式
|
||||
|
||||
| 模式 | 行为 |
|
||||
|------|------|
|
||||
| `selected`(默认) | 消息发送到选中的 pipe |
|
||||
| `local` | 消息仅在本地执行,不转发 |
|
||||
|
||||
## 架构
|
||||
|
||||
### 通信协议
|
||||
|
||||
所有通讯使用 NDJSON(Newline-Delimited JSON),每行一个消息:
|
||||
|
||||
```json
|
||||
{"type":"ping","from":"cli-abc","ts":"2026-04-11T00:00:00.000Z"}
|
||||
{"type":"prompt","data":"帮我查看 git status","from":"cli-abc","ts":"..."}
|
||||
{"type":"stream","data":"正在执行...","from":"cli-def","ts":"..."}
|
||||
{"type":"done","data":"","from":"cli-def","ts":"..."}
|
||||
```
|
||||
|
||||
### 消息类型
|
||||
|
||||
| 类型 | 方向 | 说明 |
|
||||
|------|------|------|
|
||||
| `ping`/`pong` | 双向 | 健康检查 |
|
||||
| `attach_request`/`accept`/`reject` | M→S/S→M | 连接控制 |
|
||||
| `detach` | M→S | 断开连接 |
|
||||
| `prompt` | M→S | 主向从发送 prompt |
|
||||
| `prompt_ack` | S→M | 从确认接收 |
|
||||
| `stream` | S→M | 从流式回传 AI 输出 |
|
||||
| `tool_start`/`tool_result` | S→M | 工具执行通知 |
|
||||
| `done` | S→M | 本轮完成 |
|
||||
| `error` | 双向 | 错误通知 |
|
||||
| `permission_request`/`response`/`cancel` | 双向 | 权限审批转发 |
|
||||
|
||||
### 传输层
|
||||
|
||||
```
|
||||
本机 LAN
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ PipeServer │ │ PipeServer │
|
||||
│ UDS sock │ │ UDS sock │
|
||||
│ TCP :rand │◄───TCP───►│ TCP :rand │
|
||||
├──────────────┤ ├──────────────┤
|
||||
│ LanBeacon │◄──UDP────►│ LanBeacon │
|
||||
│ 224.0.71.67 │ mcast │ 224.0.71.67 │
|
||||
└──────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
- **UDS**:本机实例间通讯,通过文件系统路径寻址(`~/.claude/pipes/cli-xxx.sock`)
|
||||
- **TCP**:LAN 实例间通讯,动态端口,通过 beacon 发现
|
||||
- **UDP Multicast**:peer 发现,3 秒广播一次 announce 包
|
||||
|
||||
### 角色模型
|
||||
|
||||
| 角色 | 说明 |
|
||||
|------|------|
|
||||
| `main` | 首个启动的实例,管理 registry |
|
||||
| `sub` | 后续启动的同机实例(或被 attach 的 LAN 实例) |
|
||||
| `master` | attach 了至少一个 slave 的实例 |
|
||||
| `slave` | 被 master attach 控制的实例 |
|
||||
|
||||
角色转换:
|
||||
- 首个启动 → `main`
|
||||
- 同机后续启动 → `sub`(自动被 main attach → `slave`)
|
||||
- LAN 发现 → 两边都是 `main`,heartbeat 自动互相 attach
|
||||
- 被 attach → 变为 `slave`(可通过 `/detach` 恢复)
|
||||
|
||||
### 发现机制
|
||||
|
||||
**本机**:通过 `~/.claude/pipes/registry.json` 文件(带文件锁),`machineId` 绑定主机身份。
|
||||
|
||||
**LAN**:通过 UDP multicast beacon:
|
||||
1. 每 3 秒广播 `{ proto, pipeName, machineId, ip, tcpPort, role }`
|
||||
2. 收到其他实例的 announce → 记入 peers Map
|
||||
3. 15 秒未收到 → 标记 peer lost
|
||||
4. Heartbeat 合并 local registry + beacon peers → 统一 attach 目标列表
|
||||
|
||||
### Heartbeat 循环(5 秒间隔)
|
||||
|
||||
```
|
||||
main/master 角色:
|
||||
1. cleanupStaleEntries() — 清理 registry 中死掉的条目
|
||||
2. getAliveSubs() — 获取存活的本地 subs
|
||||
3. refreshDiscoveredPipes() — 刷新 discoveredPipes(包含 LAN peers)
|
||||
4. 合并 LAN peers 到 state
|
||||
5. 构建统一 attach 目标列表 — 本地 subs + LAN peers
|
||||
6. 遍历未连接的目标 → 自动 attach
|
||||
7. 清理断开的 slave 连接 — 同时检查 local registry 和 beacon
|
||||
|
||||
sub 角色:
|
||||
1. 检测 main 是否存活
|
||||
2. main 死亡 → 同机则接管 main 角色,跨机则独立
|
||||
```
|
||||
|
||||
## 关键文件
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/utils/pipeTransport.ts` | PipeServer(双模 UDS+TCP)、PipeClient、类型定义 |
|
||||
| `src/utils/lanBeacon.ts` | UDP multicast beacon、singleton 管理 |
|
||||
| `src/utils/pipeRegistry.ts` | Registry CRUD、角色判定、machineId、LAN merge |
|
||||
| `src/utils/peerAddress.ts` | 地址解析(uds:/bridge:/tcp: scheme) |
|
||||
| `src/screens/REPL.tsx` | Bootstrap、heartbeat、cleanup、prompt 路由 |
|
||||
| `src/hooks/useMasterMonitor.ts` | Slave client registry、消息订阅 |
|
||||
| `src/hooks/useSlaveNotifications.ts` | Slave 端通知处理 |
|
||||
| `src/commands/pipes/pipes.ts` | /pipes 命令 |
|
||||
| `src/commands/attach/attach.ts` | /attach 命令 |
|
||||
| `src/commands/send/send.ts` | /send 命令 |
|
||||
| `packages/builtin-tools/src/tools/SendMessageTool/SendMessageTool.ts` | AI 发消息工具(含 tcp: 支持) |
|
||||
|
||||
## 后续优化方向
|
||||
|
||||
### 安全(P0)
|
||||
|
||||
1. **TCP 认证**:首次连接时交换 HMAC-SHA256 token(基于 machineId + session secret),防止未授权设备连接
|
||||
2. **JSON schema 验证**:在所有 `JSON.parse` 入口点增加 Zod 校验,防止 prototype pollution
|
||||
3. **Beacon 信息脱敏**:hash machineId 后再广播,不暴露硬件序列号
|
||||
|
||||
### 可靠性(P1)
|
||||
|
||||
4. **多网卡选择**:`getLocalIp()` 应优先选择 RFC 1918 地址,排除 VPN/Docker 接口
|
||||
5. **TCP target 验证**:`parseTcpTarget()` 应限制目标为已知 beacon peers 或 RFC 1918 范围
|
||||
6. **PipeServer close()**:改为 `Promise.allSettled` 并行关闭 UDS + TCP,加 `_closing` guard
|
||||
|
||||
### 功能(P2)
|
||||
|
||||
7. **mDNS/DNS-SD**:作为 multicast 受限环境下的 beacon 替代方案
|
||||
8. **固定端口配置**:允许用户指定 TCP 端口范围,便于防火墙精确配置
|
||||
9. **TLS 加密**:TCP 传输加密,防中间人窃听
|
||||
10. **双向 prompt**:当前只有 master → slave 方向,可考虑 slave 主动向 master 发送结果/请求
|
||||
@@ -1,113 +0,0 @@
|
||||
# PROACTIVE — 主动模式
|
||||
|
||||
> Feature Flag: `FEATURE_PROACTIVE=1`(与 `FEATURE_KAIROS=1` 共享功能)
|
||||
> 实现状态:核心循环与 SleepTool 已落地,部分外围文档仍在补齐
|
||||
> 引用数:37
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
PROACTIVE 实现 Tick 驱动的自主代理。CLI 在用户不输入时也能持续工作:定时唤醒执行任务,配合 SleepTool 控制节奏。适用于长时间运行的后台任务(等待 CI、监控文件变化、定时检查等)。
|
||||
|
||||
### 与 KAIROS 的关系
|
||||
|
||||
所有代码检查都是 `feature('PROACTIVE') || feature('KAIROS')`,即:
|
||||
- 单独开 `FEATURE_PROACTIVE=1` → 获得 proactive 能力
|
||||
- 单独开 `FEATURE_KAIROS=1` → 自动获得 proactive 能力
|
||||
- 两者都开 → 相同效果(不重复)
|
||||
|
||||
## 二、实现架构
|
||||
|
||||
### 2.1 模块状态
|
||||
|
||||
| 模块 | 文件 | 状态 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 核心逻辑 | `src/proactive/index.ts` | **已实现** | `activateProactive()`、`deactivateProactive()`、`pause/resume`、`nextTickAt` 调度状态 |
|
||||
| SleepTool 提示 | `src/tools/SleepTool/prompt.ts` | **完整** | 工具提示定义(工具名:`Sleep`) |
|
||||
| 命令注册 | `src/commands.ts:62-65` | **布线** | 动态加载 `./commands/proactive.js` |
|
||||
| 工具注册 | `src/tools.ts:26-28` | **布线** | SleepTool 动态加载 |
|
||||
| REPL 集成 | `src/screens/REPL.tsx` | **已实现** | tick 驱动、standby/sleeping 状态、页脚与 bridge automation metadata 上报 |
|
||||
| 系统提示 | `src/constants/prompts.ts:864-918` | **完整** | 自主工作行为指令(~55 行详细 prompt) |
|
||||
| 远控状态镜像 | `src/utils/sessionState.ts` | **已实现** | 向 remote-control/CCR 暴露 `automation_state` 元数据 |
|
||||
|
||||
### 2.2 系统提示内容
|
||||
|
||||
`getProactiveSection()` 注入的自主工作指令包含:
|
||||
|
||||
| 章节 | 内容 |
|
||||
|------|------|
|
||||
| Tick 驱动 | `<tick_tag>` prompt 保持存活,包含用户本地时间 |
|
||||
| 节奏控制 | SleepTool 控制等待间隔,prompt cache 5 分钟过期 |
|
||||
| 空操作规则 | 无事可做时**必须**调用 Sleep,禁止输出 "still waiting" |
|
||||
| 首次唤醒 | 简短问候,等待方向(不主动探索) |
|
||||
| 后续唤醒 | 寻找有用工作:调查、验证、检查(不 spam 用户) |
|
||||
| 偏向行动 | 读文件、搜索代码、commit — 不需询问 |
|
||||
| 终端焦点 | `terminalFocus` 字段调节自主程度 |
|
||||
|
||||
### 2.3 数据流
|
||||
|
||||
```
|
||||
activateProactive()
|
||||
│
|
||||
▼
|
||||
Tick 调度器启动
|
||||
│
|
||||
├── 定时生成 <tick_tag> 消息
|
||||
│ ├── 包含用户当前本地时间
|
||||
│ └── 注入到对话流(sessionStorage)
|
||||
│
|
||||
▼
|
||||
模型处理 tick
|
||||
│
|
||||
├── 有事可做 → 使用工具执行 → 可能再次 Sleep
|
||||
└── 无事可做 → 必须调用 SleepTool
|
||||
│
|
||||
▼
|
||||
SleepTool 等待
|
||||
│
|
||||
├── 用户插入新工作 / 队列中有命令 → 立即唤醒
|
||||
├── proactive 被关闭 → 立即中断
|
||||
└── 进入休眠时向远端 surfaces 上报 `automation_state = sleeping`
|
||||
│
|
||||
▼
|
||||
下一个 tick 到达
|
||||
```
|
||||
|
||||
## 三、当前行为补充
|
||||
|
||||
- `standby`:proactive 已开启,当前没有执行中的 turn,且已调度下一个 tick。
|
||||
- `sleeping`:模型显式调用 `SleepTool` 进入等待窗口。
|
||||
- remote-control/CCR 通过 `external_metadata.automation_state` 接收这两个状态,用于 Web UI 的 Autopilot 状态显示。
|
||||
- `SleepTool` 现在不是纯定时器;它会在共享命令队列出现新工作时提前醒来。
|
||||
|
||||
## 四、关键设计决策
|
||||
|
||||
1. **Tick 驱动**:模型通过 SleepTool 自行控制唤醒频率,不是外部事件推送
|
||||
2. **空操作必须 Sleep**:防止 "still waiting" 类空消息浪费 turn 和 token
|
||||
3. **Prompt cache 考量**:SleepTool 提示中提到 cache 5 分钟过期,建议平衡等待时间
|
||||
4. **Terminal Focus 感知**:模型根据用户是否在看终端调整自主程度
|
||||
|
||||
## 五、使用方式
|
||||
|
||||
```bash
|
||||
# 单独启用 proactive
|
||||
FEATURE_PROACTIVE=1 bun run dev
|
||||
|
||||
# 通过 KAIROS 间接启用
|
||||
FEATURE_KAIROS=1 bun run dev
|
||||
|
||||
# 组合使用
|
||||
FEATURE_PROACTIVE=1 FEATURE_KAIROS=1 FEATURE_KAIROS_BRIEF=1 bun run dev
|
||||
```
|
||||
|
||||
## 六、文件索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/proactive/index.ts` | 核心逻辑与 next-tick 状态 |
|
||||
| `src/tools/SleepTool/prompt.ts` | SleepTool 工具提示 |
|
||||
| `src/tools/SleepTool/SleepTool.ts` | 休眠/唤醒执行逻辑 |
|
||||
| `src/constants/prompts.ts:864-918` | 自主工作系统提示 |
|
||||
| `src/screens/REPL.tsx` | REPL tick 集成与 automation 状态上报 |
|
||||
| `src/utils/sessionStorage.ts:4892-4912` | Tick 消息注入 |
|
||||
| `src/utils/sessionState.ts` | bridge/CCR metadata 镜像 |
|
||||
| `src/components/PromptInput/PromptInputFooterLeftSide.tsx` | 页脚 UI 状态 |
|
||||
@@ -1,426 +0,0 @@
|
||||
# SSH Remote — 远程主机运行 Claude Code
|
||||
|
||||
## 概述
|
||||
|
||||
SSH Remote 提供两种方式在远程 Linux 主机上运行 Claude Code:
|
||||
|
||||
1. **SSH Remote 模块**(`ccb ssh <host>`)— 本地 REPL + 远程工具执行,自动部署二进制 + 认证隧道
|
||||
2. **直接 SSH 运行**(`ssh <host> -t ccb`)— 远程已安装 ccb,直接启动交互式会话
|
||||
|
||||
## 架构
|
||||
|
||||
### 方式一:SSH Remote 模块(完整模式)
|
||||
|
||||
适用场景:远端没有 API 凭据或没有安装 ccb。
|
||||
|
||||
```
|
||||
┌──────────────── 本地 Windows/Mac/Linux ───────────┐
|
||||
│ │
|
||||
│ ccb ssh <host> [dir] │
|
||||
│ │ │
|
||||
│ ├── 1. SSHProbe: 探测远端平台/架构/已有二进制 │
|
||||
│ ├── 2. SSHDeploy: 部署 dist/ 到远端 │
|
||||
│ ├── 3. SSHAuthProxy: 启动本地认证代理 │
|
||||
│ │ ├─ Unix Socket (Linux/Mac) │
|
||||
│ │ └─ TCP 127.0.0.1:<port> (Windows) │
|
||||
│ │ │
|
||||
│ └── 4. SSH -R 反向隧道 + 启动远端 CLI │
|
||||
│ ssh -R <remote>:<local> <host> \ │
|
||||
│ ANTHROPIC_BASE_URL=... \ │
|
||||
│ ANTHROPIC_AUTH_NONCE=... \ │
|
||||
│ ccb --output-format stream-json │
|
||||
│ │
|
||||
│ ┌─────── 本地 REPL (Ink TUI) ───────┐ │
|
||||
│ │ 用户输入 → NDJSON → SSH stdin │ │
|
||||
│ │ SSH stdout → NDJSON → 渲染消息 │ │
|
||||
│ │ 工具权限请求 → 本地审批 → 回传 │ │
|
||||
│ └────────────────────────────────────┘ │
|
||||
└────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ SSH 连接 (加密通道)
|
||||
│
|
||||
┌───────────────── 远端 Linux ──────────────────────┐
|
||||
│ │
|
||||
│ ccb (自动部署或已存在) │
|
||||
│ ├── --output-format stream-json │
|
||||
│ ├── --input-format stream-json │
|
||||
│ ├── --verbose -p │
|
||||
│ │ │
|
||||
│ ├── API 请求 → ANTHROPIC_BASE_URL │
|
||||
│ │ → SSH 反向隧道 → 本地 AuthProxy │
|
||||
│ │ → 注入真实凭据 → api.anthropic.com │
|
||||
│ │ │
|
||||
│ └── 工具执行 (Bash/Read/Write/...) │
|
||||
│ 直接在远端文件系统上操作 │
|
||||
└────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 方式二:直接 SSH 运行(简单模式)
|
||||
|
||||
适用场景:远端已安装 ccb 且已有 API 凭据(订阅或 API Key)。
|
||||
|
||||
```
|
||||
┌─────── 本地终端 ───────┐ ┌──────── 远端 Linux ────────┐
|
||||
│ │ SSH │ │
|
||||
│ ssh <host> -t ccb │ ──────→ │ ccb (全局安装) │
|
||||
│ │ │ ├── 使用远端自身凭据 │
|
||||
│ 终端直接显示远端 TUI │ ←────── │ ├── 远端文件系统操作 │
|
||||
│ │ TTY │ └── API 直连 Anthropic │
|
||||
└─────────────────────────┘ └─────────────────────────────┘
|
||||
```
|
||||
|
||||
### 适用场景对比
|
||||
|
||||
| | SSH Remote 模块 | 直接 SSH 运行 |
|
||||
|---|---|---|
|
||||
| 远端需要安装 ccb | 不需要(自动部署) | 需要 |
|
||||
| 远端需要 API 凭据 | 不需要(本地隧道) | 需要 |
|
||||
| 本地需要安装 ccb | 需要 | 不需要(任何终端) |
|
||||
| 斜杠命令 | 本地处理 | 远端处理 |
|
||||
| 网络延迟敏感 | 高(NDJSON 双向) | 低(仅 TTY) |
|
||||
| 推荐场景 | 远端无凭据/无安装 | 远端已配置完整 |
|
||||
|
||||
---
|
||||
|
||||
## 前置准备:SSH 密钥配置
|
||||
|
||||
两种方式都依赖 SSH 免密连接。以下是完整的密钥配置步骤。
|
||||
|
||||
### 1. 生成 SSH 密钥对(本地)
|
||||
|
||||
```bash
|
||||
# 生成 Ed25519 密钥(推荐)
|
||||
ssh-keygen -t ed25519 -C "your-email@example.com" -f ~/.ssh/id_remote
|
||||
|
||||
# 或 RSA 4096 位
|
||||
ssh-keygen -t rsa -b 4096 -C "your-email@example.com" -f ~/.ssh/id_remote
|
||||
```
|
||||
|
||||
生成两个文件:
|
||||
- `~/.ssh/id_remote` — 私钥(不可泄露)
|
||||
- `~/.ssh/id_remote.pub` — 公钥(部署到远端)
|
||||
|
||||
### 2. 将公钥部署到远端
|
||||
|
||||
```bash
|
||||
# 方式 A:ssh-copy-id(推荐)
|
||||
ssh-copy-id -i ~/.ssh/id_remote.pub user@remote-host
|
||||
|
||||
# 方式 B:手动复制
|
||||
cat ~/.ssh/id_remote.pub | ssh user@remote-host "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
|
||||
```
|
||||
|
||||
### 3. 配置 SSH Config(本地)
|
||||
|
||||
编辑 `~/.ssh/config`(不存在则创建):
|
||||
|
||||
```
|
||||
Host my-server
|
||||
HostName 192.168.1.100 # 远端 IP 或域名
|
||||
User root # 远端用户名
|
||||
IdentityFile ~/.ssh/id_remote # 私钥路径
|
||||
ServerAliveInterval 60 # 防止连接超时断开
|
||||
ServerAliveCountMax 3
|
||||
```
|
||||
|
||||
配置后可直接用别名连接:
|
||||
|
||||
```bash
|
||||
ssh my-server # 等同于 ssh -i ~/.ssh/id_remote root@192.168.1.100
|
||||
```
|
||||
|
||||
### 4. 文件权限设置
|
||||
|
||||
#### Linux / macOS
|
||||
|
||||
```bash
|
||||
chmod 700 ~/.ssh
|
||||
chmod 600 ~/.ssh/config
|
||||
chmod 600 ~/.ssh/id_remote
|
||||
chmod 644 ~/.ssh/id_remote.pub
|
||||
```
|
||||
|
||||
#### Windows(OpenSSH 强制 ACL 检查)
|
||||
|
||||
```powershell
|
||||
# 重置 .ssh 目录权限:仅允许当前用户 + SYSTEM
|
||||
icacls "$env:USERPROFILE\.ssh" /inheritance:r /grant:r "$($env:USERNAME):(OI)(CI)F" /grant "SYSTEM:(OI)(CI)F"
|
||||
|
||||
# 修复 config 文件权限
|
||||
icacls "$env:USERPROFILE\.ssh\config" /inheritance:r /grant:r "$($env:USERNAME):F" /grant "SYSTEM:F"
|
||||
|
||||
# 修复私钥权限
|
||||
icacls "$env:USERPROFILE\.ssh\id_remote" /inheritance:r /grant:r "$($env:USERNAME):F" /grant "SYSTEM:F"
|
||||
```
|
||||
|
||||
> **Windows 常见错误**:如果 `icacls` 显示 `UNKNOWN\UNKNOWN` ACL 条目,需要先移除再重新授权。权限错误会导致 SSH 拒绝使用密钥。
|
||||
|
||||
### 5. 验证免密连接
|
||||
|
||||
```bash
|
||||
ssh my-server "echo 'SSH connection OK'"
|
||||
# 应直接输出 "SSH connection OK",不要求输入密码
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 方式一:SSH Remote 模块
|
||||
|
||||
```bash
|
||||
# 基本用法 — 自动探测、部署、启动
|
||||
ccb ssh user@remote-host
|
||||
|
||||
# 使用 SSH Config 别名
|
||||
ccb ssh my-server
|
||||
|
||||
# 指定远端工作目录
|
||||
ccb ssh my-server /home/user/project
|
||||
|
||||
# 使用自定义远端二进制(跳过探测/部署)
|
||||
ccb ssh my-server --remote-bin "bun /opt/ccb/dist/cli.js"
|
||||
|
||||
# 权限控制
|
||||
ccb ssh my-server --permission-mode auto
|
||||
ccb ssh my-server --dangerously-skip-permissions
|
||||
|
||||
# 恢复远端会话
|
||||
ccb ssh my-server --continue
|
||||
ccb ssh my-server --resume <session-uuid>
|
||||
|
||||
# 选择模型
|
||||
ccb ssh my-server --model claude-sonnet-4-6-20250514
|
||||
|
||||
# 本地测试模式(不连接远端,测试 auth proxy 管道)
|
||||
ccb ssh localhost --local
|
||||
```
|
||||
|
||||
### 方式二:直接 SSH 运行
|
||||
|
||||
```bash
|
||||
# 启动交互式会话
|
||||
ssh my-server -t ccb
|
||||
|
||||
# 指定工作目录
|
||||
ssh my-server -t "ccb --cwd /home/user/project"
|
||||
|
||||
# 使用特定模型
|
||||
ssh my-server -t "ccb --model claude-sonnet-4-6-20250514"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 构建与部署
|
||||
|
||||
### 构建产物
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
bun install
|
||||
|
||||
# 构建(输出到 dist/)
|
||||
bun run build
|
||||
```
|
||||
|
||||
产物说明:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `dist/cli.js` | Bun 入口(`#!/usr/bin/env bun`) |
|
||||
| `dist/cli-node.js` | Node.js 入口(`#!/usr/bin/env node` → `import ./cli.js`) |
|
||||
| `dist/cli-bun.js` | Bun 专用入口 |
|
||||
| `dist/chunk-*.js` | 代码分割 chunk 文件(约 668 个) |
|
||||
|
||||
### 运行方式
|
||||
|
||||
```bash
|
||||
# 方式 A:通过 bun 直接运行(开发/调试)
|
||||
bun run dev
|
||||
|
||||
# 方式 B:运行构建产物(bun 运行时)
|
||||
bun dist/cli.js
|
||||
|
||||
# 方式 C:运行构建产物(node 运行时)
|
||||
node dist/cli-node.js
|
||||
|
||||
# 方式 D:全局安装后使用命令名
|
||||
ccb
|
||||
```
|
||||
|
||||
### 全局安装
|
||||
|
||||
在项目根目录执行:
|
||||
|
||||
```bash
|
||||
# bun 全局安装(推荐)
|
||||
bun install -g .
|
||||
|
||||
# 创建的命令:
|
||||
# ccb → dist/cli-node.js
|
||||
# ccb-bun → dist/cli-bun.js
|
||||
# claude-code-best → dist/cli-node.js
|
||||
|
||||
# 安装位置:~/.bun/bin/ccb
|
||||
```
|
||||
|
||||
或使用 npm:
|
||||
|
||||
```bash
|
||||
npm install -g .
|
||||
```
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
ccb --version
|
||||
# → x.x.x (Claude Code)
|
||||
```
|
||||
|
||||
### 远端部署(全流程)
|
||||
|
||||
```bash
|
||||
# 1. 登录远端
|
||||
ssh my-server
|
||||
|
||||
# 2. 克隆或同步项目代码
|
||||
git clone <repo-url> ~/ccb-project
|
||||
cd ~/ccb-project
|
||||
|
||||
# 3. 安装运行时(如果没有 bun)
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
source ~/.bashrc
|
||||
|
||||
# 4. 安装依赖 + 构建
|
||||
bun install
|
||||
bun run build
|
||||
|
||||
# 5. 全局安装
|
||||
bun install -g .
|
||||
|
||||
# 6. 确保非交互式 SSH 可访问 ccb 命令
|
||||
# bun install -g 安装到 ~/.bun/bin/,但非交互式 SSH 不加载 .bashrc,
|
||||
# 所以 PATH 中不包含 ~/.bun/bin/
|
||||
# 解决方式(任选其一):
|
||||
|
||||
# 方式 A:符号链接到系统 PATH(推荐)
|
||||
ln -sf ~/.bun/bin/ccb /usr/local/bin/ccb
|
||||
|
||||
# 方式 B:添加到 /etc/profile.d/(所有用户生效)
|
||||
echo 'export PATH="$HOME/.bun/bin:$PATH"' > /etc/profile.d/bun-path.sh
|
||||
|
||||
# 方式 C:添加到 ~/.bash_profile(当前用户,ssh -t 时生效)
|
||||
echo 'export PATH="$HOME/.bun/bin:$PATH"' >> ~/.bash_profile
|
||||
|
||||
# 7. 验证
|
||||
ccb --version
|
||||
|
||||
# 8. 从本地测试
|
||||
# (在本地终端)
|
||||
ssh my-server -t ccb
|
||||
```
|
||||
|
||||
### SSH Remote 自动部署
|
||||
|
||||
使用 `ccb ssh <host>` 时,模块自动处理:
|
||||
|
||||
1. **SSHProbe** 探测远端 `~/.local/bin/claude` 或 `command -v claude`
|
||||
2. 若二进制不存在或版本不匹配,**SSHDeploy** 通过 `scp` 传输 `dist/` 目录
|
||||
3. 在远端创建 wrapper 脚本(`~/.local/bin/claude`)
|
||||
4. 无需手动安装
|
||||
|
||||
---
|
||||
|
||||
## 模块结构
|
||||
|
||||
```
|
||||
src/ssh/
|
||||
├── createSSHSession.ts — 会话工厂:编排 probe → deploy → proxy → spawn
|
||||
├── SSHSessionManager.ts — 双向 NDJSON 通信管理 + 权限转发 + 重连
|
||||
├── SSHAuthProxy.ts — 本地认证代理(API 凭据隧道)
|
||||
├── SSHProbe.ts — 远端主机探测(平台/架构/已有二进制)
|
||||
├── SSHDeploy.ts — 远端二进制部署(scp + wrapper 脚本)
|
||||
└── __tests__/
|
||||
└── SSHSessionManager.test.ts — 17 个单元测试
|
||||
```
|
||||
|
||||
## 关键技术细节
|
||||
|
||||
### 认证隧道
|
||||
|
||||
- **AuthProxy** 在本地监听(Unix socket 或 TCP),接收远端 CLI 的 API 请求
|
||||
- 通过 SSH `-R` 反向端口转发隧道到远端
|
||||
- AuthProxy 注入本地真实凭据(API key 或 OAuth token),转发到 `api.anthropic.com`
|
||||
- `ANTHROPIC_AUTH_NONCE` header 防止未授权访问(nonce 通过环境变量传递给远端 CLI,远端 CLI 在每个 API 请求中携带此 header)
|
||||
|
||||
### waitForInit vs 存活检查
|
||||
|
||||
- **标准模式**:`waitForInit` 等待远端 CLI 发送 `{type:'system', subtype:'init'}` JSON 消息
|
||||
- **`--remote-bin` 模式**:跳过 `waitForInit`(print+stream-json 模式下 init 只在首次查询后发送),改用 3 秒进程存活检查
|
||||
|
||||
### 重连机制
|
||||
|
||||
- `SSHSessionManager` 检测 SSH 连接断开后自动重连
|
||||
- 重连时在远端 CLI 命令中追加 `--continue` 恢复会话
|
||||
- 指数退避重试(最多 5 次,间隔 1s → 2s → 4s → 8s → 16s)
|
||||
|
||||
## Feature Flag
|
||||
|
||||
SSH Remote 功能受 `SSH_REMOTE` feature flag 控制:
|
||||
|
||||
- **Dev 模式**:默认启用
|
||||
- **Build 模式**:需在 `build.ts` 的 `DEFAULT_BUILD_FEATURES` 中添加 `'SSH_REMOTE'`
|
||||
- **运行时**:`FEATURE_SSH_REMOTE=1` 环境变量
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### `ccb: command not found`(SSH 远程执行时)
|
||||
|
||||
非交互式 SSH 不加载 `.bashrc`,`~/.bun/bin` 不在 PATH 中。
|
||||
|
||||
```bash
|
||||
# 解决:创建符号链接
|
||||
ln -sf ~/.bun/bin/ccb /usr/local/bin/ccb
|
||||
```
|
||||
|
||||
### SSH 密钥被拒绝
|
||||
|
||||
```
|
||||
Permission denied (publickey)
|
||||
```
|
||||
|
||||
1. 确认公钥已添加到远端 `~/.ssh/authorized_keys`
|
||||
2. 确认本地私钥文件权限正确(`chmod 600`)
|
||||
3. 确认 `~/.ssh/config` 中 `IdentityFile` 路径正确
|
||||
4. Windows 用户检查 ACL 权限(见上方 Windows 权限设置)
|
||||
|
||||
### SSH 连接超时
|
||||
|
||||
```
|
||||
ssh: connect to host x.x.x.x port 22: Connection timed out
|
||||
```
|
||||
|
||||
1. 确认远端 SSH 服务正在运行:`systemctl status sshd`
|
||||
2. 确认防火墙允许 22 端口
|
||||
3. 确认 IP 地址/域名正确
|
||||
4. 在 `~/.ssh/config` 中添加 `ConnectTimeout 10`
|
||||
|
||||
### 403 Forbidden(SSH Remote 模块)
|
||||
|
||||
AuthProxy 的 nonce 验证失败。确认:
|
||||
1. 远端 CLI 版本包含 nonce header 注入修复
|
||||
2. `ANTHROPIC_AUTH_NONCE` 环境变量正确传递到远端
|
||||
3. `src/services/api/client.ts` 中 `x-auth-nonce` header 已启用
|
||||
|
||||
### 远端 CLI 启动后立即退出
|
||||
|
||||
```
|
||||
Remote process exited immediately (code 1)
|
||||
```
|
||||
|
||||
1. 确认远端 `bun` / `node` 运行时可用
|
||||
2. 手动在远端执行 `ccb --version` 验证安装
|
||||
3. 检查 `--remote-bin` 路径是否正确
|
||||
4. 查看 stderr 输出获取详细错误信息
|
||||
@@ -1,275 +0,0 @@
|
||||
---
|
||||
title: "StatusLine 底部状态栏 - 自定义 shell 渲染管线"
|
||||
description: "从源码角度解析 Claude Code 底部状态栏:自定义 shell 脚本 + JSON stdin 协议、三种触发源(event / settings / time)、debounce + abort、信任与 hook 开关、以及本仓库 refreshInterval 缺失修复。"
|
||||
keywords: ["statusLine", "状态栏", "自定义提示符", "refreshInterval", "Hooks"]
|
||||
---
|
||||
|
||||
{/* 本章目标:完整讲清 StatusLine 的渲染管线、触发模型、协议契约与安全网关,并记录本仓库相对官方版本的已知缺口与修复 */}
|
||||
|
||||
## 概述
|
||||
|
||||
StatusLine 是 Claude Code REPL 底部显示的一行自定义文本,由**用户提供的 shell 命令**渲染。主进程把运行时状态(模型、工作目录、token、限流、会话元数据等)打包成 JSON 通过 stdin 喂给脚本,脚本在 stdout 输出一行字符串,Ink 侧以 ANSI 转义渲染到 footer。
|
||||
|
||||
核心设计哲学:**语言无关 + 进程隔离 + Unix 管道**。用户可用 bash / python / node / 任意语言写脚本;脚本崩溃不影响主进程;输入输出都是纯文本,可以离线测试(`echo '{...}' | ./script.sh`)。
|
||||
|
||||
## 配置
|
||||
|
||||
`~/.claude/settings.json` 里添加 `statusLine` 字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusLine": {
|
||||
"type": "command",
|
||||
"command": "bash ~/.claude/statusline-command.sh",
|
||||
"refreshInterval": 1,
|
||||
"padding": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 作用 |
|
||||
|------|------|------|
|
||||
| `type` | `"command"` | 目前仅支持 command 型 |
|
||||
| `command` | `string` | shell 命令字符串;主进程用系统 shell 解释执行 |
|
||||
| `refreshInterval` | `number` (秒) | 定时刷新周期;缺省/0 表示不定时刷新 |
|
||||
| `padding` | `number` | 左右 padding,单位为 Ink cell |
|
||||
|
||||
Schema 定义在 `src/utils/settings/types.ts:550`(`statusLine` Zod object)。
|
||||
|
||||
## 渲染管线(整体图)
|
||||
|
||||
```
|
||||
┌─────────────────────── Ink 侧 ───────────────────────┐ ┌──────── 用户侧 ────────┐
|
||||
│ │ │ │
|
||||
│ buildStatusLineCommandInput() ──┐ │ │ ~/.claude/ │
|
||||
│ 收集运行时状态 │ │ │ statusline-*.sh │
|
||||
│ ▼ │ │ │
|
||||
│ executeStatusLineCommand() ─── JSON via stdin ────────────► jq '.model...' │
|
||||
│ execCommandHook() 拉起 shell │ │ 计算、格式化 │
|
||||
│ ▲ │ │ │
|
||||
│ stdout ◄──────────────────── 一行文本 ──────────────── printf '...' │
|
||||
│ │ │ │ │
|
||||
│ setAppState({ statusLineText }) ─┘ │ └────────────────────────┘
|
||||
│ zustand 存字段,组件 memo 订阅 │
|
||||
│ │
|
||||
│ <StatusLine /> → <Text><Ansi>{text}</Ansi></Text> │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Input 协议:主进程 → 脚本
|
||||
|
||||
`buildStatusLineCommandInput`(`src/components/StatusLine.tsx:53`)构造的 JSON 对象字段如下,**这是脚本可以 `jq` 读取的全部内容**:
|
||||
|
||||
| 字段 | 来源 | 备注 |
|
||||
|------|------|------|
|
||||
| `session_id` | `getSessionId()` | UUID,用于脚本侧 per-session 状态隔离 |
|
||||
| `session_name` | `getCurrentSessionTitle(sessionId)` | 用户命名的会话标题(可选) |
|
||||
| `model.id` / `model.display_name` | `getRuntimeMainLoopModel()` | 运行时真实模型(经 permission mode 降级/200k 升级) |
|
||||
| `workspace.current_dir` / `project_dir` / `added_dirs` | `getCwd()` / `getOriginalCwd()` / permission context | current_dir 随 `cd` 变化 |
|
||||
| `version` | `MACRO.VERSION` | 构建注入,如 `2.1.888` |
|
||||
| `output_style.name` | `settings.outputStyle` | 缺省 `DEFAULT_OUTPUT_STYLE_NAME` |
|
||||
| `cost.total_cost_usd` / `total_duration_ms` / `total_api_duration_ms` / `total_lines_added` / `total_lines_removed` | `cost-tracker.js` 聚合 | 会话累计 |
|
||||
| `context_window.total_input_tokens` / `total_output_tokens` | 同上 | 累计 token |
|
||||
| `context_window.context_window_size` | `getContextWindowForModel()` | 模型上下文上限 |
|
||||
| `context_window.current_usage` | `getCurrentUsage(messages)` | **最新一次 assistant message 的 usage**;含 `input_tokens` / `cache_creation_input_tokens` / `cache_read_input_tokens` / `output_tokens` |
|
||||
| `context_window.used_percentage` / `remaining_percentage` | `calculateContextPercentages()` | 0-100 浮点 |
|
||||
| `exceeds_200k_tokens` | 检查最近 assistant message | 用于 1M 上下文模型的展示 |
|
||||
| `rate_limits.five_hour` / `seven_day` | `getRawUtilization()` | `{ used_percentage, resets_at }`,来自 Claude.ai 限流 API |
|
||||
| `vim.mode` | 启用 vim 模式时 | `INSERT` / `NORMAL` / ... |
|
||||
| `agent.name` | 主线程 agent 类型 | 子 agent fork 时非空 |
|
||||
| `remote.session_id` | Bridge / Remote Control 模式 | 远程会话 |
|
||||
| `worktree` | 当前 worktree 元信息 | `name` / `path` / `branch` / `original_cwd` / `original_branch` |
|
||||
|
||||
类型签名目前在 `src/types/statusLine.ts` 是 `any` 的 stub(反编译残留),实际字段以上表为准。
|
||||
|
||||
## Output 协议:脚本 → 主进程
|
||||
|
||||
`executeStatusLineCommand`(`src/utils/hooks.ts:4752`)对脚本 stdout 做如下处理:
|
||||
|
||||
1. `trim()` 首尾空白
|
||||
2. 按 `\n` 拆行,每行再 `trim()`
|
||||
3. 空行丢弃,剩余用 `\n` 重新拼接
|
||||
|
||||
多行输出会被**保留为多行**(Ink 渲染时 `<Text>` 允许换行),但设计推荐**单行**——多行会挤占 REPL 高度,fullscreen 模式下可能挤掉 ScrollBox 行。
|
||||
|
||||
状态码约定:
|
||||
- `exit 0` + 有 stdout → 显示
|
||||
- `exit 0` + 空 stdout → 清空 statusLine(显示为空)
|
||||
- 非 0 → 忽略,保留上次内容;`logResult=true` 时 warn 级日志
|
||||
- 超时(默认 5000ms) → 忽略
|
||||
- 被 AbortController 取消 → 忽略
|
||||
|
||||
ANSI 颜色可用,Ink 通过 `<Ansi>{text}</Ansi>` 组件解析 SGR 序列。
|
||||
|
||||
## 三种触发源
|
||||
|
||||
StatusLine 的重算由**三类事件**驱动,全部经同一个 debounce 队列:
|
||||
|
||||
### 1. Event-driven(`src/components/StatusLine.tsx:275`)
|
||||
|
||||
监听这些状态变化,触发 `scheduleUpdate()`:
|
||||
|
||||
- `lastAssistantMessageId` — 新助手回复出现
|
||||
- `permissionMode` — `/mode` 切换权限模式
|
||||
- `vimMode` — vim insert/normal 切换
|
||||
- `mainLoopModel` — `/model` 切换
|
||||
|
||||
### 2. Settings-driven(`src/components/StatusLine.tsx:294`)
|
||||
|
||||
`settings.statusLine.command` 字符串变化时(热重载 settings.json),标记下一次结果 log 并立即 `doUpdate()`。
|
||||
|
||||
### 3. Time-driven(`src/components/StatusLine.tsx:292`,本仓库补丁)
|
||||
|
||||
读取 `settings.statusLine.refreshInterval`(秒),`setInterval` 每到点走一次 `scheduleUpdate()`。配置为 0 或缺省时不启定时器(零开销)。
|
||||
|
||||
> **本仓库历史缺口**:反编译出的 `StatusLine.tsx` 最初没有 Time-driven 触发路径,`refreshInterval` 字段也不在 Zod schema 里。导致脚本里 TTL 倒计时、时钟类动态内容不会秒刷,只有助手回复出现时才重算。已在 2026-05-06 补齐,细节见下方"已知缺口与修复"。
|
||||
|
||||
## Debounce + Abort
|
||||
|
||||
三种触发源都走 `scheduleUpdate`(`src/components/StatusLine.tsx:259`):
|
||||
|
||||
```
|
||||
scheduleUpdate() → setTimeout(300ms) → doUpdate()
|
||||
│
|
||||
└─ 再次 schedule 会 clearTimeout 前次
|
||||
```
|
||||
|
||||
300ms debounce 合并抖动事件(例如短时间连续切 vim/permission)。
|
||||
|
||||
`doUpdate()` 里:
|
||||
|
||||
```
|
||||
abortControllerRef.current?.abort() // 取消上一次 in-flight shell
|
||||
controller = new AbortController()
|
||||
executeStatusLineCommand(..., controller.signal, ...)
|
||||
```
|
||||
|
||||
**单飞(single-flight)语义**:任何新触发都会 abort 上一次未完成的 shell 调用,保证同一时刻最多一个子进程。这对 `refreshInterval: 1` 尤其关键——若脚本执行 > 1 秒,新 tick 到来时老进程被 kill,不会堆积。
|
||||
|
||||
## 安全网关
|
||||
|
||||
`executeStatusLineCommand`(`src/utils/hooks.ts:4752`)在执行前有**三层拦截**:
|
||||
|
||||
1. `shouldDisableAllHooksIncludingManaged()` → managed settings 全局禁用 hooks 时直接返回
|
||||
2. `shouldSkipHookDueToTrust()` → **工作区未接受信任对话框时跳过**,避免打开未知仓库时执行任意 shell 命令(RCE 防护)
|
||||
3. `shouldAllowManagedHooksOnly()` → 非 managed settings 禁用 hooks 但 managed 未禁用时,只读取 policySettings 源的 statusLine
|
||||
|
||||
组件侧配合(`src/components/StatusLine.tsx:318`):未接受 trust 时在通知中心提示 `"statusline skipped · restart to fix"`。
|
||||
|
||||
另外,`statusLineShouldDisplay`(`src/components/StatusLine.tsx:46`)在 **Kairos assistant mode** 下直接返回 false——因为那时 statusline 字段反映的是 REPL/daemon 进程状态,不是 agent 子进程在跑的东西,显示出来会误导用户。
|
||||
|
||||
## 渲染细节
|
||||
|
||||
### memo 隔离
|
||||
|
||||
```tsx
|
||||
export const StatusLine = memo(StatusLineInner)
|
||||
```
|
||||
|
||||
父组件 `PromptInputFooter` 每次 `setMessages` 都 rerender,但 `StatusLine` 的 props 只有 `lastAssistantMessageId` 会变,`memo` 阻断了无意义的重渲染。此前(未 memo 版本)一个 session 内大约 18 次冗余渲染。
|
||||
|
||||
### 订阅粒度
|
||||
|
||||
```tsx
|
||||
const statusLineText = useAppState(s => s.statusLineText)
|
||||
```
|
||||
|
||||
`useAppState` 是选择器订阅,仅在 `statusLineText` 字段变化时触发 rerender;`doUpdate()` 里还做了幂等检查(`prev.statusLineText === text` 则直接返回原 state),**文本不变就不更新 zustand**,连一次 notify 都省掉。
|
||||
|
||||
### Fullscreen 占位
|
||||
|
||||
```tsx
|
||||
{statusLineText ? (
|
||||
<Text dimColor wrap="truncate"><Ansi>{statusLineText}</Ansi></Text>
|
||||
) : isFullscreenEnvEnabled() ? (
|
||||
<Text> </Text> // 占位一行
|
||||
) : null}
|
||||
```
|
||||
|
||||
Fullscreen 模式下 footer `flexShrink:0`,statusline 从 0 行变 1 行会挤掉 ScrollBox 一行内容导致抖动。首次脚本还没返回时,用空格文本占住一行高度,脚本返回后原位替换。
|
||||
|
||||
## 内置 `/statusline` slash command
|
||||
|
||||
`src/commands/statusline.tsx` 定义了一个 **prompt 型 command**,展开成自然语言指令喂给主 Agent:
|
||||
|
||||
```
|
||||
Create an AgentTool with subagent_type "statusline-setup" and the prompt "<user-args>"
|
||||
```
|
||||
|
||||
默认 prompt 是 `"Configure my statusLine from my shell PS1 configuration"`。主 Agent 收到后会调用内置子 agent `statusline-setup`。该子 agent 权限极小:
|
||||
|
||||
- **Tools**: 仅 `Read`、`Edit`
|
||||
- **Allowed paths**: `Read(~/**)`、`Edit(~/.claude/settings.json)`
|
||||
|
||||
也就是说它**不能 Write 新文件、不能跑 Bash**。典型工作是读用户的 shell 配置、读/改 `settings.json`、增量编辑已有的 statusline 脚本。
|
||||
|
||||
## 编写自定义脚本的要点
|
||||
|
||||
1. **脚本必须无状态** — 每次 tick 主进程 fork 一次新 shell,进程内变量不跨调用保留。需要跨 tick 的状态(上次时间戳、上次 token 数)用 `~/.claude/statusline-state/<hash>.state` 文件持久化。
|
||||
2. **按 `session_id` 哈希隔离状态文件** — 多会话同时开着时共享一个 state 文件会串。典型做法:`md5(session_id) | head -c 16` 作为文件名。
|
||||
3. **防御性读取** — state 文件可能损坏/被截断,按行 read + 字段校验(数字字段用 `case "$var" in ''|*[!0-9]*) invalid ;;`)。
|
||||
4. **`refreshInterval` 不等于"脚本秒级调用"** — tick 和事件触发(新消息、模式切换)都走同一 debounce 队列,脚本实际被调用的频率介于"每 N 秒"和"每 N+0.3 秒"之间;且 abort 机制下,上一次没跑完会被 kill。
|
||||
5. **执行时间预算** — 默认 5000ms 超时;为避免 `refreshInterval=1` 时频繁超时,脚本热路径应在 100ms 内完成。重计算(curl、git log 拉取)需缓存。
|
||||
6. **颜色用 ANSI 转义** — 不要依赖 TERM 环境变量;Ink 的 `<Ansi>` 组件独立解析 SGR。
|
||||
7. **不要输出多行** — 单行文本,否则挤占 REPL 布局。
|
||||
8. **处理 `current_usage` 为 null 的情况** — 首次响应之前 `context_window.current_usage` 可能为 null,脚本应有 fallback(如读 state 里上次命中率)。
|
||||
|
||||
### 示例:Cache 命中率 + TTL 倒计时
|
||||
|
||||
本仓库默认安装了一个示例脚本 `~/.claude/statusline-command.sh`(用户侧),输出格式 `<dir> | <model> | ctx:N% | Cache 97% 59:43`:
|
||||
|
||||
- **命中率** = `cache_read / (input + cache_creation + cache_read)`(取自 `current_usage`)
|
||||
- **TTL** 从上次响应倒数 60 分钟,**只在 token signature 变化时重置时间戳**,避免秒级 tick 把 TTL 一直锁在 60:00
|
||||
- **颜色分段** — 命中率 ≥50% 绿 / <50% 灰;TTL 0-20m 绿 / 20-40m 黄 / 40-55m 红 / 最后 5m 闪红 / 过期 `exp` 灰
|
||||
- **Per-session state** — `~/.claude/statusline-state/<md5(session_id)[:16]>.state` 三行(signature、timestamp、hit),读前做 numeric 校验
|
||||
- **Fallback** — `current_usage` 为 null 时读 state 显示上次命中率
|
||||
|
||||
> 该脚本配合 `refreshInterval: 1` 即可秒刷 TTL,前提是 `refreshInterval` 触发路径已实现(见下节)。
|
||||
|
||||
## 已知缺口与修复(本仓库)
|
||||
|
||||
反编译版的 `StatusLine.tsx` 存在一处功能缺口:
|
||||
|
||||
| 项 | 官方 Claude Code | 本仓库原始 | 本仓库现状 |
|
||||
|----|-----------------|-----------|-----------|
|
||||
| `refreshInterval` Zod 字段 | ✅ 有 | ❌ 无 | ✅ 已补 |
|
||||
| Time-driven `setInterval` 触发 | ✅ 有 | ❌ 无 | ✅ 已补 |
|
||||
| Event-driven 触发 | ✅ 有 | ✅ 有 | — |
|
||||
| Settings-driven 触发 | ✅ 有 | ✅ 有 | — |
|
||||
| Debounce + Abort | ✅ 有 | ✅ 有 | — |
|
||||
| Trust 网关 | ✅ 有 | ✅ 有 | — |
|
||||
|
||||
修复(2026-05-06):
|
||||
|
||||
**1. `src/utils/settings/types.ts:554`** — statusLine schema 新增 `refreshInterval: z.number().optional()`,让字段进入类型系统而非被当未知键忽略。
|
||||
|
||||
**2. `src/components/StatusLine.tsx:292`** — 新增 Time-driven useEffect:
|
||||
|
||||
```tsx
|
||||
const refreshIntervalMs = (settings?.statusLine?.refreshInterval ?? 0) * 1000;
|
||||
useEffect(() => {
|
||||
if (refreshIntervalMs <= 0) return;
|
||||
const id = setInterval(() => scheduleUpdate(), refreshIntervalMs);
|
||||
return () => clearInterval(id);
|
||||
}, [refreshIntervalMs, scheduleUpdate]);
|
||||
```
|
||||
|
||||
关键点:
|
||||
- 走 `scheduleUpdate`(非 `doUpdate`)复用 300ms debounce,interval + event 双触发不会双跑
|
||||
- `refreshIntervalMs <= 0` 时不启定时器,对未启用该字段的用户零开销
|
||||
- 依赖数组含 `refreshIntervalMs`,settings 热重载会自动清理旧 interval 重建新的
|
||||
|
||||
**静默失效特征**:修复前 settings.json 写 `refreshInterval: 1` 无任何报错——JSON 解析通过,Zod schema 默认 strip 多余字段,官方文档又说支持这个字段,用户很容易以为生效了而没意识到 TTL/时钟类输出根本没秒刷。这是反编译版本的典型"文档与实现不一致"。
|
||||
|
||||
## 相关源码
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `src/components/StatusLine.tsx` | UI 组件、触发逻辑、buildStatusLineCommandInput |
|
||||
| `src/utils/hooks.ts:4752` | `executeStatusLineCommand`:shell 执行、输出处理、安全网关 |
|
||||
| `src/utils/settings/types.ts:550` | `statusLine` Zod schema |
|
||||
| `src/types/statusLine.ts` | `StatusLineCommandInput` 类型(当前为 stub) |
|
||||
| `src/commands/statusline.tsx` | `/statusline` slash command 定义 |
|
||||
| `src/state/AppStateStore.ts:95` | `statusLineText` 字段声明 |
|
||||
| `src/components/PromptInput/PromptInputFooter.tsx:159` | StatusLine 组件挂载点 |
|
||||
@@ -1,310 +0,0 @@
|
||||
# Stub 恢复设计 1-4
|
||||
|
||||
> 日期:2026-04-12
|
||||
> 目标:基于当前代码边界,为下一阶段 4 个 stub/半 stub 命令面给出可实施的设计方案。
|
||||
> 排序原则:按建议实施顺序排序,不按问题严重性排序。
|
||||
|
||||
## 设计原则
|
||||
|
||||
- 先做能独立闭环、收益明确、改动边界清晰的项。
|
||||
- 大项拆成 `MVP` 和 `Phase 2+`,避免一次性掉进大范围恢复。
|
||||
- 优先复用已有状态、传输层、日志与配置能力,不重造协议。
|
||||
- 设计以当前仓库实际代码为准,不以旧文档的理想状态为准。
|
||||
|
||||
## 1. `claude daemon status` / `claude daemon stop`
|
||||
|
||||
### 现状
|
||||
|
||||
- `start` 路径已有完整 supervisor + worker 生命周期:
|
||||
`src/daemon/main.ts`
|
||||
`src/daemon/workerRegistry.ts`
|
||||
- `status` / `stop` 目前只是占位输出:
|
||||
`src/daemon/main.ts`
|
||||
- `/remote-control-server` 有自己的命令内 UI 状态,但只维护当前进程内的 `daemonProcess`,并不适合作为跨进程 CLI 管理基础:
|
||||
`src/commands/remoteControlServer/remoteControlServer.tsx`
|
||||
|
||||
### 目标
|
||||
|
||||
- 让 `claude daemon status` 和 `claude daemon stop` 在另一个 CLI 进程中也能正确工作。
|
||||
- 不依赖 TUI 内存态,不要求当前命令进程就是启动 daemon 的那个进程。
|
||||
|
||||
### MVP 方案
|
||||
|
||||
- 新增 daemon 状态文件,例如:
|
||||
`~/.claude/daemon/remote-control.json`
|
||||
- `start` 时写入:
|
||||
- supervisor pid
|
||||
- cwd
|
||||
- startedAt
|
||||
- worker kinds
|
||||
- 最近状态
|
||||
- `status`:
|
||||
- 读取状态文件
|
||||
- 用现有进程探测能力验证 pid 是否存活
|
||||
- 输出 `running / stopped / stale`
|
||||
- stale 时自动清理状态文件
|
||||
- `stop`:
|
||||
- 读取 pid
|
||||
- 发送 `SIGTERM`
|
||||
- 等待退出
|
||||
- 超时后 `SIGKILL`
|
||||
- 清理状态文件
|
||||
|
||||
### 代码范围
|
||||
|
||||
- 新增 `src/daemon/state.ts`
|
||||
- 修改 `src/daemon/main.ts`
|
||||
- 轻量修改 `src/commands/remoteControlServer/remoteControlServer.tsx`,让 UI 尽量读取同一份状态文件
|
||||
|
||||
### 验证
|
||||
|
||||
1. `claude daemon start`
|
||||
2. 新开终端执行 `claude daemon status`
|
||||
3. 执行 `claude daemon stop`
|
||||
4. 再次执行 `claude daemon status`,确认返回 `stopped` 或清晰的 `stale cleaned`
|
||||
|
||||
### 风险
|
||||
|
||||
- Windows 信号模型和 Unix 不同,`stop` 需要超时兜底。
|
||||
- 当前设计默认单 supervisor,不处理多实例并发。
|
||||
|
||||
### 工作量判断
|
||||
|
||||
- 小
|
||||
- 适合作为下一步的首选实现项
|
||||
|
||||
## 2. `BG_SESSIONS`
|
||||
|
||||
### 现状
|
||||
|
||||
- fast-path 已接好:
|
||||
`src/entrypoints/cli.tsx`
|
||||
- session registry 已有真实实现:
|
||||
`src/utils/concurrentSessions.ts`
|
||||
- `exit` 在 bg session 内已会 `tmux detach-client`:
|
||||
`src/commands/exit/exit.tsx`
|
||||
- 但 CLI handler 仍全空:
|
||||
`src/cli/bg.ts`
|
||||
- task summary 仍然是 stub:
|
||||
`src/utils/taskSummary.ts`
|
||||
|
||||
### 目标
|
||||
|
||||
- 先把 `ps` / `logs` / `kill` 做成真正有用的 session 管理命令。
|
||||
- 不在第一阶段就强行补完 `attach` / `--bg`。
|
||||
|
||||
### Phase 2A:MVP
|
||||
|
||||
- 实现 `ps`
|
||||
- 从 registry 读取 live sessions
|
||||
- 展示 pid、kind、sessionId、cwd、name、startedAt、bridgeSessionId
|
||||
- 如果有 activity/status,则一并展示
|
||||
- 实现 `logs`
|
||||
- 支持按 `sessionId / pid / name` 查找
|
||||
- 优先复用本地 transcript/log 读取能力
|
||||
- 如果 registry 里存在 `logPath`,支持 tail 文件
|
||||
- 实现 `kill`
|
||||
- 解析目标 session
|
||||
- 发退出信号
|
||||
- 清理 stale registry
|
||||
|
||||
### Phase 2B:后续
|
||||
|
||||
- 实现 `attach`
|
||||
- 实现 `--bg`
|
||||
- 实现 `taskSummary` 的中途状态更新
|
||||
|
||||
### 为什么要拆
|
||||
|
||||
- 现有 registry 记录了 `pid / sessionId / name / logPath`
|
||||
- 但没有可靠的 tmux attach target
|
||||
- 所以 `attach` 和 `--bg` 不是简单补 handler,而是需要补启动/附着元数据设计
|
||||
|
||||
### 代码范围
|
||||
|
||||
- 修改 `src/cli/bg.ts`
|
||||
- 修改 `src/utils/concurrentSessions.ts` 以便后续 attach/--bg 扩展
|
||||
- 修改 `src/utils/taskSummary.ts`
|
||||
- 复用:
|
||||
`src/utils/sessionStorage.ts`
|
||||
`src/utils/udsClient.ts`
|
||||
|
||||
### 验证
|
||||
|
||||
1. `ps` 能列出 live sessions
|
||||
2. `logs <sessionId|pid|name>` 能输出对应日志
|
||||
3. `kill <sessionId|pid|name>` 能结束目标 session
|
||||
|
||||
### 风险
|
||||
|
||||
- `attach` / `--bg` 第二阶段需要 tmux 元数据设计
|
||||
- Windows 下 tmux 路径需要明确降级策略
|
||||
|
||||
### 工作量判断
|
||||
|
||||
- `ps/logs/kill` 中等
|
||||
- `attach/--bg` 明显更大,应分阶段
|
||||
|
||||
## 3. `TEMPLATES`
|
||||
|
||||
### 现状
|
||||
|
||||
- 命令入口只有 fast-path:
|
||||
`src/entrypoints/cli.tsx`
|
||||
- handler 是空的:
|
||||
`src/cli/handlers/templateJobs.ts`
|
||||
- `markdownConfigLoader` 已把 `templates` 纳入配置目录:
|
||||
`src/utils/markdownConfigLoader.ts`
|
||||
- `query / stopHooks` 已预留 job classifier 链路:
|
||||
`src/query/stopHooks.ts`
|
||||
- `jobs/classifier.ts` 仍是 stub:
|
||||
`src/jobs/classifier.ts`
|
||||
|
||||
### 目标
|
||||
|
||||
- 把 `new / list / reply` 做成可用的模板任务系统。
|
||||
- 第一阶段不碰复杂的自动分类与自动执行。
|
||||
|
||||
### MVP 方案
|
||||
|
||||
- 模板来源:
|
||||
`.claude/templates/*.md`
|
||||
- 模板格式:
|
||||
复用现有 markdown + frontmatter 解析,不另外设计 DSL
|
||||
- `list`
|
||||
- 列出所有模板
|
||||
- 显示模板名、description、路径
|
||||
- `new <template> [args...]`
|
||||
- 解析模板
|
||||
- 在 `~/.claude/jobs/<job-id>/` 下创建 job 目录
|
||||
- 写入 `template.md`、`input.txt`、`state.json`
|
||||
- 返回 job id 与目录
|
||||
- `reply <job-id> <text>`
|
||||
- 将回复写入 `replies.jsonl` 或 `input.txt`
|
||||
- 更新 `state.json`
|
||||
|
||||
### Phase 2
|
||||
|
||||
- 恢复 `src/jobs/classifier.ts`
|
||||
- 让带 `CLAUDE_JOB_DIR` 的 job session 在 turn 完成后自动更新 `state.json`
|
||||
- 再决定是否补自动 job runner
|
||||
|
||||
### 为什么要拆
|
||||
|
||||
- 当前证据表明这是“template job commands”,不是单纯模板列表
|
||||
- 但自动 job 运行链路没有足够现成实现,先做文件系统 job lifecycle 更稳
|
||||
|
||||
### 代码范围
|
||||
|
||||
- 修改 [src/cli/handlers/templateJobs.ts](</e:/Source_code/Claude-code-bast/src/cli/handlers/templateJobs.ts:1>)
|
||||
- 新增 `src/jobs/state.ts`
|
||||
- 新增 `src/jobs/templates.ts`
|
||||
- Phase 2 再改 [src/jobs/classifier.ts](</e:/Source_code/Claude-code-bast/src/jobs/classifier.ts:1>)
|
||||
|
||||
### 验证
|
||||
|
||||
1. `list` 能列出 `.claude/templates`
|
||||
2. `new` 能创建 job 目录和状态文件
|
||||
3. `reply` 能更新 job 内容和状态
|
||||
4. Phase 2 再验证 classifier 写状态
|
||||
|
||||
### 风险
|
||||
|
||||
- frontmatter schema 需要先定义最小字段集
|
||||
- 一旦扩展到“自动运行 job”,范围会明显膨胀
|
||||
|
||||
### 工作量判断
|
||||
|
||||
- MVP 中等
|
||||
- 完整 job 系统偏大
|
||||
|
||||
## 4. `assistant [sessionId]`
|
||||
|
||||
### 现状
|
||||
|
||||
- attach 主流程其实已经存在:
|
||||
[src/main.tsx](</e:/Source_code/Claude-code-bast/src/main.tsx:4708>)
|
||||
- 远端 viewer 所需基础模块已存在:
|
||||
[src/remote/RemoteSessionManager.ts](</e:/Source_code/Claude-code-bast/src/remote/RemoteSessionManager.ts:1>)
|
||||
[src/hooks/useAssistantHistory.ts](</e:/Source_code/Claude-code-bast/src/hooks/useAssistantHistory.ts:1>)
|
||||
[src/assistant/sessionHistory.ts](</e:/Source_code/Claude-code-bast/src/assistant/sessionHistory.ts:1>)
|
||||
- 真正 stub 的主要是:
|
||||
[src/assistant/sessionDiscovery.ts](</e:/Source_code/Claude-code-bast/src/assistant/sessionDiscovery.ts:1>)
|
||||
[src/assistant/AssistantSessionChooser.ts](</e:/Source_code/Claude-code-bast/src/assistant/AssistantSessionChooser.ts:1>)
|
||||
[src/commands/assistant/assistant.ts](</e:/Source_code/Claude-code-bast/src/commands/assistant/assistant.ts:7>)
|
||||
[src/assistant/index.ts](</e:/Source_code/Claude-code-bast/src/assistant/index.ts:1>)
|
||||
|
||||
### 目标
|
||||
|
||||
- 不一次性恢复整个 KAIROS 助手系统。
|
||||
- 先做“明确 sessionId 的 viewer attach 可用”,再逐步补 discovery / chooser / install。
|
||||
|
||||
### Phase 4A:MVP
|
||||
|
||||
- 只支持 `claude assistant <sessionId>`
|
||||
- 对 `claude assistant` 无参数模式,先返回明确提示:
|
||||
- 当前版本需要显式 `sessionId`
|
||||
- discovery 尚未启用
|
||||
- 这样可以直接复用现有 attach 分支,不必先恢复 chooser/install wizard
|
||||
|
||||
### Phase 4B
|
||||
|
||||
- 恢复 `discoverAssistantSessions()`
|
||||
- 数据来源优先复用现有 sessions / bridge / teleport API,而不是新协议
|
||||
- 让 `claude assistant` 无参数时能拿到候选 session 列表
|
||||
|
||||
### Phase 4C
|
||||
|
||||
- 恢复 `AssistantSessionChooser`
|
||||
- 多 session 时可交互选择
|
||||
|
||||
### Phase 4D
|
||||
|
||||
- 最后考虑 install wizard 辅助函数
|
||||
- 这部分属于“没有 session 时如何引导”,不是 attach 核心路径
|
||||
|
||||
### 为什么要拆
|
||||
|
||||
- attach 渲染层与远端消息通道大部分已经在
|
||||
- 真正缺的是“如何发现目标 session”和“如何交互选择”
|
||||
- 如果把 `src/assistant/index.ts` 的整套 KAIROS 正常模式也一起拉进来,范围会失控
|
||||
|
||||
### 代码范围
|
||||
|
||||
- Phase 4A:
|
||||
- [src/main.tsx](</e:/Source_code/Claude-code-bast/src/main.tsx:4708>)
|
||||
- [src/commands/assistant/index.ts](</e:/Source_code/Claude-code-bast/src/commands/assistant/index.ts:1>)
|
||||
- Phase 4B:
|
||||
- [src/assistant/sessionDiscovery.ts](</e:/Source_code/Claude-code-bast/src/assistant/sessionDiscovery.ts:1>)
|
||||
- Phase 4C:
|
||||
- [src/assistant/AssistantSessionChooser.ts](</e:/Source_code/Claude-code-bast/src/assistant/AssistantSessionChooser.ts:1>)
|
||||
- Phase 4D:
|
||||
- [src/commands/assistant/assistant.ts](</e:/Source_code/Claude-code-bast/src/commands/assistant/assistant.ts:7>)
|
||||
|
||||
### 验证
|
||||
|
||||
1. `claude assistant <sessionId>` 能进入 remote viewer
|
||||
2. 历史懒加载工作正常
|
||||
3. 无参数模式先给出明确提示
|
||||
4. 后续阶段再分别验证 discovery / chooser / install
|
||||
|
||||
### 风险
|
||||
|
||||
- 这是四项里范围最大的
|
||||
- 一旦把 KAIROS 正常模式整体拉入,会从“viewer attach”膨胀成“完整 assistant mode 恢复”
|
||||
|
||||
### 工作量判断
|
||||
|
||||
- Phase 4A 中等
|
||||
- 4A-4D 全做完很大
|
||||
|
||||
## 建议执行顺序
|
||||
|
||||
1. `claude daemon status` / `claude daemon stop`
|
||||
2. `BG_SESSIONS` 先做 `ps/logs/kill`
|
||||
3. `TEMPLATES` 先做 job 文件系统 MVP
|
||||
4. `assistant [sessionId]` 先做显式 sessionId attach,再补 discovery/chooser/install
|
||||
|
||||
## 简短结论
|
||||
|
||||
这四项里,最适合立刻实现的是 `daemon status/stop`。`BG_SESSIONS` 和 `TEMPLATES` 适合按 MVP 先补 handler 与文件系统闭环。`assistant [sessionId]` 不能整块硬上,应该按“attach → discovery → chooser → install”拆开恢复。
|
||||
@@ -1,167 +0,0 @@
|
||||
# TEAMMEM — 团队共享记忆
|
||||
|
||||
> Feature Flag: `FEATURE_TEAMMEM=1`
|
||||
> 实现状态:完整可用(需要 Anthropic OAuth + GitHub remote)
|
||||
> 引用数:51
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
TEAMMEM 实现基于 GitHub 仓库的团队共享记忆系统。`memory/team/` 目录中的文件双向同步到 Anthropic 服务器,团队所有认证成员可共享项目知识。
|
||||
|
||||
### 核心特性
|
||||
|
||||
- **增量同步**:只上传内容哈希变化的文件(delta upload)
|
||||
- **冲突解决**:基于 ETag 的乐观锁 + 412 冲突重试
|
||||
- **密钥扫描**:上传前检测并跳过包含密钥的文件(PSR M22174)
|
||||
- **路径穿越防护**:所有写入路径验证在 `memory/team/` 边界内
|
||||
- **分批上传**:自动拆分超过 200KB 的 PUT 请求避免网关拒绝
|
||||
|
||||
## 二、用户交互
|
||||
|
||||
### 同步行为
|
||||
|
||||
| 事件 | 行为 |
|
||||
|------|------|
|
||||
| 项目启动 | 自动 pull 团队记忆到 `memory/team/` |
|
||||
| 本地文件编辑 | watcher 检测变更,自动 push |
|
||||
| 服务端更新 | 下次 pull 时覆盖本地(server-wins) |
|
||||
| 密钥检测 | 跳过该文件,记录警告,不阻止其他文件同步 |
|
||||
|
||||
### API 端点
|
||||
|
||||
```
|
||||
GET /api/claude_code/team_memory?repo={owner/repo} → 完整数据 + entryChecksums
|
||||
GET /api/claude_code/team_memory?repo={owner/repo}&view=hashes → 仅 checksums(冲突解决用)
|
||||
PUT /api/claude_code/team_memory?repo={owner/repo} → 上传 entries(upsert 语义)
|
||||
```
|
||||
|
||||
## 三、实现架构
|
||||
|
||||
### 3.1 同步状态
|
||||
|
||||
```ts
|
||||
type SyncState = {
|
||||
lastKnownChecksum: string | null // ETag 条件请求
|
||||
serverChecksums: Map<string, string> // sha256:<hex> 逐文件哈希
|
||||
serverMaxEntries: number | null // 从 413 学习的服务端容量
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Pull 流程(Server → Local)
|
||||
|
||||
文件:`src/services/teamMemorySync/index.ts:770-867`
|
||||
|
||||
```
|
||||
pullTeamMemory(state)
|
||||
│
|
||||
▼
|
||||
检查 OAuth + GitHub remote
|
||||
│
|
||||
▼
|
||||
fetchTeamMemory(state, repo, etag)
|
||||
├── 304 Not Modified → 返回(无变化)
|
||||
├── 404 → 返回(服务端无数据)
|
||||
└── 200 → 解析 TeamMemoryData
|
||||
│
|
||||
▼
|
||||
刷新 serverChecksums(per-key hashes)
|
||||
│
|
||||
▼
|
||||
writeRemoteEntriesToLocal(entries)
|
||||
├── 路径穿越验证(validateTeamMemKey)
|
||||
├── 文件大小检查(> 250KB 跳过)
|
||||
├── 内容比较(相同则跳过写入)
|
||||
└── 并行写入(Promise.all)
|
||||
```
|
||||
|
||||
### 3.3 Push 流程(Local → Server)
|
||||
|
||||
文件:`src/services/teamMemorySync/index.ts:889-1146`
|
||||
|
||||
```
|
||||
pushTeamMemory(state)
|
||||
│
|
||||
▼
|
||||
readLocalTeamMemory(maxEntries)
|
||||
├── 递归扫描 memory/team/ 目录
|
||||
├── 跳过超大文件(> 250KB)
|
||||
├── 密钥扫描(scanForSecrets,gitleaks 规则)
|
||||
└── 按 serverMaxEntries 截断(如果已知)
|
||||
│
|
||||
▼
|
||||
计算 delta = 本地文件 - serverChecksums
|
||||
(只包含哈希不同的文件)
|
||||
│
|
||||
▼
|
||||
batchDeltaByBytes(delta)
|
||||
(拆分为 ≤200KB 的批次)
|
||||
│
|
||||
▼
|
||||
逐批 uploadTeamMemory(state, repo, batch, etag)
|
||||
├── 200 成功 → 更新 serverChecksums
|
||||
├── 412 冲突 → fetchTeamMemoryHashes() 刷新 checksums
|
||||
│ → 重试 delta 计算(最多 2 次)
|
||||
└── 413 超容量 → 学习 serverMaxEntries
|
||||
```
|
||||
|
||||
### 3.4 密钥扫描
|
||||
|
||||
文件:`src/services/teamMemorySync/secretScanner.ts`
|
||||
|
||||
使用 gitleaks 规则模式扫描文件内容。检测到密钥时:
|
||||
- 跳过该文件(不上传)
|
||||
- 记录 `tengu_team_mem_secret_skipped` 事件(仅记录规则 ID,不记录值)
|
||||
- 不阻止其他文件同步
|
||||
|
||||
### 3.5 文件监视
|
||||
|
||||
文件:`src/services/teamMemorySync/watcher.ts`
|
||||
|
||||
监视 `memory/team/` 目录变更,触发自动 push。抑制由 pull 写入引起的假变更。
|
||||
|
||||
### 3.6 路径安全
|
||||
|
||||
文件:`src/memdir/teamMemPaths.ts`
|
||||
|
||||
- `validateTeamMemKey(relPath)` — 验证相对路径不超出 `memory/team/` 边界
|
||||
- `getTeamMemPath()` — 返回 team memory 根目录路径
|
||||
|
||||
## 四、关键设计决策
|
||||
|
||||
1. **Server-wins on pull, Local-wins on push**:pull 时服务端内容覆盖本地;push 时本地编辑覆盖服务端。本地用户正在编辑,不应被静默丢弃
|
||||
2. **Delta upload**:只上传哈希变化的条目,节省带宽。首次 push 为全量,后续增量
|
||||
3. **分批 PUT**:单次 PUT ≤200KB,避免 API 网关(~256-512KB)拒绝。每批独立 upsert,部分失败不影响已提交批次
|
||||
4. **密钥扫描在上传前**:PSR M22174 要求密钥永不离开本机。扫描在 `readLocalTeamMemory` 中执行,密钥文件不进入上传集
|
||||
5. **ETag 乐观锁**:push 使用 `If-Match` header。412 时 probe `?view=hashes`(只获取 checksums,不下载内容),刷新后重试
|
||||
6. **服务端容量动态学习**:不假设客户端容量上限,从 413 的 `extra_details.max_entries` 学习
|
||||
|
||||
## 五、使用方式
|
||||
|
||||
```bash
|
||||
# 启用 feature
|
||||
FEATURE_TEAMMEM=1 bun run dev
|
||||
|
||||
# 前提条件:
|
||||
# 1. 已通过 Anthropic OAuth 登录
|
||||
# 2. 项目有 GitHub remote(git remote -v 显示 origin)
|
||||
# 3. memory/team/ 目录自动创建
|
||||
```
|
||||
|
||||
## 六、外部依赖
|
||||
|
||||
| 依赖 | 说明 |
|
||||
|------|------|
|
||||
| Anthropic OAuth | first-party 认证 |
|
||||
| GitHub Remote | `getGithubRepo()` 获取 `owner/repo` 作为同步 scope |
|
||||
| Team Memory API | `/api/claude_code/team_memory` 端点 |
|
||||
|
||||
## 七、文件索引
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `src/services/teamMemorySync/index.ts` | 1257 | 核心同步逻辑(pull/push/sync) |
|
||||
| `src/services/teamMemorySync/watcher.ts` | — | 文件监视 + 自动同步触发 |
|
||||
| `src/services/teamMemorySync/secretScanner.ts` | — | gitleaks 密钥扫描 |
|
||||
| `src/services/teamMemorySync/types.ts` | — | Zod schema + 类型定义 |
|
||||
| `src/services/teamMemorySync/teamMemSecretGuard.ts` | — | 密钥防护辅助 |
|
||||
| `src/memdir/teamMemPaths.ts` | — | 路径验证 + 目录管理 |
|
||||
@@ -1,37 +0,0 @@
|
||||
# Tier 3 — 纯 Stub / N/A 低优先级 Feature 概览
|
||||
|
||||
> 本文档汇总所有 Tier 3 feature。这些功能要么是纯 Stub(所有函数返回空值),
|
||||
> 要么是 Anthropic 内部基础设施(N/A),要么是引用量极低的辅助功能。
|
||||
|
||||
## 概览
|
||||
|
||||
| Feature | 引用 | 状态 | 类别 | 简要说明 |
|
||||
|---------|------|------|------|---------|
|
||||
| CHICAGO_MCP | 16 | 已实现 | 工具 | Computer Use + Chrome MCP 控制(build 默认启用) |
|
||||
| MONITOR_TOOL | 13 | 已实现 | 工具 | 后台监控工具,持续监视 shell 输出(build 默认启用) |
|
||||
| BG_SESSIONS | 11 | 部分实现 | 会话管理 | 后台会话注册/清理已实现,任务摘要是 stub(dev 默认启用) |
|
||||
| SHOT_STATS | 10 | 已实现 | 统计 | API 调用统计面板(build 默认启用) |
|
||||
| EXTRACT_MEMORIES | 7 | 已实现 | 记忆 | 自动记忆提取(build 默认启用,受 GrowthBook 门控) |
|
||||
| TEMPLATES | 6 | 部分实现 | 项目管理 | 项目/提示模板系统(dev 默认启用) |
|
||||
| LODESTONE | 6 | 已实现 | 深度链接 | URL 协议处理器(build 默认启用) |
|
||||
|
||||
## 单引用 Feature(40+ 个)
|
||||
|
||||
以下 feature 各只有 1 处引用,多为内部标记或实验性功能:
|
||||
|
||||
UNATTENDED_RETRY, ULTRATHINK, TORCH, SLOW_OPERATION_LOGGING, SKILL_IMPROVEMENT,
|
||||
SELF_HOSTED_RUNNER, RUN_SKILL_GENERATOR, PERFETTO_TRACING, NATIVE_CLIENT_ATTESTATION,
|
||||
KAIROS_DREAM(见 kairos.md), IS_LIBC_MUSL, IS_LIBC_GLIBC, DUMP_SYSTEM_PROMPT,
|
||||
COMPACTION_REMINDERS, CCR_REMOTE_SETUP, BYOC_ENVIRONMENT_RUNNER, BUILTIN_EXPLORE_PLAN_AGENTS,
|
||||
BUILDING_CLAUDE_APPS, ANTI_DISTILLATION_CC, AGENT_TRIGGERS, ABLATION_BASELINE
|
||||
|
||||
## 优先级说明
|
||||
|
||||
这些 feature 被列为 Tier 3 的原因:
|
||||
|
||||
1. **已实现但影响范围小**(CHICAGO_MCP, LODESTONE, SHOT_STATS, EXTRACT_MEMORIES, MONITOR_TOOL):已在 build/dev 默认启用,主要作为其他功能的基础设施
|
||||
2. **部分实现**(BG_SESSIONS, TEMPLATES):核心注册已实现,但部分功能如任务摘要仍是 stub
|
||||
3. **辅助功能**(STREAMLINED_OUTPUT, HOOK_PROMPTS):影响范围小
|
||||
4. **CCR 系列**:依赖远程控制基础设施,需要 BRIDGE_MODE 先完善
|
||||
|
||||
如需深入了解某个 Tier 3 feature,可以在代码库中搜索 `feature('FEATURE_NAME')` 查看具体使用场景。
|
||||
@@ -1,198 +0,0 @@
|
||||
# TOKEN_BUDGET — Token 预算自动持续模式
|
||||
|
||||
> Feature Flag: `FEATURE_TOKEN_BUDGET=1`
|
||||
> 实现状态:完整可用
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
TOKEN_BUDGET 让用户在 prompt 中指定一个 output token 预算目标(如 `+500k`、`spend 2M tokens`),Claude 会**自动持续工作**直到达到目标,无需用户反复按回车催促继续。
|
||||
|
||||
适用于大型重构、批量修改、大规模代码生成等需要多轮工具调用的长任务。
|
||||
|
||||
## 二、用户交互
|
||||
|
||||
### 语法
|
||||
|
||||
| 格式 | 示例 | 说明 |
|
||||
|------|------|------|
|
||||
| 简写(开头) | `+500k` | 输入开头直接写 |
|
||||
| 简写(结尾) | `帮我重构这个模块 +2m` | 输入末尾追加 |
|
||||
| 完整语法 | `spend 2M tokens` 或 `use 1B tokens` | 自然语言嵌入 |
|
||||
|
||||
单位支持:`k`(千)、`m`(百万)、`b`(十亿),大小写不敏感。
|
||||
|
||||
### UI 反馈
|
||||
|
||||
- **输入框高亮**:输入包含预算语法时,对应文字会被高亮标记(`PromptInput.tsx` 通过 `findTokenBudgetPositions` 计算)
|
||||
- **Spinner 进度**:底部 spinner 显示实时进度,格式如:
|
||||
- 未完成:`Target: 125,000 / 500,000 (25%) · ~2m 30s`
|
||||
- 已完成:`Target: 510,000 used (500,000 min ✓)`
|
||||
- 包含 ETA(基于当前 token 产出速率计算)
|
||||
|
||||
## 三、实现架构
|
||||
|
||||
### 数据流
|
||||
|
||||
```
|
||||
用户输入 "+500k"
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────┐
|
||||
│ parseTokenBudget() │ src/utils/tokenBudget.ts
|
||||
│ 正则解析 → 500,000 │
|
||||
└────────┬────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────┐
|
||||
│ REPL.tsx │ 提交时调用
|
||||
│ snapshotOutputTokens │ snapshotOutputTokensForTurn(500000)
|
||||
│ ForTurn(500000) │ 记录 turn 起始 token 数 + 预算
|
||||
└────────┬────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────┐
|
||||
│ query.ts 主循环 │ 每轮结束后检查
|
||||
│ checkTokenBudget() │ 当前 output tokens vs 预算
|
||||
└────────┬────────────────┘
|
||||
│
|
||||
┌────┴─────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
continue stop
|
||||
(未达 90%) (已达 90% 或收益递减)
|
||||
│ │
|
||||
▼ ▼
|
||||
注入 nudge 正常结束
|
||||
消息继续 发送完成事件
|
||||
```
|
||||
|
||||
### 核心模块
|
||||
|
||||
#### 1. 解析层 — `src/utils/tokenBudget.ts`
|
||||
|
||||
三个正则表达式解析用户输入:
|
||||
|
||||
```
|
||||
SHORTHAND_START_RE = /^\s*\+(\d+(?:\.\d+)?)\s*(k|m|b)\b/i // "+500k" 在开头
|
||||
SHORTHAND_END_RE = /\s\+(\d+(?:\.\d+)?)\s*(k|m|b)\s*[.!?]?\s*$/i // "+2m" 在结尾
|
||||
VERBOSE_RE = /\b(?:use|spend)\s+(\d+(?:\.\d+)?)\s*(k|m|b)\s*tokens?\b/i // "spend 2M tokens"
|
||||
```
|
||||
|
||||
- `parseTokenBudget(text)` — 提取预算数值,返回 `number | null`
|
||||
- `findTokenBudgetPositions(text)` — 返回匹配位置数组,用于输入框高亮
|
||||
- `getBudgetContinuationMessage(pct, turnTokens, budget)` — 生成继续消息
|
||||
|
||||
#### 2. 状态层 — `src/bootstrap/state.ts`
|
||||
|
||||
模块级单例变量追踪当前 turn 的预算状态:
|
||||
|
||||
```
|
||||
outputTokensAtTurnStart — 本 turn 开始时的累计 output token 数
|
||||
currentTurnTokenBudget — 本 turn 的预算目标(null 表示无预算)
|
||||
budgetContinuationCount — 本 turn 已自动续接的次数
|
||||
```
|
||||
|
||||
关键函数:
|
||||
- `getTotalOutputTokens()` — 从 `STATE.modelUsage` 汇总所有模型的 output tokens
|
||||
- `getTurnOutputTokens()` — `getTotalOutputTokens() - outputTokensAtTurnStart`
|
||||
- `snapshotOutputTokensForTurn(budget)` — 重置 turn 起点,设置新预算
|
||||
- `getCurrentTurnTokenBudget()` — 返回当前预算
|
||||
|
||||
#### 3. 决策层 — `src/query/tokenBudget.ts`
|
||||
|
||||
`checkTokenBudget(tracker, agentId, budget, globalTurnTokens)` 做出 continue/stop 决策:
|
||||
|
||||
**继续条件**:
|
||||
- 不在子 agent 中(`agentId` 为空)
|
||||
- 预算存在且 > 0
|
||||
- 当前 token 未达预算的 **90%**
|
||||
- 非收益递减(连续 3 轮 nudge 后,每轮新增 < 500 tokens)
|
||||
|
||||
**停止条件**:
|
||||
- 达到预算 90%
|
||||
- 收益递减(模型已经"做不动了")
|
||||
- 子 agent 模式下直接跳过
|
||||
|
||||
**收益递减检测**:`continuationCount >= 3` 且最近两次 nudge 的 delta 都 < 500 tokens。
|
||||
|
||||
#### 4. 主循环集成 — `src/query.ts`
|
||||
|
||||
```
|
||||
query() 函数内:
|
||||
1. 创建 budgetTracker = createBudgetTracker()
|
||||
2. 进入 while 循环
|
||||
3. 每轮结束后调用 checkTokenBudget()
|
||||
4. decision.action === 'continue' 时:
|
||||
- 注入 meta user message(nudge)
|
||||
- continue 回到循环顶部
|
||||
5. decision.action === 'stop' 时:
|
||||
- 记录完成事件(含 diminishingReturns 标记)
|
||||
- 正常返回
|
||||
```
|
||||
|
||||
#### 5. UI 层
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `components/PromptInput/PromptInput.tsx:534` | 输入框中高亮预算语法 |
|
||||
| `components/Spinner.tsx:319-338` | spinner 显示进度百分比 + ETA |
|
||||
| `screens/REPL.tsx:2897` | 提交时解析预算并快照 |
|
||||
| `screens/REPL.tsx:2138` | 用户取消时清除预算 |
|
||||
| `screens/REPL.tsx:2963` | turn 结束时捕获预算信息用于显示 |
|
||||
|
||||
#### 6. 系统提示 — `src/constants/prompts.ts:538-551`
|
||||
|
||||
注入 `token_budget` section:
|
||||
|
||||
> "When the user specifies a token target (e.g., '+500k', 'spend 2M tokens', 'use 1B tokens'), your output token count will be shown each turn. Keep working until you approach the target — plan your work to fill it productively. The target is a hard minimum, not a suggestion. If you stop early, the system will automatically continue you."
|
||||
|
||||
注意:这段 prompt **无条件缓存**(不随预算开关变化),因为 "When the user specifies..." 的措辞在没有预算时是空操作。
|
||||
|
||||
#### 7. API 附件 — `src/utils/attachments.ts:3830-3845`
|
||||
|
||||
每轮 API 调用附带 `output_token_usage` attachment:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "output_token_usage",
|
||||
"turn": 125000, // 本 turn 产出
|
||||
"session": 350000, // 会话总产出
|
||||
"budget": 500000 // 预算目标
|
||||
}
|
||||
```
|
||||
|
||||
让模型能看到自己的进度。
|
||||
|
||||
## 四、关键设计决策
|
||||
|
||||
1. **90% 阈值而非 100%**:在 `COMPLETION_THRESHOLD = 0.9` 处停止,避免最后一轮 nudge 产生远超预算的 token
|
||||
2. **收益递减保护**:连续 3 轮 nudge 后如果每轮产出 < 500 tokens,判定模型已无实质进展,提前终止
|
||||
3. **子 agent 豁免**:AgentTool 内部的子任务不做预算检查,避免子任务重复触发续接
|
||||
4. **无条件缓存系统提示**:预算 prompt 始终注入(不随预算变化 toggle),避免每次切换预算导致 ~20K token 的 cache miss
|
||||
5. **用户取消清预算**:按 Escape 取消时调用 `snapshotOutputTokensForTurn(null)`,防止残留预算触发续接
|
||||
|
||||
## 五、使用方式
|
||||
|
||||
```bash
|
||||
# 启用 feature
|
||||
FEATURE_TOKEN_BUDGET=1 bun run dev
|
||||
|
||||
# 在 prompt 中使用
|
||||
> +500k 重构所有测试文件
|
||||
> spend 2M tokens 把这个项目从 JS 迁移到 TS
|
||||
> 帮我写完整的 CRUD 模块 +1m
|
||||
```
|
||||
|
||||
## 六、文件索引
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `src/utils/tokenBudget.ts` | 73 | 正则解析 + 位置查找 + 续接消息生成 |
|
||||
| `src/query/tokenBudget.ts` | 93 | 预算追踪器 + continue/stop 决策 |
|
||||
| `src/bootstrap/state.ts:724-743` | 20 | turn 级 token 快照状态 |
|
||||
| `src/constants/prompts.ts:538-551` | 14 | 系统提示注入 |
|
||||
| `src/utils/attachments.ts:3830-3844` | 17 | API attachment 附加 |
|
||||
| `src/query.ts:280,1311-1358` | 48 | 主循环集成 |
|
||||
| `src/screens/REPL.tsx:2897,2963,2138` | 20 | REPL 提交/完成/取消处理 |
|
||||
| `src/components/Spinner.tsx:319-338` | 20 | 进度条 UI |
|
||||
| `src/components/PromptInput/PromptInput.tsx:534` | 1 | 输入高亮 |
|
||||
@@ -1,3 +1,9 @@
|
||||
---
|
||||
title: "Langfuse 监控集成"
|
||||
description: "Agent loop 实时监控,可视化每次 API 调用、token 消耗、工具执行链路,可一键转化为训练数据集。"
|
||||
keywords: ["Langfuse", "OpenTelemetry", "LLM 追踪", "可观测性", "数据脱敏"]
|
||||
---
|
||||
|
||||
# Langfuse 监控集成
|
||||
|
||||
> 实现状态:已完成,通过环境变量启用
|
||||
@@ -1,161 +0,0 @@
|
||||
# TREE_SITTER_BASH — Bash AST 解析
|
||||
|
||||
> Feature Flag: `FEATURE_TREE_SITTER_BASH=1`
|
||||
> 实现状态:完整可用(纯 TypeScript 实现,~7000+ 行)
|
||||
> 引用数:3
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
TREE_SITTER_BASH 启用一个完整的 Bash AST 解析器,用于安全验证 Bash 命令。它用完整的树遍历安全分析器取代了旧的基于正则表达式的 shell-quote 解析器。关键属性是 **fail-closed**:任何无法识别的内容都被归类为 `too-complex` 并需要用户批准。
|
||||
|
||||
### 关联 Feature
|
||||
|
||||
| Feature | 说明 |
|
||||
|---------|------|
|
||||
| `TREE_SITTER_BASH` | 激活用于权限检查的 AST 解析器 |
|
||||
| `TREE_SITTER_BASH_SHADOW` | Shadow/观测模式:运行解析器但丢弃结果,仅记录遥测 |
|
||||
|
||||
## 二、安全架构
|
||||
|
||||
### 2.1 Fail-Closed 设计
|
||||
|
||||
核心设计使用 **allowlist** 遍历模式:
|
||||
|
||||
- `walkArgument()` 只处理已知安全的节点类型(`word`、`number`、`raw_string`、`string`、`concatenation`、`arithmetic_expansion`、`simple_expansion`)
|
||||
- 任何未知节点类型 → `tooComplex()` → 需要用户批准
|
||||
- 解析器加载但失败(超时/节点预算/panic)→ 返回 `PARSE_ABORTED` 符号(区别于"模块未加载")
|
||||
|
||||
### 2.2 解析结果
|
||||
|
||||
```ts
|
||||
parseForSecurity(cmd) 返回:
|
||||
{ kind: 'simple', commands: SimpleCommand[] } // 可静态分析
|
||||
{ kind: 'too-complex', reason, nodeType } // 需要用户批准
|
||||
{ kind: 'parse-unavailable' } // 解析器未加载
|
||||
```
|
||||
|
||||
### 2.3 安全检查层次
|
||||
|
||||
```
|
||||
parseForSecurity(cmd)
|
||||
│
|
||||
▼
|
||||
parseCommandRaw(cmd) → AST root node
|
||||
│
|
||||
▼
|
||||
预检查:控制字符、Unicode 空白、反斜杠+空白、
|
||||
zsh ~[ ] 语法、zsh =cmd 展开、大括号+引号混淆
|
||||
│
|
||||
▼
|
||||
walkProgram(root) → collectCommands(root, commands, varScope)
|
||||
│
|
||||
├── 'command' → walkCommand()
|
||||
├── 'pipeline'/'list' → 结构性,递归子节点
|
||||
├── 'for_statement' → 跟踪循环变量为 VAR_PLACEHOLDER
|
||||
├── 'if/while' → 作用域隔离的分支
|
||||
├── 'subshell' → 作用域复制
|
||||
├── 'variable_assignment' → walkVariableAssignment()
|
||||
├── 'declaration_command' → 验证 declare/export flags
|
||||
├── 'test_command' → walk test expressions
|
||||
└── 其他 → tooComplex()
|
||||
│
|
||||
▼
|
||||
checkSemantics(commands)
|
||||
├── EVAL_LIKE_BUILTINS(eval, source, exec, trap...)
|
||||
├── ZSH_DANGEROUS_BUILTINS(zmodload, emulate...)
|
||||
├── SUBSCRIPT_EVAL_FLAGS(test -v, printf -v, read -a)
|
||||
├── Shell keywords as argv[0](误解析检测)
|
||||
├── /proc/*/environ 访问
|
||||
├── jq system() 和危险 flags
|
||||
└── 包装器剥离(time, nohup, timeout, nice, env, stdbuf)
|
||||
```
|
||||
|
||||
## 三、实现架构
|
||||
|
||||
### 3.1 核心模块
|
||||
|
||||
| 模块 | 文件 | 行数 | 职责 |
|
||||
|------|------|------|------|
|
||||
| 门控入口 | `src/utils/bash/parser.ts` | ~110 | `parseCommand()`、`parseCommandRaw()`、`ensureInitialized()` |
|
||||
| Bash 解析器 | `src/utils/bash/bashParser.ts` | 4437 | 纯 TS 词法分析 + 递归下降解析器 |
|
||||
| 安全分析器 | `src/utils/bash/ast.ts` | 2680 | 树遍历安全分析 + `parseForSecurity()` |
|
||||
| AST 分析辅助 | `src/utils/bash/treeSitterAnalysis.ts` | 507 | 引号上下文、复合结构、危险模式提取 |
|
||||
| 权限检查入口 | `src/tools/BashTool/bashPermissions.ts` | — | 集成 AST 结果到权限决策 |
|
||||
|
||||
### 3.2 Bash 解析器
|
||||
|
||||
文件:`src/utils/bash/bashParser.ts`(4437 行)
|
||||
|
||||
- 纯 TypeScript 实现(无原生依赖)
|
||||
- 生成与 tree-sitter-bash 兼容的 AST
|
||||
- 关键类型:`TsNode`(type、text、startIndex、endIndex、children)
|
||||
- 安全限制:`PARSE_TIMEOUT_MS = 50`、`MAX_NODES = 50_000` — 防止对抗性输入导致 OOM
|
||||
|
||||
### 3.3 安全分析器
|
||||
|
||||
文件:`src/utils/bash/ast.ts`(2680 行)
|
||||
|
||||
核心函数:
|
||||
|
||||
| 函数 | 职责 |
|
||||
|------|------|
|
||||
| `parseForSecurity(cmd)` | 顶层入口,返回 `simple/too-complex/parse-unavailable` |
|
||||
| `parseForSecurityFromAst(cmd, root)` | 接受预解析 AST |
|
||||
| `checkSemantics(commands)` | 后解析语义检查 |
|
||||
| `walkCommand()` | 提取 argv、envVars、redirects |
|
||||
| `walkArgument()` | Allowlist 参数遍历 |
|
||||
| `collectCommands()` | 递归收集所有命令 |
|
||||
|
||||
### 3.4 AST 分析辅助
|
||||
|
||||
文件:`src/utils/bash/treeSitterAnalysis.ts`(507 行)
|
||||
|
||||
| 函数 | 职责 |
|
||||
|------|------|
|
||||
| `extractQuoteContext()` | 识别单引号、双引号、ANSI-C 字符串、heredoc |
|
||||
| `extractCompoundStructure()` | 检测管道、子 shell、命令组 |
|
||||
| `hasActualOperatorNodes()` | 区分真实 `;`/`&&`/`||` 与转义形式 |
|
||||
| `extractDangerousPatterns()` | 检测命令替换、参数展开、heredocs |
|
||||
| `analyzeCommand()` | 单次遍历提取 |
|
||||
|
||||
### 3.5 Shadow 模式
|
||||
|
||||
`TREE_SITTER_BASH_SHADOW` 运行解析器但**从不影响权限决策**:
|
||||
|
||||
```ts
|
||||
// Shadow 模式:记录遥测,然后强制使用旧版路径
|
||||
astResult = { kind: 'parse-unavailable' }
|
||||
astRoot = null
|
||||
// 记录: available, astTooComplex, astSemanticFail, subsDiffer, ...
|
||||
```
|
||||
|
||||
记录 `tengu_tree_sitter_shadow` 事件,包含与旧版 `splitCommand()` 的对比数据。用于在不影响行为的情况下收集遥测。
|
||||
|
||||
## 四、关键设计决策
|
||||
|
||||
1. **Allowlist 遍历**:只处理已知安全的节点类型,未知类型直接 `tooComplex()`
|
||||
2. **PARSE_ABORTED 符号**:区分"解析器未加载"和"解析器加载但失败"。后者阻止回退旧版(旧版缺少 `EVAL_LIKE_BUILTINS` 检查)
|
||||
3. **变量作用域跟踪**:`VAR=value && cmd $VAR` 模式。静态值解析为真实字符串,`$()` 输出使用 `VAR_PLACEHOLDER`
|
||||
4. **PS4/IFS Allowlist**:PS4 赋值使用严格字符白名单 `[A-Za-z0-9 _+:.\/=\[\]-]`,只允许 `${VAR}` 引用
|
||||
5. **包装器剥离**:从 argv 前面剥离 `time/nohup/timeout/nice/env/stdbuf`,未知标志 → fail-closed
|
||||
6. **Shadow 安全性**:Shadow 模式**总是**强制 `astResult = { kind: 'parse-unavailable' }`,绝不影响权限
|
||||
|
||||
## 五、使用方式
|
||||
|
||||
```bash
|
||||
# 激活 AST 解析用于权限检查
|
||||
FEATURE_TREE_SITTER_BASH=1 bun run dev
|
||||
|
||||
# Shadow 模式(仅遥测,不影响行为)
|
||||
FEATURE_TREE_SITTER_BASH_SHADOW=1 bun run dev
|
||||
```
|
||||
|
||||
## 六、文件索引
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `src/utils/bash/parser.ts` | ~110 | 门控入口点 |
|
||||
| `src/utils/bash/bashParser.ts` | 4437 | 纯 TS bash 解析器 |
|
||||
| `src/utils/bash/ast.ts` | 2680 | 安全分析器(核心) |
|
||||
| `src/utils/bash/treeSitterAnalysis.ts` | 507 | AST 分析辅助 |
|
||||
| `packages/builtin-tools/src/tools/BashTool/bashPermissions.ts` | ~140 | 权限集成 + Shadow 遥测 |
|
||||
@@ -1,114 +0,0 @@
|
||||
# UDS_INBOX / pipes
|
||||
|
||||
## 概述
|
||||
|
||||
`UDS_INBOX` 现在不是一个“空壳 flag”,而是一套已经落地的本机 IPC 能力。但它同时承载了两层不同目标,必须拆开理解:
|
||||
|
||||
1. **UDS peer messaging**
|
||||
- 面向任意 Claude Code 进程。
|
||||
- 使用 `src/utils/udsMessaging.ts` 和 `src/utils/udsClient.ts`。
|
||||
- 对外入口是 `/peers` 和 `SendMessageTool` 的 `uds:<socket-path>` 地址。
|
||||
2. **pipes control plane**
|
||||
- 面向交互式 REPL 会话之间的主从协作。
|
||||
- 使用 `src/utils/pipeTransport.ts`、`src/utils/pipeRegistry.ts` 和 `src/screens/REPL.tsx` 中的内联 bootstrap。
|
||||
- 对外入口是 `/pipes`、`/attach`、`/detach`、`/send`、`/pipe-status`、`/history`、`/claim-main`。
|
||||
|
||||
这两层都依赖本机 socket,但职责不同。`/peers` 解决“找到其他会话并发消息”,`/pipes` 解决“把一个 REPL 变成另一个 REPL 的受控 worker”。
|
||||
|
||||
## 为什么要有单独的 `pipes`
|
||||
|
||||
单独的 `pipes` 层有三个实际理由:
|
||||
|
||||
1. **命名与角色模型不同**
|
||||
- UDS peer 层按 `messagingSocketPath` 寻址。
|
||||
- pipes 层按 `cli-xxxxxxxx` 会话名、`main/sub/master/slave` 角色和 `machineId` 注册表工作。
|
||||
2. **交互语义不同**
|
||||
- peer 层是通用消息投递。
|
||||
- pipes 层需要 attach、detach、历史收集、选择性广播、状态栏和 REPL 快捷键。
|
||||
3. **UI 集成不同**
|
||||
- peer 层主要服务工具调用。
|
||||
- pipes 层直接影响 REPL 提交路径和 PromptInput 页脚。
|
||||
|
||||
如果把两者硬合并,`SendMessageTool` 的通用寻址和 REPL 的主从控制会互相污染,命令语义也会变得混乱。
|
||||
|
||||
## 当前通信模型
|
||||
|
||||
### 1. UDS peer messaging
|
||||
|
||||
- 服务端:`src/utils/udsMessaging.ts`
|
||||
- 客户端:`src/utils/udsClient.ts`
|
||||
- 发现方式:读取 `~/.claude/sessions/*.json`
|
||||
- 地址方式:`uds:<socket-path>`
|
||||
- 传输方式:**本机 Unix socket / Windows named pipe**
|
||||
|
||||
这层是真正的“通用收件箱”。
|
||||
|
||||
### 2. pipes control plane
|
||||
|
||||
- 服务端/客户端:`src/utils/pipeTransport.ts`
|
||||
- 注册表:`src/utils/pipeRegistry.ts`
|
||||
- 生效入口:`src/screens/REPL.tsx`
|
||||
- 发现方式:扫描 `~/.claude/pipes/` + `registry.json`
|
||||
- 会话名:`cli-${sessionId.slice(0, 8)}`
|
||||
- 传输方式:**本机 Unix socket / Windows named pipe**
|
||||
|
||||
这层是真正的“主从 REPL 协调平面”。
|
||||
|
||||
## 关于“局域网通信”的事实
|
||||
|
||||
当前实现**不是**真正的局域网传输。
|
||||
|
||||
代码里虽然保存了这些字段:
|
||||
|
||||
- `localIp`
|
||||
- `hostname`
|
||||
- `machineId`
|
||||
- `mac`
|
||||
|
||||
但这些字段当前只用于:
|
||||
|
||||
1. 注册表展示
|
||||
2. main/sub 身份判定
|
||||
3. `claim-main` 的机器级归属切换
|
||||
4. 状态输出与排障信息
|
||||
|
||||
它们**没有**被用于创建 TCP/WebSocket 连接。真正的传输仍然是 `getPipePath(name)` 返回的本机 socket 路径。
|
||||
|
||||
所以目前更准确的描述应该是:
|
||||
|
||||
- `pipes` 支持 **本机多实例协作**
|
||||
- `registry` 带有 **机器身份元数据**
|
||||
- 但 **尚未实现跨机器局域网 transport**
|
||||
|
||||
如果未来要做真局域网版本,至少还需要:
|
||||
|
||||
1. TCP/WebSocket transport
|
||||
2. 认证与会话授权
|
||||
3. 发现与地址交换
|
||||
4. 超时、重连和安全边界
|
||||
|
||||
## 当前 REPL 行为
|
||||
|
||||
当前线上行为由 `src/screens/REPL.tsx` 的内联实现负责:
|
||||
|
||||
1. 启动时创建当前 REPL 的 pipe server
|
||||
2. 通过 `pipeRegistry` 判定 `main` / `sub`
|
||||
3. 处理 `attach_request` / `detach` / `prompt`
|
||||
4. 主实例心跳探测并维护 `slaves`
|
||||
5. `/pipes` 打开状态栏并维护选择器
|
||||
6. 提交普通消息时,仅向**已连接**的 selected pipes 广播
|
||||
|
||||
最近的收敛点:
|
||||
|
||||
- 过去遗留了一套未接线的 hook 方案
|
||||
- 当前已明确以 `REPL.tsx` 内联 bootstrap 为唯一生效实现
|
||||
- 选中但未连接的 pipe 不再导致本地处理被错误跳过
|
||||
|
||||
## 文档与代码对齐约定
|
||||
|
||||
后续关于 `UDS_INBOX` / `pipes` 的说明应遵守以下表述:
|
||||
|
||||
1. 默认称为“本机 IPC / 本机多实例协作”
|
||||
2. 不把 `localIp` / `hostname` 元数据表述成已完成的 LAN transport
|
||||
3. 明确区分 `/peers` 和 `/pipes` 的两层职责
|
||||
4. 以 `src/screens/REPL.tsx`、`src/utils/pipeTransport.ts`、`src/utils/pipeRegistry.ts` 为事实来源
|
||||
@@ -1,107 +0,0 @@
|
||||
# ULTRAPLAN — 增强规划
|
||||
|
||||
> Feature Flag: `FEATURE_ULTRAPLAN=1`
|
||||
> 实现状态:关键字检测完整,命令处理完整,CCR 远程会话完整
|
||||
> 引用数:10
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
ULTRAPLAN 在用户输入中检测 "ultraplan" 关键字时,自动进入增强计划模式。相比普通 plan mode,ultraplan 提供更深入的规划能力,支持本地和远程(CCR)执行。
|
||||
|
||||
### 触发方式
|
||||
|
||||
| 方式 | 行为 |
|
||||
|------|------|
|
||||
| 输入含 "ultraplan" 的文本 | 自动重定向到 `/ultraplan` 命令 |
|
||||
| `/ultraplan` 斜杠命令 | 直接执行 |
|
||||
| 彩虹高亮 | 输入框中 "ultraplan" 关键字彩虹动画 |
|
||||
|
||||
## 二、实现架构
|
||||
|
||||
### 2.1 模块状态
|
||||
|
||||
| 模块 | 文件 | 行数 | 状态 |
|
||||
|------|------|------|------|
|
||||
| 命令处理器 | `src/commands/ultraplan.tsx` | 525 | **完整** |
|
||||
| CCR 会话 | `src/utils/ultraplan/ccrSession.ts` | 349 | **完整** |
|
||||
| 关键字检测 | `src/utils/ultraplan/keyword.ts` | 127 | **完整** |
|
||||
| 嵌入式提示 | `src/utils/ultraplan/prompt.txt` | 1 | **完整** |
|
||||
| REPL 对话框 | `src/screens/REPL.tsx` | — | **布线** |
|
||||
| 关键字高亮 | `src/components/PromptInput/PromptInput.tsx` | — | **布线** |
|
||||
|
||||
### 2.2 关键字检测
|
||||
|
||||
文件:`src/utils/ultraplan/keyword.ts`(127 行)
|
||||
|
||||
`findUltraplanTriggerPositions(text)` 智能过滤:
|
||||
- 排除引号内的 "ultraplan"
|
||||
- 排除路径中的 "ultraplan"(如 `/path/to/ultraplan/`)
|
||||
- 排除斜杠命令以外的上下文
|
||||
- `replaceUltraplanKeyword(text)` 清理关键字
|
||||
|
||||
### 2.3 CCR 远程会话
|
||||
|
||||
文件:`src/utils/ultraplan/ccrSession.ts`(349 行)
|
||||
|
||||
`ExitPlanModeScanner` 类实现完整的事件状态机:
|
||||
- `pollForApprovedExitPlanMode()` — 3 秒轮询间隔
|
||||
- 超时处理和重试
|
||||
- 支持远程(teleport)和本地执行
|
||||
|
||||
### 2.4 数据流
|
||||
|
||||
```
|
||||
用户输入 "帮我 ultraplan 重构这个模块"
|
||||
│
|
||||
▼
|
||||
processUserInput 检测 "ultraplan"
|
||||
│
|
||||
▼
|
||||
重定向到 /ultraplan 命令
|
||||
│
|
||||
├── 本地执行 → EnterPlanMode
|
||||
│
|
||||
└── 远程执行 → teleportToRemote → CCR 会话
|
||||
│
|
||||
▼
|
||||
ExitPlanModeScanner 轮询
|
||||
│
|
||||
▼
|
||||
用户在远程审批 → 本地收到结果
|
||||
```
|
||||
|
||||
## 三、需要补全的内容
|
||||
|
||||
| 模块 | 说明 |
|
||||
|------|------|
|
||||
| `src/screens/REPL.tsx` 中的 UltraplanChoiceDialog / UltraplanLaunchDialog | 用户选择本地/远程执行的对话框组件 |
|
||||
| `src/commands/ultraplan/` | 空目录,可能是未合并的子命令结构 |
|
||||
|
||||
## 四、关键设计决策
|
||||
|
||||
1. **智能关键字过滤**:排除引号和路径中的 "ultraplan",避免误触发
|
||||
2. **本地/远程双模式**:支持本地 plan mode 和 CCR 远程会话
|
||||
3. **彩虹高亮反馈**:输入框中 "ultraplan" 关键字使用彩虹动画,暗示这是特殊功能
|
||||
4. **processUserInput 集成**:在用户输入处理管道中拦截,无缝重定向
|
||||
|
||||
## 五、使用方式
|
||||
|
||||
```bash
|
||||
# 启用 feature
|
||||
FEATURE_ULTRAPLAN=1 bun run dev
|
||||
|
||||
# 在 REPL 中使用
|
||||
# > ultraplan 重构认证模块
|
||||
# > /ultraplan
|
||||
```
|
||||
|
||||
## 六、文件索引
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `src/commands/ultraplan.tsx` | 525 | 斜杠命令处理器 |
|
||||
| `src/utils/ultraplan/ccrSession.ts` | 349 | CCR 远程会话管理 |
|
||||
| `src/utils/ultraplan/keyword.ts` | 127 | 关键字检测和替换 |
|
||||
| `src/utils/ultraplan/prompt.txt` | 1 | 嵌入式提示 |
|
||||
| `src/utils/processUserInput/processUserInput.ts:468` | — | 关键字重定向 |
|
||||
| `src/components/PromptInput/PromptInput.tsx` | — | 彩虹高亮 |
|
||||
@@ -1,188 +0,0 @@
|
||||
# WEB_SEARCH_TOOL — 网页搜索工具
|
||||
|
||||
> 实现状态:适配器架构完成,支持 API / Bing / Brave 三种后端
|
||||
> 引用数:核心工具,无 feature flag 门控(始终启用)
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
WebSearchTool 让模型可以搜索互联网获取最新信息。原始实现仅支持 Anthropic API 服务端搜索(`web_search_20250305` server tool),在第三方代理端点下不可用。现已重构为适配器架构,支持 API 服务端搜索,以及 Bing / Brave 两个 HTML 解析后端,确保任何 API 端点都能使用搜索功能。
|
||||
|
||||
## 二、实现架构
|
||||
|
||||
### 2.1 适配器模式
|
||||
|
||||
```
|
||||
WebSearchTool.call()
|
||||
│
|
||||
▼
|
||||
createAdapter() ← 适配器工厂
|
||||
│
|
||||
├── ApiSearchAdapter — Anthropic 官方 API 服务端搜索
|
||||
│ └── 使用 web_search_20250305 server tool
|
||||
│ 通过 queryModelWithStreaming 二次调用 API
|
||||
│
|
||||
├── BingSearchAdapter — Bing HTML 抓取 + 正则提取
|
||||
│ └── 直接抓取 Bing 搜索页 HTML
|
||||
│ 正则提取 b_algo 块中的标题/URL/摘要
|
||||
│
|
||||
└── BraveSearchAdapter — Brave LLM Context API
|
||||
└── 调用 Brave HTTPS GET 接口
|
||||
将 grounding payload 映射为标题/URL/摘要
|
||||
```
|
||||
|
||||
### 2.2 模块结构
|
||||
|
||||
| 模块 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 工具入口 | `packages/builtin-tools/src/tools/WebSearchTool/WebSearchTool.ts` | `buildTool()` 定义:schema、权限、执行、输出格式化 |
|
||||
| 工具 prompt | `packages/builtin-tools/src/tools/WebSearchTool/prompt.ts` | 搜索工具的系统提示词 |
|
||||
| UI 渲染 | `packages/builtin-tools/src/tools/WebSearchTool/UI.tsx` | 搜索结果的终端渲染组件 |
|
||||
| 适配器接口 | `packages/builtin-tools/src/tools/WebSearchTool/adapters/types.ts` | `WebSearchAdapter` 接口、`SearchResult`/`SearchOptions`/`SearchProgress` 类型 |
|
||||
| 适配器工厂 | `packages/builtin-tools/src/tools/WebSearchTool/adapters/index.ts` | `createAdapter()` 工厂函数,选择后端 |
|
||||
| API 适配器 | `packages/builtin-tools/src/tools/WebSearchTool/adapters/apiAdapter.ts` | 封装原有 `queryModelWithStreaming` 逻辑,使用 server tool |
|
||||
| Bing 适配器 | `packages/builtin-tools/src/tools/WebSearchTool/adapters/bingAdapter.ts` | Bing HTML 抓取 + 正则解析 |
|
||||
| Brave 适配器 | `packages/builtin-tools/src/tools/WebSearchTool/adapters/braveAdapter.ts` | Brave LLM Context API 适配与结果映射 |
|
||||
| 单元测试 | `packages/builtin-tools/src/tools/WebSearchTool/__tests__/bingAdapter.test.ts`, `packages/builtin-tools/src/tools/WebSearchTool/__tests__/braveAdapter*.test.ts`, `packages/builtin-tools/src/tools/WebSearchTool/__tests__/adapterFactory.test.ts` | Bing / Brave 解析与工厂逻辑测试 |
|
||||
| 集成测试 | `packages/builtin-tools/src/tools/WebSearchTool/__tests__/bingAdapter.integration.ts`, `packages/builtin-tools/src/tools/WebSearchTool/__tests__/braveAdapter.integration.ts` | 真实网络请求验证 |
|
||||
|
||||
### 2.3 数据流
|
||||
|
||||
```
|
||||
模型调用 WebSearchTool(query, allowed_domains, blocked_domains)
|
||||
│
|
||||
▼
|
||||
validateInput() — 校验 query 非空、allowed/block 不共存
|
||||
│
|
||||
▼
|
||||
createAdapter() → ApiSearchAdapter | BingSearchAdapter | BraveSearchAdapter
|
||||
│
|
||||
▼
|
||||
adapter.search(query, { allowedDomains, blockedDomains, signal, onProgress })
|
||||
│
|
||||
├── onProgress({ type: 'query_update', query })
|
||||
│
|
||||
├── axios.get(search-engine-url)
|
||||
│ └── API 鉴权请求头
|
||||
│
|
||||
├── extractResults(payload) — 按后端提取结果
|
||||
│ └── grounding → SearchResult[] 映射
|
||||
│
|
||||
├── 客户端域名过滤 (allowedDomains / blockedDomains)
|
||||
│
|
||||
├── onProgress({ type: 'search_results_received', resultCount })
|
||||
│
|
||||
▼
|
||||
格式化为 markdown 链接列表返回给模型
|
||||
```
|
||||
|
||||
## 三、Bing 适配器技术细节
|
||||
|
||||
### 3.1 反爬绕过
|
||||
|
||||
使用 13 个 Edge 浏览器请求头(含 `Sec-Ch-Ua`、`Sec-Fetch-*` 等),避免 Bing 返回 JS 渲染的空页面:
|
||||
|
||||
```typescript
|
||||
const BROWSER_HEADERS = {
|
||||
'User-Agent': '...Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0',
|
||||
'Sec-Ch-Ua': '"Microsoft Edge";v="131", "Chromium";v="131", ...',
|
||||
'Sec-Fetch-Dest': 'document',
|
||||
'Sec-Fetch-Mode': 'navigate',
|
||||
'Sec-Fetch-Site': 'none',
|
||||
'Sec-Fetch-User': '?1',
|
||||
// ... 共 13 个标头
|
||||
}
|
||||
```
|
||||
|
||||
`setmkt=en-US` 参数强制美式英语市场,避免 IP 地理定位导致区域化结果。
|
||||
|
||||
### 3.2 URL 解码(`resolveBingUrl()`)
|
||||
|
||||
Bing 返回的重定向 URL 格式:`bing.com/ck/a?...&u=a1aHR0cHM6Ly9...`
|
||||
|
||||
- `u` 参数前 2 字符为协议前缀:`a1` = https,`a0` = http
|
||||
- 剩余部分为 base64url 编码的真实 URL
|
||||
- Bing 内部链接和相对路径被过滤返回 `undefined`
|
||||
|
||||
### 3.3 摘要提取(`extractSnippet()`)
|
||||
|
||||
三级降级策略:
|
||||
|
||||
1. `<p class="b_lineclamp...">` — Bing 的搜索摘要段落
|
||||
2. `<div class="b_caption">` 内的 `<p>` — 备选摘要位置
|
||||
3. `<div class="b_caption">` 直接文本 — 最终 fallback
|
||||
|
||||
### 3.4 域名过滤
|
||||
|
||||
客户端侧实现,支持子域名匹配:
|
||||
- `allowedDomains`:白名单,结果域名必须匹配列表中的某项(含子域名)
|
||||
- `blockedDomains`:黑名单,匹配的结果被过滤
|
||||
- 两者不可同时使用(`validateInput` 校验)
|
||||
|
||||
## 四、适配器选择逻辑
|
||||
|
||||
`createAdapter()` 按以下优先级选择后端,并按选中的后端 key 缓存适配器实例:
|
||||
|
||||
```typescript
|
||||
export function createAdapter(): WebSearchAdapter {
|
||||
// 1. WEB_SEARCH_ADAPTER=api|bing|brave 显式指定
|
||||
// 2. Anthropic 官方 API Base URL → ApiSearchAdapter
|
||||
// 3. 第三方代理 / 非官方端点 → BingSearchAdapter
|
||||
}
|
||||
```
|
||||
|
||||
显式指定 `WEB_SEARCH_ADAPTER=brave` 时,会改用 Brave LLM Context API 后端,并要求
|
||||
`BRAVE_SEARCH_API_KEY` 或 `BRAVE_API_KEY`。
|
||||
|
||||
## 五、接口定义
|
||||
|
||||
### WebSearchAdapter
|
||||
|
||||
```typescript
|
||||
interface WebSearchAdapter {
|
||||
search(query: string, options: SearchOptions): Promise<SearchResult[]>
|
||||
}
|
||||
|
||||
interface SearchResult {
|
||||
title: string
|
||||
url: string
|
||||
snippet?: string
|
||||
}
|
||||
|
||||
interface SearchOptions {
|
||||
allowedDomains?: string[]
|
||||
blockedDomains?: string[]
|
||||
signal?: AbortSignal
|
||||
onProgress?: (progress: SearchProgress) => void
|
||||
}
|
||||
|
||||
interface SearchProgress {
|
||||
type: 'query_update' | 'search_results_received'
|
||||
query?: string
|
||||
resultCount?: number
|
||||
}
|
||||
```
|
||||
|
||||
### 工具 Input Schema
|
||||
|
||||
```typescript
|
||||
{
|
||||
query: string // 搜索关键词,最少 2 字符
|
||||
allowed_domains?: string[] // 域名白名单
|
||||
blocked_domains?: string[] // 域名黑名单
|
||||
}
|
||||
```
|
||||
|
||||
## 六、文件索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/WebSearchTool.ts` | 工具定义入口 |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/prompt.ts` | 搜索工具 prompt |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/UI.tsx` | 终端 UI 渲染 |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/adapters/types.ts` | 适配器接口 |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/adapters/index.ts` | 适配器工厂 |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/adapters/apiAdapter.ts` | API 服务端搜索适配器 |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/adapters/bingAdapter.ts` | Bing HTML 解析适配器 |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/__tests__/bingAdapter.test.ts` | 单元测试 (32 cases) |
|
||||
| `packages/builtin-tools/src/tools/WebSearchTool/__tests__/bingAdapter.integration.ts` | 集成测试 |
|
||||
| `src/tools.ts` | 工具注册 |
|
||||
@@ -1,183 +0,0 @@
|
||||
# WORKFLOW_SCRIPTS — 确定性多 agent 工作流编排
|
||||
|
||||
> Feature Flag:`FEATURE_WORKFLOW_SCRIPTS=1`
|
||||
> 引擎包:[`@claude-code-best/workflow-engine`](../../packages/workflow-engine/)(确定性 JS 脚本编排,零核心层运行时依赖)
|
||||
> 集成层:[`src/workflow/`](../../src/workflow/)
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
WORKFLOW_SCRIPTS 让 Claude Code 用**确定性 JavaScript 脚本**编排多个子 agent:可分解/并行、多视角置信、规模超单上下文、可 resume/可审计。
|
||||
|
||||
- **编排原语**:`agent` / `parallel` / `pipeline` / `phase` / `log` / `workflow`(见引擎包)。
|
||||
- **确定性**:脚本在受限沙箱内执行,禁用 `Date.now()` / `Math.random()` / 无参 `new Date()`,保证 journal 可重放。
|
||||
- **深度后端**:单一 `claude-code` AgentAdapter 接入当前会话体系(provider / model / agentType / 工具),workflow 内的 `agent()` 调用真实子 agent。
|
||||
- **监控面板**:`/workflows` 双栏实时面板(见 §六)。
|
||||
- **编排手册**:`/ultracode` 注入编排工作法(见 §七)。
|
||||
|
||||
> 历史说明:早期版本为 YAML/JSON DSL + 全 Stub 实现(`WorkflowDetailDialog` 等),已全量重写为引擎驱动的 JS 方案。
|
||||
|
||||
## 二、实现架构
|
||||
|
||||
```
|
||||
.claude/workflows/<name>.ts Workflow 工具(name/script/scriptPath/args/resumeFromRunId)
|
||||
│ │
|
||||
▼ ▼
|
||||
namedWorkflowCommands.ts src/workflow/wiring.ts (createWorkflowToolCore)
|
||||
(/<name> 命令发现) │
|
||||
▼
|
||||
WorkflowService(门面:launch/kill/subscribe/listRuns/listNamed)
|
||||
│
|
||||
┌────────────────┼─────────────────┐
|
||||
▼ ▼ ▼
|
||||
ports.ts registry.ts progress/
|
||||
(端口聚合) (AgentAdapterRegistry) bus + store
|
||||
│ │
|
||||
▼ ▼
|
||||
hostHandle.ts backends/claudeCodeBackend.ts
|
||||
(不透明 host) (深度读会话体系,跑真实 agent)
|
||||
│
|
||||
▼
|
||||
@claude-code-best/workflow-engine
|
||||
(runWorkflow / hooks / journal / budget / 并发信号量)
|
||||
```
|
||||
|
||||
### 2.1 模块清单
|
||||
|
||||
| 层 | 文件 | 职责 |
|
||||
|----|------|------|
|
||||
| 引擎 | `packages/workflow-engine/src/` | 确定性脚本沙箱 + hooks + journal + budget + 信号量;导出 `createWorkflowTool` |
|
||||
| 工具装配 | `src/workflow/wiring.ts` | `createWorkflowToolCore()` —— 用 `WorkflowService.ports` 组装 `Workflow` 工具 |
|
||||
| 服务门面 | `src/workflow/service.ts` | `WorkflowService` 单例:`launch` / `kill` / `subscribe` / `listRuns` / `listNamed` / `getWorkflowService()` |
|
||||
| 端口 | `src/workflow/ports.ts` | `createWorkflowPorts()` 聚合所有端口(agentRunner/registry/progress/task/journal/permission/logger/hostFactory) |
|
||||
| 后端注册 | `src/workflow/registry.ts` | `buildRegistry()` 注册 `claude-code` 后端并设为默认 |
|
||||
| 深度后端 | `src/workflow/backends/claudeCodeBackend.ts` | AgentAdapter:按 `agentType`/`model` 解析会话体系,跑真实子 agent,结构化输出 |
|
||||
| Host 句柄 | `src/workflow/hostHandle.ts` | `buildHostBundle()` 不透明包装 `toolUseContext`/`canUseTool`/`parentMessage` |
|
||||
| 进度总线 | `src/workflow/progress/bus.ts` | 基于 Set 的进度事件发射 |
|
||||
| 进度状态 | `src/workflow/progress/store.ts` | reducer:按 `agentId` 精确关联 `agent_done`(修并发竞态) |
|
||||
| 监控面板 | `src/workflow/panel/*.tsx` | `/workflows` 双栏 UI(见 §六) |
|
||||
| 命名命令 | `src/workflow/namedWorkflowCommands.ts` | 扫描 `.claude/workflows/` 生成 `/<name>` 命令 |
|
||||
| 权限请求 | `src/workflow/WorkflowPermissionRequest.tsx` | workflow 启动权限 UI |
|
||||
|
||||
### 2.2 注册点
|
||||
|
||||
| 位置 | 内容 |
|
||||
|------|------|
|
||||
| `src/tools.ts:152-153,254` | `createWorkflowToolCore()` 动态加载并注册 `Workflow` 工具(feature-gated) |
|
||||
| `src/commands.ts:95-97,392` | `/workflows` 命令(local-jsx,加载 `panelCall.js`) |
|
||||
| `src/skills/bundled/ultracode.ts` + `index.ts` | `/ultracode` 知识 skill(`registerBundledSkill`) |
|
||||
|
||||
## 三、编排原语
|
||||
|
||||
workflow 脚本内可用的钩子(语义详见引擎包 `engine/hooks.ts`):
|
||||
|
||||
| 原语 | 语义 |
|
||||
|------|------|
|
||||
| `agent(prompt, opts?)` | 派发一个子 agent;返回最终文本,或(带 `opts.schema`)结构化对象。opts:`model` / `agentType` / `label` / `phase` / `schema` |
|
||||
| `parallel([() => …])` | 并发跑 thunk 数组,**barrier**(等全部完成);单项抛错 → 该项 `null`,其余保留 |
|
||||
| `pipeline(items, s1, s2, …)` | 每个 item 链式过各 stage;**item 间无 barrier**,stage 内顺序;单 item 某 stage 抛错 → 该 item `null` |
|
||||
| `phase(title)` | 标记阶段(面板按此分组展示) |
|
||||
| `log(msg)` | 进度日志(面板展示,无状态变更) |
|
||||
| `workflow(name \| { scriptPath }, args?)` | 嵌套一层子 workflow(仅允许一层) |
|
||||
|
||||
**硬限**:单次 `parallel`/`pipeline` ≤ `MAX_ITEMS_PER_CALL`(4096);单 workflow 总 agent ≤ `MAX_TOTAL_AGENTS`(1000);并发 cap 默认 = `DEFAULT_MAX_CONCURRENCY`(3),可经 Workflow 工具的 `maxConcurrency` 入参覆盖,绝对上限 `MAX_CONCURRENCY_CAP`(16)。
|
||||
|
||||
## 四、编写 workflow
|
||||
|
||||
脚本置于 `.claude/workflows/<name>.js|.mjs`(也接受 `.ts`,但**引擎不转译 TS**,含类型注解会报语法错——推荐 `.js`/`.mjs`),自动成为 `/<name>` 命令。
|
||||
|
||||
```js
|
||||
// .claude/workflows/review-changes.js
|
||||
export const meta = {
|
||||
name: 'review-changes',
|
||||
description: '按维度审查改动并对抗式验证',
|
||||
phases: [{ title: 'Review' }, { title: 'Verify' }],
|
||||
}
|
||||
|
||||
const DIMENSIONS = [
|
||||
{ key: 'bugs', prompt: '找正确性 bug' },
|
||||
{ key: 'perf', prompt: '找性能问题' },
|
||||
]
|
||||
|
||||
const results = await pipeline(
|
||||
DIMENSIONS,
|
||||
d => agent(d.prompt, { label: `review:${d.key}`, phase: 'Review' }),
|
||||
review => parallel(
|
||||
(review.findings || []).map(f => () =>
|
||||
agent(`对抗式验证:${f.title}`, { phase: 'Verify' })
|
||||
)
|
||||
)
|
||||
)
|
||||
return results.flat().filter(Boolean)
|
||||
```
|
||||
|
||||
**脚本执行约束**(引擎执行模型,违反直接报错):
|
||||
|
||||
脚本是 `new AsyncFunction` 的**函数体**,不是 ESM 模块:
|
||||
|
||||
- **禁 `import`**:`agent`/`parallel`/`pipeline`/`phase`/`log`/`workflow` 与 `args`/`budget` 是注入的形参,直接用。
|
||||
- **禁 TS 语法**:不要类型注解(`x: number`)、`interface`、`enum`、`as`、泛型。引擎不转译,即便文件是 `.ts` 也会原样报语法错。
|
||||
- **只允许一处 `export const meta = {...}`**(引擎正则提取剥离);不要 `export` 其他、不要 `export default`。
|
||||
- **顶层 `return` 返回结果**。
|
||||
|
||||
**确定性约束**(违反则 resume 失效):
|
||||
- 禁 `Date.now()` / `Math.random()` / 无参 `new Date()`(沙箱强制抛错)。需时间戳/随机种子经 `args` 传入。
|
||||
- `export const meta = { ... }` 必须是**纯字面量**(无变量、函数调用、模板插值)——加载期求值,否则抛 `ScriptError`。
|
||||
|
||||
## 五、Workflow 工具
|
||||
|
||||
模型通过 `Workflow` 工具启动 workflow(input schema 见引擎包 `tool/schema.ts`):
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `script` | 内联脚本字符串 |
|
||||
| `name` | 命名 workflow 名(对应 `.claude/workflows/<name>`) |
|
||||
| `scriptPath` | 脚本文件路径 |
|
||||
| `args` | 透传给脚本的 `args`(任意 JSON 值) |
|
||||
| `resumeFromRunId` | 从既有 runId 重放(已完成 `agent()` 秒回,发散点后现场重跑) |
|
||||
|
||||
## 六、监控面板:`/workflows`
|
||||
|
||||
`/workflows` 打开三区焦点面板(local-jsx,全屏):
|
||||
|
||||
- **顶部 tabs**:每个 run 一个 tab(状态圆点 + workflow 名 + `#runId短码`);同名脚本多次跑会多个 tab。
|
||||
- **左 phase 侧栏**:`All` + 合并 meta 声明的 phase(未启动 `○` pending 灰)与实际 phase(`●` running / `✓` done);选中即决定右栏筛选。
|
||||
- **右 agent 列表**:按选中 phase 过滤;状态色 + 行尾文字(`running` / `object` / `text` / `dead`)。
|
||||
|
||||
**键位**:`Tab`/`Shift+Tab` 切 run · `←`/`→` 切左右焦点列(phases ↔ agents)· `↑`/`↓` 列内移动 · `r` resume · `x` kill · `n` 新建提示 · `q`/`Esc` 退出。
|
||||
|
||||
**视觉**:无内框,左右一条竖线分隔;聚焦列标题橙粗;选中/光标行铺橙底(`backgroundColor`),文字色不变。
|
||||
|
||||
进度按引擎 `agentId` 精确关联 `agent_done`(解决并发 LIFO 竞态)。pending phase 来自 `run_started` 事件携带的 `meta.phases`,store 落地 `declaredPhases`,面板 `mergePhases` 合并。`useSyncExternalStore` 订阅 `WorkflowService`,稳定快照,无变更不重渲染。
|
||||
|
||||
## 七、`/ultracode` skill
|
||||
|
||||
`/ultracode`(`src/skills/bundled/ultracode.ts`)注入多 agent workflow 编排工作法:何时用 / 何时不用、编排原语速查、质量模式库(adversarial-verify / judge-panel / loop-until-dry / multi-modal-sweep / completeness-critic)、确定性约束、后端路由、resume/budget、文件与命令。
|
||||
|
||||
**纯知识 prompt skill**:零运行时副作用,不改主循环、不切换行为开关。调用即把手册注入上下文。
|
||||
|
||||
## 八、resume / journal / budget
|
||||
|
||||
- **journal**:每次 run 记录到 `.claude/workflow-runs/<runId>/journal.jsonl`。`resumeFromRunId` 重放 journal,已完成 `agent()` 秒回缓存结果。
|
||||
- **budget**:`budget.total` 为 token 硬顶(默认 `null` = 无限);`budget.spent()` / `budget.remaining()` 读实时消耗;耗尽后再发 `agent()` 抛错。
|
||||
- **并发**:引擎 `Semaphore` 默认许可 3(`DEFAULT_MAX_CONCURRENCY`),可经 Workflow 工具的 `maxConcurrency` 入参 per-run 覆盖(钳到 `[1, MAX_CONCURRENCY_CAP=16]`)。
|
||||
- **错误**:脚本语法/meta 错 → `parseScript` 即时返错(不进后台);agent 抛错 → `kind:'dead'` → `null`,workflow 继续(`parallel`/`pipeline` 容错);`WorkflowAbortedError` → `killed`。
|
||||
|
||||
## 九、文件索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/workflow/wiring.ts` | `Workflow` 工具装配(`createWorkflowToolCore`) |
|
||||
| `src/workflow/service.ts` | `WorkflowService` 门面 |
|
||||
| `src/workflow/ports.ts` | 端口聚合(`createWorkflowPorts`) |
|
||||
| `src/workflow/registry.ts` | `AgentAdapterRegistry` + 默认后端 |
|
||||
| `src/workflow/backends/claudeCodeBackend.ts` | 深度后端 AgentAdapter |
|
||||
| `src/workflow/hostHandle.ts` | 不透明 host 句柄(`buildHostBundle`) |
|
||||
| `src/workflow/progress/bus.ts` | 进度事件总线 |
|
||||
| `src/workflow/progress/store.ts` | 进度 reducer(`agentId` 关联) |
|
||||
| `src/workflow/panel/*.tsx` | `/workflows` 双栏面板 |
|
||||
| `src/workflow/namedWorkflowCommands.ts` | `/<name>` 命令发现 |
|
||||
| `src/workflow/WorkflowPermissionRequest.tsx` | 启动权限 UI |
|
||||
| `src/skills/bundled/ultracode.ts` | `/ultracode` 知识 skill |
|
||||
| `src/tools.ts:152-153,254` | 工具注册 |
|
||||
| `src/commands.ts:95-97,392` | `/workflows` 命令注册 |
|
||||
| `packages/workflow-engine/` | 引擎包(hooks / journal / budget / 并发) |
|
||||
Reference in New Issue
Block a user