mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
feat: 远程群控 (#243)
* feat: restore pipe IPC, LAN pipes, monitor tool, and PR-package features Core IPC system (UDS_INBOX): - PipeServer/PipeClient with UDS + TCP dual transport, NDJSON protocol - PipeRegistry: machineId-based role assignment, file locking - Master/slave attach, prompt relay, permission forwarding - Heartbeat lifecycle with parallel isPipeAlive probes - Commands: /pipes, /attach, /detach, /send, /claim-main, /pipe-status LAN Pipes (LAN_PIPES): - UDP multicast beacon (224.0.71.67:7101) for zero-config LAN discovery - PipeServer TCP listener, PipeClient TCP connect mode - Heartbeat auto-attaches LAN peers via TCP - Cross-machine attach allowed regardless of role - /pipes shows [LAN] peers with role + hostname/IP - SendMessageTool supports tcp: scheme with user consent Architecture — extracted hooks from REPL.tsx (~830 lines → ~20 lines): - usePipeIpc: lifecycle (bootstrap, handlers, heartbeat, cleanup) - usePipeRelay: slave→master message relay via module singleton - usePipePermissionForward: permission request/cancel forwarding - usePipeRouter: selected pipe input routing with role+IP labels - Shared ndjsonFramer.ts replaces 3 duplicate NDJSON parsers Key fixes applied during development: - Multicast binds to correct LAN interface (not WSL/Docker) - Beacon ref stored as module singleton (not Zustand state mutation) - Heartbeat preserves LAN peers in discoveredPipes and selectedPipes - Disconnect handler calls removeSlaveClient (fixes listener leak) - cleanupStaleEntries probes without lock, writes briefly under lock - getMachineId uses async execFile (not blocking execSync) - globalThis.__pipeSendToMaster replaced with setPipeRelay singleton - M key only toggles route mode when selector panel is expanded - User prompt displayed in message list on pipe broadcast - Broadcast notifications show [role] + hostname/IP for LAN peers Other restored features: - Monitor tool: /monitor command, MonitorTool, MonitorMcpTask lifecycle - Daemon supervisor and remoteControlServer command - Tools: SnipTool, SleepTool, ListPeersTool, SendUserFileTool, WebBrowserTool, WorkflowTool, and 10+ stub→implementation rewrites - Feature flags: UDS_INBOX, LAN_PIPES, MONITOR_TOOL, FORK_SUBAGENT, KAIROS, COORDINATOR_MODE, WORKFLOW_SCRIPTS, HISTORY_SNIP Tests: 2190 pass / 0 fail (15 new: lanBeacon 7, peerAddress 8) * fix: resolve merge conflicts and fix all tsc/test errors after main merge - Export ToolResultBlockParam from Tool.ts (14 tool files fixed) - Migrate ink imports from ../../ink.js to @anthropic/ink (7 files) - Fix toolUseID → toolUseId typo in monitor.ts and MonitorTool.tsx - Add fallback values for string|undefined type errors (8 locations) - Fix AppState type in assistant.ts, add NewInstallWizard stubs - Fix ParsedRepository.repo → .name in subscribe-pr.ts - Fix AgentId/string type mismatch in BackgroundTasksDialog.tsx - Fix PipeRelayFn return type in pipePermissionRelay.ts - Use PipeMessage type in usePipeRelay.ts - Fix lanBeacon.test.ts mock type assertions - Create missing MouseActionEvent class for ink package - Use ansi: color format instead of bare "green"/"red" - Resolve theme.permission access via getTheme() Result: 0 tsc errors, 2496 tests pass, 0 fail Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 恢复 /poor 的说明 --------- Co-authored-by: unraid <local@unraid.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -250,7 +250,7 @@ FEATURE_KAIROS=1 FEATURE_PROACTIVE=1 FEATURE_FORK_SUBAGENT=1 bun run dev
|
||||
| Feature | 引用 | 状态 | 说明 |
|
||||
|---------|------|------|------|
|
||||
| CHICAGO_MCP | 16 | N/A | Anthropic 内部 MCP 基础设施 |
|
||||
| UDS_INBOX | 17 | Stub | Unix 域套接字对等消息 |
|
||||
| UDS_INBOX | 17 | Experimental | 本机 UDS 消息层 + 本机 named-pipe 协调层 |
|
||||
| MONITOR_TOOL | 13 | Stub | 文件/进程监控工具 |
|
||||
| BG_SESSIONS | 11 | Stub | 后台会话管理 |
|
||||
| SHOT_STATS | 10 | 无实现 | 逐 prompt 统计 |
|
||||
|
||||
@@ -1005,38 +1005,32 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
|
||||
## 28. UDS_INBOX
|
||||
|
||||
**编译时引用次数**: 18(单引号 17 + 双引号 1)
|
||||
**功能描述**: UDS(Unix Domain Socket)收件箱。允许 Claude Code 实例之间通过 Unix 套接字发送消息。
|
||||
**分类**: PARTIAL
|
||||
**缺失原因**: `src/utils/udsMessaging.ts` 仅 1 行,`src/utils/udsClient.ts` 仅 3 行(空壳),命令入口缺失
|
||||
**编译时引用次数**: 18(历史快照)
|
||||
**功能描述**: 本机进程间通信能力。当前由两层组成:
|
||||
1. `udsMessaging` / `udsClient`:通用 UDS 消息层,供 `SendMessageTool` 与 `/peers` 使用。
|
||||
2. `pipeTransport` / `pipeRegistry`:会话级 named-pipe 协调层,供 `/pipes`、`/attach`、`/detach`、`/send`、`/pipe-status`、`/history`、`/claim-main` 使用。
|
||||
|
||||
**当前分类**: IMPLEMENTED / EXPERIMENTAL
|
||||
|
||||
**当前事实**:
|
||||
- `src/utils/udsMessaging.ts` 与 `src/utils/udsClient.ts` 已实现,不再是空壳。
|
||||
- `src/utils/pipeTransport.ts` 使用本机 named pipe / Unix socket;`localIp` / `hostname` / `machineId` 仅用于注册表展示与身份判定,不是已上线的局域网传输层。
|
||||
- `src/screens/REPL.tsx` 内联承载当前有效的 pipe 控制平面;早期 hook 试验路径已清理。
|
||||
|
||||
**核心实现文件**:
|
||||
|
||||
| 文件路径 | 行数 | 功能说明 |
|
||||
|----------|------|----------|
|
||||
| src/tools/SendMessageTool/SendMessageTool.ts | 917 行 | 发送消息工具(完整实现) |
|
||||
| src/tools/SendMessageTool/prompt.ts | 49 行 | 消息工具提示词 |
|
||||
| src/utils/udsClient.ts | 3 行 | UDS 客户端(桩) |
|
||||
| src/utils/udsMessaging.ts | 1 行 | UDS 消息(桩) |
|
||||
| 文件路径 | 功能说明 |
|
||||
|----------|----------|
|
||||
| src/utils/udsMessaging.ts | 通用 UDS server / inbox |
|
||||
| src/utils/udsClient.ts | 通用 peer 发现、探活、发送 |
|
||||
| src/utils/pipeTransport.ts | named-pipe server/client、探活、AppState 扩展 |
|
||||
| src/utils/pipeRegistry.ts | main/sub 注册表、machineId、claim-main |
|
||||
| src/commands/peers/peers.ts | UDS peer 可达性检查 |
|
||||
| src/commands/pipes/pipes.ts | pipe 注册表检查与选择器入口 |
|
||||
| src/commands/attach/attach.ts | master -> slave attach |
|
||||
| src/screens/REPL.tsx | 当前生效的 REPL pipe bootstrap 与心跳 |
|
||||
|
||||
**引用该标志的文件(10 个)**:
|
||||
1. src/cli/print.ts — CLI 输出
|
||||
2. src/commands.ts — 命令注册(引用 `commands/peers/index.js`)
|
||||
3. src/components/messages/UserTextMessage.tsx — 用户消息
|
||||
4. src/main.tsx — 主入口
|
||||
5. src/setup.ts — 初始化
|
||||
6. src/tools.ts — 工具注册
|
||||
7. src/tools/SendMessageTool/SendMessageTool.ts — 发送消息工具
|
||||
8. src/tools/SendMessageTool/prompt.ts — 提示词
|
||||
9. src/utils/concurrentSessions.ts — 并发会话
|
||||
10. src/utils/messages/systemInit.ts — 系统初始化消息
|
||||
|
||||
**缺失文件**:
|
||||
- src/commands/peers/index.ts — 命令入口缺失
|
||||
- src/utils/udsMessaging.ts — 仅 1 行空壳
|
||||
- src/utils/udsClient.ts — 仅 3 行空壳
|
||||
|
||||
**启用所需修复**: 需要实现 UDS 客户端和消息模块,并创建命令入口。
|
||||
**备注**: 如需真实局域网通信,需要单独引入 TCP/WebSocket 传输、认证与发现机制;现有代码尚未实现该层。详见 `docs/features/uds-inbox.md`。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1011,38 +1011,32 @@ src/utils/swarm/ 目录(22 个文件):
|
||||
|
||||
## 28. UDS_INBOX
|
||||
|
||||
**编译时引用次数**: 18(单引号 17 + 双引号 1)
|
||||
**功能描述**: UDS(Unix Domain Socket)收件箱。允许 Claude Code 实例之间通过 Unix 套接字发送消息。
|
||||
**分类**: PARTIAL
|
||||
**缺失原因**: `src/utils/udsMessaging.ts` 仅 1 行,`src/utils/udsClient.ts` 仅 3 行(空壳),命令入口缺失
|
||||
**编译时引用次数**: 18(历史快照)
|
||||
**功能描述**: 本机进程间通信能力。当前由两层组成:
|
||||
1. `udsMessaging` / `udsClient`:通用 UDS 消息层,供 `SendMessageTool` 与 `/peers` 使用。
|
||||
2. `pipeTransport` / `pipeRegistry`:会话级 named-pipe 协调层,供 `/pipes`、`/attach`、`/detach`、`/send`、`/pipe-status`、`/history`、`/claim-main` 使用。
|
||||
|
||||
**当前分类**: IMPLEMENTED / EXPERIMENTAL
|
||||
|
||||
**当前事实**:
|
||||
- `src/utils/udsMessaging.ts` 与 `src/utils/udsClient.ts` 已实现,不再是空壳。
|
||||
- `src/utils/pipeTransport.ts` 使用本机 named pipe / Unix socket;`localIp` / `hostname` / `machineId` 仅用于注册表展示与身份判定,不是已上线的局域网传输层。
|
||||
- `src/screens/REPL.tsx` 内联承载当前有效的 pipe 控制平面;早期 hook 试验路径已清理。
|
||||
|
||||
**核心实现文件**:
|
||||
|
||||
| 文件路径 | 行数 | 功能说明 |
|
||||
|----------|------|----------|
|
||||
| src/tools/SendMessageTool/SendMessageTool.ts | 917 行 | 发送消息工具(完整实现) |
|
||||
| src/tools/SendMessageTool/prompt.ts | 49 行 | 消息工具提示词 |
|
||||
| src/utils/udsClient.ts | 3 行 | UDS 客户端(桩) |
|
||||
| src/utils/udsMessaging.ts | 1 行 | UDS 消息(桩) |
|
||||
| 文件路径 | 功能说明 |
|
||||
|----------|----------|
|
||||
| src/utils/udsMessaging.ts | 通用 UDS server / inbox |
|
||||
| src/utils/udsClient.ts | 通用 peer 发现、探活、发送 |
|
||||
| src/utils/pipeTransport.ts | named-pipe server/client、探活、AppState 扩展 |
|
||||
| src/utils/pipeRegistry.ts | main/sub 注册表、machineId、claim-main |
|
||||
| src/commands/peers/peers.ts | UDS peer 可达性检查 |
|
||||
| src/commands/pipes/pipes.ts | pipe 注册表检查与选择器入口 |
|
||||
| src/commands/attach/attach.ts | master -> slave attach |
|
||||
| src/screens/REPL.tsx | 当前生效的 REPL pipe bootstrap 与心跳 |
|
||||
|
||||
**引用该标志的文件(10 个)**:
|
||||
1. src/cli/print.ts — CLI 输出
|
||||
2. src/commands.ts — 命令注册(引用 `commands/peers/index.js`)
|
||||
3. src/components/messages/UserTextMessage.tsx — 用户消息
|
||||
4. src/main.tsx — 主入口
|
||||
5. src/setup.ts — 初始化
|
||||
6. src/tools.ts — 工具注册
|
||||
7. src/tools/SendMessageTool/SendMessageTool.ts — 发送消息工具
|
||||
8. src/tools/SendMessageTool/prompt.ts — 提示词
|
||||
9. src/utils/concurrentSessions.ts — 并发会话
|
||||
10. src/utils/messages/systemInit.ts — 系统初始化消息
|
||||
|
||||
**缺失文件**:
|
||||
- src/commands/peers/index.ts — 命令入口缺失
|
||||
- src/utils/udsMessaging.ts — 仅 1 行空壳
|
||||
- src/utils/udsClient.ts — 仅 3 行空壳
|
||||
|
||||
**启用所需修复**: 需要实现 UDS 客户端和消息模块,并创建命令入口。
|
||||
**备注**: 如需真实局域网通信,需要单独引入 TCP/WebSocket 传输、认证与发现机制;现有代码尚未实现该层。详见 `docs/features/uds-inbox.md`。
|
||||
|
||||
---
|
||||
|
||||
|
||||
545
docs/features/lan-pipes-implementation.md
Normal file
545
docs/features/lan-pipes-implementation.md
Normal file
@@ -0,0 +1,545 @@
|
||||
# LAN Pipes 实现文档
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 目标
|
||||
|
||||
在现有 UDS (Unix Domain Socket) 本地 Pipe 通讯系统基础上,增加 **TCP 传输层** 和 **UDP Multicast 发现机制**,使同一局域网内不同机器上的 Claude Code CLI 实例可以:
|
||||
|
||||
1. **自动发现** — 通过 UDP multicast 零配置发现 LAN 内的其他实例
|
||||
2. **TCP 连接** — 通过 TCP 建立跨机器的双向 NDJSON 管道
|
||||
3. **复用现有协议** — attach/detach/prompt/stream 等消息类型无需修改
|
||||
|
||||
### 1.2 设计原则
|
||||
|
||||
- **向后兼容**:所有 LAN 功能通过 `feature('LAN_PIPES')` 门控,不影响现有 UDS 功能
|
||||
- **双模式共存**:PipeServer 同时监听 UDS 和 TCP,PipeClient 根据参数自动选择连接模式
|
||||
- **本地优先**:本地 registry 条目优先于 LAN beacon 发现的条目
|
||||
- **安全保守**:TCP 连接需用户显式同意,multicast TTL=1 不跨路由器
|
||||
|
||||
### 1.3 架构总览
|
||||
|
||||
```
|
||||
Machine A (192.168.1.10) Machine B (192.168.1.20)
|
||||
┌───────────────────────────┐ ┌───────────────────────────┐
|
||||
│ PipeServer │ │ PipeServer │
|
||||
│ UDS: cli-abc.sock │ │ UDS: cli-def.sock │
|
||||
│ TCP: 0.0.0.0:<random> │◄──TCP───►│ TCP: 0.0.0.0:<random> │
|
||||
├───────────────────────────┤ ├───────────────────────────┤
|
||||
│ LanBeacon │ │ LanBeacon │
|
||||
│ UDP multicast │◄──UDP───►│ UDP multicast │
|
||||
│ 224.0.71.67:7101 │ mcast │ 224.0.71.67:7101 │
|
||||
├───────────────────────────┤ ├───────────────────────────┤
|
||||
│ PipeRegistry │ │ PipeRegistry │
|
||||
│ registry.json (local) │ │ registry.json (local) │
|
||||
│ + mergeWithLanPeers() │ │ + mergeWithLanPeers() │
|
||||
└───────────────────────────┘ └───────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Feature Flag
|
||||
|
||||
### 2.1 注册
|
||||
|
||||
**文件**: `scripts/dev.ts` (L49), `build.ts` (L43)
|
||||
|
||||
`LAN_PIPES` 添加到 `DEFAULT_FEATURES` / `DEFAULT_BUILD_FEATURES` 数组中,dev 和 build 默认启用。
|
||||
|
||||
也可通过环境变量 `FEATURE_LAN_PIPES=1` 单独启用。
|
||||
|
||||
### 2.2 使用约束
|
||||
|
||||
Bun 的 `feature()` 只能在 `if` 语句或三元条件中直接使用(编译时常量),不能赋值给变量。所有使用点均遵循此约束。
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心变更详情
|
||||
|
||||
### 3.1 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 // 0 = 随机端口
|
||||
}
|
||||
```
|
||||
|
||||
#### PipeServer 类变更
|
||||
|
||||
| 成员 | 变更类型 | 说明 |
|
||||
|------|----------|------|
|
||||
| `tcpServer: Server \| null` | 新增字段 | TCP net.Server 实例 |
|
||||
| `_tcpAddress: TcpEndpoint \| null` | 新增字段 | TCP 监听地址 |
|
||||
| `tcpAddress` getter | 新增 | 公开 TCP 端口信息 |
|
||||
| `setupSocket(socket)` | 重构提取 | 从 `start()` 中提取,UDS 和 TCP 共用 |
|
||||
| `start(options?)` | 修改签名 | 新增可选 `PipeServerOptions` 参数 |
|
||||
| `startTcpServer(port)` | 新增私有方法 | 启动 TCP 监听 |
|
||||
| `close()` | 修改 | 增加 TCP server 关闭逻辑 |
|
||||
|
||||
**关键设计决策**:`setupSocket()` 方法被提取为共享逻辑,使 UDS 和 TCP 的 socket 处理完全一致。两种传输模式共享同一组 `clients: Set<Socket>` 和 `handlers`,对上层代码完全透明。
|
||||
|
||||
#### 代码路径
|
||||
|
||||
```
|
||||
start(options?)
|
||||
├── ensurePipesDir()
|
||||
├── 清理 stale socket (Unix)
|
||||
├── createServer() → UDS 监听 (现有逻辑)
|
||||
│ └── setupSocket() ← 提取的共享逻辑
|
||||
└── if options.enableTcp
|
||||
└── startTcpServer(port)
|
||||
├── createServer() → TCP 监听 0.0.0.0
|
||||
│ └── setupSocket() ← 同一个方法
|
||||
└── 记录 _tcpAddress
|
||||
```
|
||||
|
||||
### 3.2 PipeClient TCP 扩展
|
||||
|
||||
**文件**: `src/utils/pipeTransport.ts`
|
||||
|
||||
#### PipeClient 类变更
|
||||
|
||||
| 成员 | 变更类型 | 说明 |
|
||||
|------|----------|------|
|
||||
| `tcpEndpoint: TcpEndpoint \| null` | 新增字段 | TCP 连接目标 |
|
||||
| `constructor(target, sender?, tcpEndpoint?)` | 修改签名 | 新增可选 TCP endpoint |
|
||||
| `connect(timeout)` | 修改 | 根据 tcpEndpoint 分派 |
|
||||
| `connectTcp(timeout)` | 新增私有方法 | TCP 连接实现 |
|
||||
| `connectUds(timeout)` | 重构提取 | 原 `connect()` 的 UDS 逻辑 |
|
||||
|
||||
**关键设计决策**:TCP 连接不需要等待文件存在(UDS 的 `access()` 轮询),直接建立 TCP 连接。超时机制相同。
|
||||
|
||||
### 3.3 工厂函数更新
|
||||
|
||||
```typescript
|
||||
// 新签名
|
||||
export async function createPipeServer(
|
||||
name: string,
|
||||
options?: PipeServerOptions, // 新增
|
||||
): Promise<PipeServer>
|
||||
|
||||
export async function connectToPipe(
|
||||
targetName: string,
|
||||
senderName?: string,
|
||||
timeoutMs?: number,
|
||||
tcpEndpoint?: TcpEndpoint, // 新增
|
||||
): Promise<PipeClient>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.4 LAN Beacon — UDP Multicast 发现
|
||||
|
||||
**文件**: `src/utils/lanBeacon.ts` (新文件,~170 行)
|
||||
|
||||
#### 协议参数
|
||||
|
||||
| 参数 | 值 | 说明 |
|
||||
|------|-----|------|
|
||||
| Multicast 组 | `224.0.71.67` | "CC" = Claude Code 的 ASCII 对应 |
|
||||
| 端口 | `7101` | 固定 UDP 端口 |
|
||||
| 广播间隔 | `3000ms` | 3 秒一次 announce |
|
||||
| Peer 超时 | `15000ms` | 15 秒无 announce 视为 lost |
|
||||
| TTL | `1` | 仅链路本地,不跨路由器 |
|
||||
|
||||
#### Announce 包格式
|
||||
|
||||
```typescript
|
||||
type LanAnnounce = {
|
||||
proto: 'claude-pipe-v1' // 协议标识符(用于过滤非本协议 UDP 包)
|
||||
pipeName: string // e.g. "cli-abc12345"
|
||||
machineId: string // OS-level 稳定指纹
|
||||
hostname: string // 主机名
|
||||
ip: string // 发送端本地 IPv4
|
||||
tcpPort: number // TCP PipeServer 端口
|
||||
role: 'main' | 'sub' // 当前角色
|
||||
ts: number // unix ms 时间戳
|
||||
}
|
||||
```
|
||||
|
||||
#### LanBeacon 类 API
|
||||
|
||||
```typescript
|
||||
class LanBeacon extends EventEmitter {
|
||||
constructor(announce: Omit<LanAnnounce, 'proto' | 'ts'>)
|
||||
start(): void // 开始广播 + 监听
|
||||
stop(): void // 停止并释放资源
|
||||
getPeers(): Map<string, LanAnnounce> // 当前已知 peers
|
||||
updateAnnounce(partial): void // 更新自身 announce 数据
|
||||
|
||||
// Events
|
||||
on('peer-discovered', (peer: LanAnnounce) => void)
|
||||
on('peer-lost', (pipeName: string) => void)
|
||||
}
|
||||
```
|
||||
|
||||
#### 内部行为
|
||||
|
||||
1. **启动**:`createSocket({ type: 'udp4', reuseAddr: true })` → `bind(7101)` → `addMembership('224.0.71.67')` → `setMulticastTTL(1)`
|
||||
2. **广播**:`setInterval(sendAnnounce, 3000)` + 启动时立即发一次
|
||||
3. **接收**:`socket.on('message')` → JSON.parse → 过滤 `proto !== 'claude-pipe-v1'` 和自身 → 更新 peers Map → 触发 `peer-discovered` 事件
|
||||
4. **清理**:`setInterval(cleanupStalePeers, 7500)` — 超过 15 秒未收到 announce 的 peer 从 Map 移除,触发 `peer-lost` 事件
|
||||
5. **停止**:清除所有 timer → `dropMembership` → `socket.close()` → 清空 peers
|
||||
|
||||
#### 错误处理
|
||||
|
||||
所有 socket/网络错误均为 **non-fatal**(logError 但不 throw)。multicast 在某些网络环境可能不支持,这不应阻止 CLI 正常运行。
|
||||
|
||||
---
|
||||
|
||||
### 3.5 Registry 扩展
|
||||
|
||||
**文件**: `src/utils/pipeRegistry.ts`
|
||||
|
||||
#### 类型变更
|
||||
|
||||
```typescript
|
||||
export interface PipeRegistryEntry {
|
||||
// ... 现有字段 ...
|
||||
tcpPort?: number // 新增:TCP 监听端口
|
||||
lanVisible?: boolean // 新增:是否参与 LAN 广播
|
||||
}
|
||||
```
|
||||
|
||||
#### 新增函数
|
||||
|
||||
```typescript
|
||||
export type MergedPipeEntry = {
|
||||
id: string
|
||||
pipeName: string
|
||||
role: string
|
||||
machineId: string
|
||||
ip: string
|
||||
hostname: string
|
||||
alive: boolean
|
||||
source: 'local' | 'lan' // 来源标识
|
||||
tcpEndpoint?: TcpEndpoint // LAN peer 的 TCP 端点
|
||||
}
|
||||
|
||||
export function mergeWithLanPeers(
|
||||
registry: PipeRegistry,
|
||||
lanPeers: Map<string, LanAnnounce>,
|
||||
): MergedPipeEntry[]
|
||||
```
|
||||
|
||||
**合并逻辑**:
|
||||
1. 先添加本地 registry 的 main 和所有 subs(`source: 'local'`)
|
||||
2. 遍历 LAN peers,跳过已在本地 registry 中存在的 pipeName
|
||||
3. 剩余的 LAN peers 作为 `source: 'lan'` 条目添加
|
||||
|
||||
---
|
||||
|
||||
### 3.6 Peer Address 扩展
|
||||
|
||||
**文件**: `src/utils/peerAddress.ts`
|
||||
|
||||
#### parseAddress 变更
|
||||
|
||||
```typescript
|
||||
// 之前
|
||||
export function parseAddress(to: string): {
|
||||
scheme: 'uds' | 'bridge' | 'other'
|
||||
target: string
|
||||
}
|
||||
|
||||
// 之后
|
||||
export function parseAddress(to: string): {
|
||||
scheme: 'uds' | 'bridge' | 'tcp' | 'other' // 新增 'tcp'
|
||||
target: string
|
||||
}
|
||||
```
|
||||
|
||||
新增 `tcp:` 前缀解析:`tcp:192.168.1.20:7100` → `{ scheme: 'tcp', target: '192.168.1.20:7100' }`
|
||||
|
||||
#### 新增 parseTcpTarget
|
||||
|
||||
```typescript
|
||||
export function parseTcpTarget(
|
||||
target: string,
|
||||
): { host: string; port: number } | null
|
||||
```
|
||||
|
||||
解析 `host:port` 字符串,正则 `^([^:]+):(\d+)$`。
|
||||
|
||||
---
|
||||
|
||||
### 3.7 REPL Bootstrap 集成
|
||||
|
||||
**文件**: `src/screens/REPL.tsx`
|
||||
|
||||
#### 启动阶段 (L5165-5200)
|
||||
|
||||
在现有 `createPipeServer(pipeName)` 调用处:
|
||||
|
||||
```typescript
|
||||
// 根据 LAN_PIPES flag 决定是否启用 TCP
|
||||
const server = await createPipeServer(
|
||||
pipeName,
|
||||
feature('LAN_PIPES') ? { enableTcp: true, tcpPort: 0 } : undefined
|
||||
);
|
||||
|
||||
// 启动 LAN beacon
|
||||
if (feature('LAN_PIPES') && server.tcpAddress) {
|
||||
const { LanBeacon } = require('../utils/lanBeacon.js');
|
||||
lanBeaconInstance = new LanBeacon({
|
||||
pipeName, machineId, hostname, ip, tcpPort: server.tcpAddress.port, role
|
||||
});
|
||||
lanBeaconInstance.start();
|
||||
|
||||
// Store beacon in module-level singleton (not on Zustand state)
|
||||
const { setLanBeacon } = require('../utils/lanBeacon.js');
|
||||
setLanBeacon(lanBeaconInstance);
|
||||
|
||||
// 注册 entry 时附带 tcpPort
|
||||
await registerAsMain({ ...entry, tcpPort: server.tcpAddress.port, lanVisible: true });
|
||||
}
|
||||
```
|
||||
|
||||
#### Heartbeat <20><>段
|
||||
|
||||
在 main heartbeat 循环中:
|
||||
|
||||
1. `refreshDiscoveredPipes(aliveSubs)` 同时包含本地 subs 和 LAN beacon peers
|
||||
2. auto-attach 循环同时遍历本地 subs 和 LAN peers(LAN peers 通过 TCP endpoint 连接)
|
||||
3. cleanup 时检查 LAN beacon peers 列表,避免误删 LAN 连接
|
||||
|
||||
```typescript
|
||||
// auto-attach 统一目标列表:本地 subs + LAN peers
|
||||
const attachTargets = [...aliveSubs.map(s => ({ pipeName: s.pipeName }))];
|
||||
if (feature('LAN_PIPES')) {
|
||||
const beacon = getLanBeacon();
|
||||
for (const [name, peer] of beacon.getPeers()) {
|
||||
attachTargets.push({ pipeName: name, tcpEndpoint: { host: peer.ip, port: peer.tcpPort } });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Cleanup 阶段
|
||||
|
||||
```typescript
|
||||
// 停止 LAN beacon
|
||||
const { getLanBeacon, setLanBeacon } = require('../utils/lanBeacon.js');
|
||||
const beacon = getLanBeacon();
|
||||
if (beacon) {
|
||||
try { beacon.stop(); } catch {}
|
||||
setLanBeacon(null);
|
||||
}
|
||||
```
|
||||
|
||||
**Beacon 存储方案**:使用 `lanBeacon.ts` 中的 module-level singleton(`getLanBeacon()`/`setLanBeacon()`),不挂在 Zustand store state 上,避免 `setState` 展开时丢失引用。
|
||||
|
||||
---
|
||||
|
||||
### 3.8 /pipes 命令 LAN 显示
|
||||
|
||||
**文件**: `src/commands/pipes/pipes.ts`
|
||||
|
||||
在现有 registry 显示之后,如果 `feature('LAN_PIPES')` 启用:
|
||||
|
||||
1. 通过 `getLanBeacon()` 获取 LAN peers
|
||||
2. 调用 `mergeWithLanPeers()` 合并
|
||||
3. 过滤 `source === 'lan'` 的条目
|
||||
4. 显示格式:`☐ [role] pipeName hostname/ip tcp:host:port [LAN]`
|
||||
|
||||
---
|
||||
|
||||
### 3.9 /attach 命令 TCP 支持
|
||||
|
||||
**文件**: `src/commands/attach/attach.ts`
|
||||
|
||||
在连接之前,如果 `feature('LAN_PIPES')` 启用:
|
||||
|
||||
1. 在 `discoveredPipes` 中查找目标 pipe
|
||||
2. 通过 `_lanBeacon.getPeers()` 检查是否为 LAN peer
|
||||
3. 如果是,构造 `TcpEndpoint` 传给 `connectToPipe()`
|
||||
4. 错误消息中包含 TCP 端点信息便于诊断
|
||||
|
||||
---
|
||||
|
||||
### 3.10 SendMessageTool TCP 支持
|
||||
|
||||
**文件**: `src/tools/SendMessageTool/SendMessageTool.ts`
|
||||
|
||||
#### inputSchema 描述更新
|
||||
|
||||
当 `LAN_PIPES` 启用时,`to` 字段描述追加 `, or "tcp:<host>:<port>" for a LAN peer`。
|
||||
|
||||
#### checkPermissions
|
||||
|
||||
```typescript
|
||||
if (feature('LAN_PIPES') && parseAddress(input.to).scheme === 'tcp') {
|
||||
return {
|
||||
behavior: 'ask',
|
||||
message: `Send a message to LAN peer ${input.to}?...`,
|
||||
decisionReason: {
|
||||
type: 'safetyCheck',
|
||||
reason: 'Cross-machine LAN message requires explicit user consent',
|
||||
classifierApprovable: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**安全设计**:`classifierApprovable: false` 确保自动模式不会跳过用户确认。
|
||||
|
||||
#### validateInput
|
||||
|
||||
新增 `tcp:` scheme 验证分支(与 `uds:` 类似,仅允许 plain text 消息)。
|
||||
|
||||
#### call()
|
||||
|
||||
```typescript
|
||||
if (addr.scheme === 'tcp' && feature('LAN_PIPES')) {
|
||||
const ep = parseTcpTarget(addr.target);
|
||||
const client = new PipeClient(input.to, `send-${process.pid}`, ep);
|
||||
await client.connect(5000);
|
||||
client.send({ type: 'chat', data: input.message });
|
||||
client.disconnect();
|
||||
return { data: { success: true, message: `... → TCP ${ep.host}:${ep.port}` } };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据流
|
||||
|
||||
### 4.1 LAN 发现流程
|
||||
|
||||
```
|
||||
CLI-A 启动
|
||||
→ PipeServer.start({ enableTcp: true, tcpPort: 0 })
|
||||
→ TCP server 监听 0.0.0.0:随机端口
|
||||
→ LanBeacon.start()
|
||||
→ 每 3s 广播 UDP announce (pipeName, ip, tcpPort, role, machineId)
|
||||
|
||||
CLI-B 启动 (另一台机器)
|
||||
→ 同上
|
||||
→ LanBeacon 收到 CLI-A 的 announce
|
||||
→ peer-discovered 事件
|
||||
→ Heartbeat 循环合并 LAN peers 到 discoveredPipes
|
||||
|
||||
用户在 CLI-B 执行 /pipes
|
||||
→ 显示 CLI-A 条目,标记 [LAN]
|
||||
```
|
||||
|
||||
### 4.2 跨机器 Attach 流程
|
||||
|
||||
```
|
||||
CLI-B 执行 /attach cli-abc12345
|
||||
→ feature('LAN_PIPES') → 查找 discoveredPipes → 找到 LAN peer
|
||||
→ _lanBeacon.getPeers() → 获取 { ip: '192.168.1.10', tcpPort: 7100 }
|
||||
→ connectToPipe(name, myName, undefined, { host: '192.168.1.10', port: 7100 })
|
||||
→ PipeClient.connectTcp() → net.createConnection({ host, port })
|
||||
→ client.send({ type: 'attach_request' })
|
||||
→ 等待 attach_accept / attach_reject
|
||||
→ 成功:注册 slave client,切换 master 角色
|
||||
```
|
||||
|
||||
### 4.3 跨机器消息发送
|
||||
|
||||
```
|
||||
用户或 AI 使用 SendMessageTool
|
||||
→ to: "tcp:192.168.1.20:7102"
|
||||
→ checkPermissions → behavior: 'ask' → 用户确认
|
||||
→ parseTcpTarget('192.168.1.20:7102') → { host, port }
|
||||
→ new PipeClient(to, sender, { host, port })
|
||||
→ client.connect(5000)
|
||||
→ client.send({ type: 'chat', data: message })
|
||||
→ client.disconnect()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 测试
|
||||
|
||||
### 5.1 新增测试文件
|
||||
|
||||
| 文件 | 测试数 | 覆盖内容 |
|
||||
|------|--------|----------|
|
||||
| `src/utils/__tests__/lanBeacon.test.ts` | 7 | socket 初始化、announce 发送、peer 发现、自身过滤、协议过滤、role 更新 |
|
||||
| `src/utils/__tests__/peerAddress.test.ts` | 8 | uds/bridge/tcp/other scheme 解析、parseTcpTarget 正确/异常 |
|
||||
|
||||
### 5.2 测试策略
|
||||
|
||||
- **lanBeacon.test.ts**:mock dgram 模块,验证 beacon 的发送/接收/清理逻辑
|
||||
- **peerAddress.test.ts**:纯函数测试,无外部依赖
|
||||
- **现有 pipeTransport.test.ts**:2 个现有测试继续通过(TCP 扩展不改变 UDS 行为)
|
||||
|
||||
### 5.3 测试结果
|
||||
|
||||
```
|
||||
全量测试:2190 pass / 0 fail / 130 files / 4.27s
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 变更文件清单
|
||||
|
||||
| 文件 | 操作 | 变更行数(约) |
|
||||
|------|------|-------------|
|
||||
| `scripts/dev.ts` | 修改 | +1 (feature flag) |
|
||||
| `build.ts` | 修改 | +1 (feature flag) |
|
||||
| `src/utils/pipeTransport.ts` | 修改 | +120 (TCP 扩展) |
|
||||
| `src/utils/lanBeacon.ts` | **新增** | ~170 (UDP beacon) |
|
||||
| `src/utils/pipeRegistry.ts` | 修改 | +80 (类型 + merge 函数) |
|
||||
| `src/utils/peerAddress.ts` | 修改 | +12 (tcp scheme + parseTcpTarget) |
|
||||
| `src/screens/REPL.tsx` | 修改 | +45 (bootstrap + heartbeat + cleanup) |
|
||||
| `src/commands/pipes/pipes.ts` | 修改 | +25 (LAN peers 显示) |
|
||||
| `src/commands/attach/attach.ts` | 修改 | +25 (TCP endpoint 解析) |
|
||||
| `src/tools/SendMessageTool/SendMessageTool.ts` | 修改 | +45 (tcp scheme 全链路) |
|
||||
| `src/utils/__tests__/lanBeacon.test.ts` | **新增** | ~140 (7 tests) |
|
||||
| `src/utils/__tests__/peerAddress.test.ts` | **新增** | ~60 (8 tests) |
|
||||
| `docs/features/lan-pipes.md` | **新增** | ~90 (用户文档) |
|
||||
|
||||
---
|
||||
|
||||
## 7. 已知限制和后续改进
|
||||
|
||||
### 7.1 当前限制
|
||||
|
||||
1. **无 TCP 认证**:TCP 连接无握手认证,同一局域网内任何知道端口号的进程都能连接
|
||||
2. **beacon ref 通过 `(state as any)._lanBeacon` 传递**:这是一个 pragmatic hack,因为 AppState 类型由 decompiled 代码定义,修改类型的成本过高
|
||||
3. **multicast 依赖网络环境**:部分企业网络、AP 隔离的 WiFi 可能不支持 multicast
|
||||
4. **TCP 端口随机**:每次启动分配不同端口,需依赖 beacon 发现
|
||||
|
||||
### 7.2 后续改进方向
|
||||
|
||||
1. **HMAC-SHA256 认证**:首次 TCP 握手交换 machineId + challenge token
|
||||
2. **heartbeat 中 TCP auto-attach LAN peers**:目前 heartbeat 只 auto-attach 本地 registry 的 subs,LAN peers 需手动 /attach
|
||||
3. **固定端口范围配置**:允许用户配置 TCP 端口范围,便于防火墙规则
|
||||
4. **mDNS/DNS-SD 作为 beacon 替代**:在 multicast 受限的环境提供更可靠的发现
|
||||
5. **加密传输**:TLS over TCP,确保消息不被中间人窃听
|
||||
|
||||
---
|
||||
|
||||
## 8. 防火墙要求
|
||||
|
||||
| 协议 | 端口 | 方向 | 用途 |
|
||||
|------|------|------|------|
|
||||
| UDP | 7101 | IN + OUT | Multicast beacon 发现 |
|
||||
| TCP | 动态 (0) | IN | PipeServer TCP 监听 |
|
||||
|
||||
### Windows
|
||||
|
||||
```powershell
|
||||
netsh advfirewall firewall add rule name="Claude LAN Beacon" dir=in action=allow protocol=UDP localport=7101
|
||||
netsh advfirewall firewall add rule name="Claude LAN Pipes" dir=in action=allow program="<bun路径>" enable=yes
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
首次运行时系统弹窗允许即可。
|
||||
|
||||
### Linux
|
||||
|
||||
```bash
|
||||
sudo firewall-cmd --add-port=7101/udp
|
||||
# TCP 端口随机,建议放行 bun 进程
|
||||
```
|
||||
86
docs/features/lan-pipes.md
Normal file
86
docs/features/lan-pipes.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# LAN Pipes — 局域网跨机器通讯
|
||||
|
||||
## 概述
|
||||
|
||||
在现有 UDS (Unix Domain Socket) 本地 Pipe 通讯基础上,增加 TCP 传输层和 UDP Multicast 发现机制,使同一局域网内不同机器上的 Claude Code 实例可以互相发现、连接和双向通讯。
|
||||
|
||||
## Feature Flag
|
||||
|
||||
`LAN_PIPES` — dev/build 默认启用。也可通过 `FEATURE_LAN_PIPES=1` 环境变量启用。
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
Machine A (192.168.1.10) Machine B (192.168.1.20)
|
||||
┌─────────────────────────┐ ┌─────────────────────────┐
|
||||
│ PipeServer │ │ PipeServer │
|
||||
│ UDS: cli-abc.sock │ │ UDS: cli-def.sock │
|
||||
│ TCP: 0.0.0.0:7100 │◄─TCP────►│ TCP: 0.0.0.0:7102 │
|
||||
├─────────────────────────┤ ├─────────────────────────┤
|
||||
│ LanBeacon │◄─UDP─────│ LanBeacon │
|
||||
│ multicast 224.0.71.67 │ mcast ►│ multicast 224.0.71.67 │
|
||||
└─────────────────────────┘ └─────────────────────────┘
|
||||
```
|
||||
|
||||
## 组件
|
||||
|
||||
### 1. PipeServer TCP 扩展 (`pipeTransport.ts`)
|
||||
|
||||
- `PipeServer.start()` 接受 `PipeServerOptions`,可选启用 TCP 监听
|
||||
- 内部维护两个 `net.Server` — UDS + TCP,共享同一组 clients 和 handlers
|
||||
- `PipeServer.tcpAddress` getter 返回 TCP 端口信息
|
||||
|
||||
### 2. PipeClient TCP 扩展 (`pipeTransport.ts`)
|
||||
|
||||
- 构造函数新增可选 `TcpEndpoint` 参数
|
||||
- `connect()` 根据是否有 TCP endpoint 选择连接模式
|
||||
- 对下游调用者完全透明
|
||||
|
||||
### 3. LAN Beacon (`lanBeacon.ts`)
|
||||
|
||||
- UDP multicast 组: `224.0.71.67:7101`
|
||||
- 每 3 秒广播 announce 包,包含 pipeName、machineId、hostname、ip、tcpPort、role
|
||||
- 15 秒无 announce 视为 peer lost
|
||||
- TTL=1,仅 link-local,不跨路由器
|
||||
|
||||
### 4. Registry 扩展 (`pipeRegistry.ts`)
|
||||
|
||||
- `PipeRegistryEntry` 新增 `tcpPort?` 和 `lanVisible?` 字段
|
||||
- `mergeWithLanPeers()` 合并本地 registry 和 LAN beacon 发现的远端 peers
|
||||
|
||||
### 5. Peer Address (`peerAddress.ts`)
|
||||
|
||||
- `parseAddress()` 新增 `tcp` scheme: `tcp:192.168.1.20:7100`
|
||||
- `parseTcpTarget()` 解析 `host:port` 字符串
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 查看 LAN Peers
|
||||
|
||||
```
|
||||
/pipes
|
||||
```
|
||||
|
||||
输出中会显示 `[LAN]` 标记的远端实例。
|
||||
|
||||
### 连接远端实例
|
||||
|
||||
```
|
||||
/attach <pipe-name>
|
||||
```
|
||||
|
||||
自动检测 LAN peer 并通过 TCP 连接。
|
||||
|
||||
### 发送消息到 LAN Peer
|
||||
|
||||
```
|
||||
/send tcp:192.168.1.20:7100 <message>
|
||||
```
|
||||
|
||||
或通过 SendMessage tool 使用 `tcp:` scheme。
|
||||
|
||||
## 安全
|
||||
|
||||
- TCP 连接需用户显式同意(checkPermissions 返回 `ask`)
|
||||
- Multicast TTL=1,仅限链路本地
|
||||
- 后续可增加 HMAC-SHA256 challenge 认证
|
||||
342
docs/features/pipes-and-lan.md
Normal file
342
docs/features/pipes-and-lan.md
Normal file
@@ -0,0 +1,342 @@
|
||||
# Pipes + LAN Pipes 完整功能指南
|
||||
|
||||
## 概述
|
||||
|
||||
Pipes 系统提供 Claude Code CLI 实例之间的通讯能力,分两层:
|
||||
|
||||
1. **Pipes(本机)**:同一台机器上的多个 CLI 实例通过 UDS(Unix Domain Socket / Windows Named Pipe)协作
|
||||
2. **LAN Pipes(局域网)**:不同机器上的 CLI 实例通过 TCP + UDP Multicast 协作
|
||||
|
||||
两层使用同一套协议(NDJSON)和同一套命令(`/pipes`、`/attach`、`/send` 等),对用户透明。
|
||||
|
||||
## Feature Flags
|
||||
|
||||
| Flag | 控制范围 | 默认 |
|
||||
|------|----------|------|
|
||||
| `UDS_INBOX` | 本机 Pipe IPC 全部功能 | dev/build 启用 |
|
||||
| `LAN_PIPES` | 局域网 TCP + beacon 扩展 | dev/build 启用 |
|
||||
|
||||
手动启用:`FEATURE_UDS_INBOX=1 FEATURE_LAN_PIPES=1 bun run dev`
|
||||
|
||||
## 快速上手
|
||||
|
||||
### 本机多实例
|
||||
|
||||
```bash
|
||||
# 终端 1
|
||||
bun run dev
|
||||
# 启动后自动注册为 main
|
||||
|
||||
# 终端 2
|
||||
bun run dev
|
||||
# 自动注册为 sub-1,被 main 自动 attach
|
||||
```
|
||||
|
||||
在终端 1 中输入 `/pipes`,可以看到两个实例。选中 sub-1 后,输入的消息会自动转发到 sub-1 执行。
|
||||
|
||||
### 局域网多机器
|
||||
|
||||
```bash
|
||||
# 机器 A (192.168.50.22)
|
||||
bun run dev
|
||||
|
||||
# 机器 B (192.168.50.27)
|
||||
bun run dev
|
||||
```
|
||||
|
||||
两边启动后等 3-5 秒(beacon 广播间隔),LAN peers 会自动发现并 attach。输入 `/pipes` 可看到标记 `[LAN]` 的远端实例。
|
||||
|
||||
### 防火墙配置(两台机器都需要)
|
||||
|
||||
**Windows**(管理员 PowerShell):
|
||||
```powershell
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Beacon (UDP)" -Direction Inbound -Protocol UDP -LocalPort 7101 -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Pipes (TCP)" -Direction Inbound -Protocol TCP -LocalPort 1024-65535 -Program (Get-Command bun).Source -Action Allow -Profile Private
|
||||
New-NetFirewallRule -DisplayName "Claude Code LAN Beacon Out (UDP)" -Direction Outbound -Protocol UDP -RemotePort 7101 -Action Allow -Profile Private
|
||||
# 确认网络为"专用":Get-NetConnectionProfile
|
||||
```
|
||||
|
||||
**macOS**(首次运行时系统弹出对话框,点击"允许"即可):
|
||||
```bash
|
||||
# 如果需要手动放行 pf 防火墙:
|
||||
echo "pass in proto udp from any to any port 7101" | sudo pfctl -ef -
|
||||
```
|
||||
|
||||
**Linux**(firewalld / iptables):
|
||||
```bash
|
||||
# firewalld
|
||||
sudo firewall-cmd --zone=trusted --add-port=7101/udp --permanent
|
||||
sudo firewall-cmd --zone=trusted --add-port=1024-65535/tcp --permanent
|
||||
sudo firewall-cmd --reload
|
||||
|
||||
# 或 iptables
|
||||
sudo iptables -A INPUT -p udp --dport 7101 -j ACCEPT
|
||||
sudo iptables -A INPUT -p tcp --dport 1024:65535 -m owner --uid-owner $(id -u) -j ACCEPT
|
||||
```
|
||||
|
||||
确认:网络为局域网(非公共 WiFi),路由器未开启 AP 隔离。
|
||||
|
||||
## 交互面板与快捷键
|
||||
|
||||
### 状态栏
|
||||
|
||||
执行 `/pipes` 后,输入框底部出现 pipe 状态栏(单行):
|
||||
|
||||
```
|
||||
pipe: cli-a91bad56 (main) 192.168.50.22 2/3 selected selected pipes only · ←/→ or m switch · Shift+↓ edit
|
||||
```
|
||||
|
||||
状态栏始终可见(直到会话结束),显示:当前 pipe 名、角色、IP、已选数/总数、路由模式。
|
||||
|
||||
### 展开选择面板
|
||||
|
||||
按 **Shift+↓**(Shift + 下箭头)展开选择面板:
|
||||
|
||||
```
|
||||
pipe: cli-a91bad56 (main) 192.168.50.22 ↑↓ move Space select ←/→ or m route Enter/Esc close Shift+↓ toggle
|
||||
当前普通 prompt 走 已选 sub;切换不会清空选择
|
||||
☑ cli-da029538 (sub-1 XC/192.168.50.22)
|
||||
☐ cli-04d67950 (main vmwin11/192.168.50.27)
|
||||
☑ cli-893747d3 [offline] (sub-2 vmwin11/192.168.50.27)
|
||||
```
|
||||
|
||||
### 面板内快捷键
|
||||
|
||||
| 快捷键 | 场景 | 作用 |
|
||||
|--------|------|------|
|
||||
| **Shift+↓** | 状态栏可见时 | 展开/收起选择面板 |
|
||||
| **↑ / ↓** | 面板展开时 | 上下移动光标 |
|
||||
| **Space** | 面板展开时 | 切换当前光标所在 pipe 的选中状态(☑ ↔ ☐) |
|
||||
| **Enter** | 面板展开时 | 确认并关闭面板 |
|
||||
| **Esc** | 面板展开时 | 取消并关闭面板 |
|
||||
| **← / → 或 M** | 状态栏可见且有选中 pipe 时 | 切换路由模式(`selected pipes only` ↔ `local main`) |
|
||||
|
||||
### M 键 — 路由模式切换
|
||||
|
||||
M 键(或 ← / →)用于在两种路由模式之间切换,**无需展开面板**:
|
||||
|
||||
| 模式 | 状态栏显示 | 行为 |
|
||||
|------|-----------|------|
|
||||
| `selected pipes only` | 绿色高亮 | 输入的 prompt **仅**发送到选中的 pipe,本地不执行 |
|
||||
| `local main` | 灰色 | 输入的 prompt 在**本地 main** 执行,不转发到任何 pipe |
|
||||
|
||||
切换路由模式**不会清空选择**。你可以在 `local main` 模式下保持选择,随时按 M 切回 `selected pipes only` 继续向远端发送。
|
||||
|
||||
### 完整操作流程示例
|
||||
|
||||
```
|
||||
1. 输入 /pipes → 状态栏出现,显示发现的实例
|
||||
2. 按 Shift+↓ → 展开选择面板
|
||||
3. 按 ↓ 移动到目标 pipe → 光标移到 cli-04d67950
|
||||
4. 按 Space → 选中 ☑ cli-04d67950
|
||||
5. 按 Enter → 确认,面板收起
|
||||
6. 输入 "帮我检查 git status" → prompt 自动发送到 cli-04d67950 执行
|
||||
7. 按 M → 切换到 local main 模式
|
||||
8. 输入 "本地做点什么" → 仅在本地执行
|
||||
9. 按 M → 切回 selected pipes only
|
||||
10. 输入 "继续远端任务" → 又发送到 cli-04d67950
|
||||
```
|
||||
|
||||
## 命令参考
|
||||
|
||||
### /pipes
|
||||
|
||||
显示所有发现的实例,管理选择状态。再次执行 `/pipes` 切换面板展开/收起。
|
||||
|
||||
```
|
||||
/pipes — 显示所有实例 + 切换选择面板
|
||||
/pipes select <name> — 选中某实例(消息会广播到它)
|
||||
/pipes deselect <name> — 取消选中
|
||||
/pipes all — 全选
|
||||
/pipes none — 全部取消
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
Your pipe: cli-a91bad56
|
||||
Role: main
|
||||
Machine ID: 205d6c3a...
|
||||
IP: 192.168.50.22
|
||||
Host: XC
|
||||
|
||||
Main machine: 205d6c3a... (this machine)
|
||||
[main] cli-a91bad56 XC/192.168.50.22 [alive] (you)
|
||||
☑ [sub-1] cli-da029538 XC/192.168.50.22 [alive] [connected]
|
||||
|
||||
LAN Peers:
|
||||
☐ [main] cli-04d67950 vmwin11/192.168.50.27 tcp:192.168.50.27:58853 [LAN]
|
||||
|
||||
Selected: cli-da029538
|
||||
```
|
||||
|
||||
### /attach <name>
|
||||
|
||||
手动 attach 到一个实例,使其成为你的 slave。
|
||||
|
||||
```
|
||||
/attach cli-04d67950 — 连接到指定 pipe(自动解析 LAN TCP 端点)
|
||||
```
|
||||
|
||||
attach 后,对方变为 slave,你变为 master。可以向它发送 prompt。通常不需要手动 attach——heartbeat 会自动发现并连接。
|
||||
|
||||
### /detach <name>
|
||||
|
||||
断开与某个 slave 的连接。
|
||||
|
||||
```
|
||||
/detach cli-04d67950
|
||||
```
|
||||
|
||||
### /send <name> <message>
|
||||
|
||||
向指定 pipe 发送消息(不依赖选择状态,直接指定目标)。
|
||||
|
||||
```
|
||||
/send cli-04d67950 请帮我检查一下日志
|
||||
/send tcp:192.168.50.27:58853 hello — 直接通过 TCP 地址发送
|
||||
```
|
||||
|
||||
### /claim-main
|
||||
|
||||
强制声明当前机器为 main(用于 main 意外退出后的恢复)。
|
||||
|
||||
## 消息路由
|
||||
|
||||
### 选中 pipe 后的自动路由
|
||||
|
||||
1. 通过 `/pipes select` 或 Shift+Down 面板选中一个或多个 pipe
|
||||
2. 在输入框中正常输入消息
|
||||
3. 消息自动发送到所有选中的已连接 pipe
|
||||
4. 每个 pipe 独立执行,结果流式回传到 main 的消息列表
|
||||
|
||||
### 路由模式
|
||||
|
||||
| 模式 | 行为 |
|
||||
|------|------|
|
||||
| `selected`(默认) | 消息发送到选中的 pipe |
|
||||
| `local` | 消息仅在本地执行,不转发 |
|
||||
|
||||
## 架构
|
||||
|
||||
### 通信协议
|
||||
|
||||
所有通讯使用 NDJSON(Newline-Delimited JSON),每行一个消息:
|
||||
|
||||
```json
|
||||
{"type":"ping","from":"cli-abc","ts":"2026-04-11T00:00:00.000Z"}
|
||||
{"type":"prompt","data":"帮我查看 git status","from":"cli-abc","ts":"..."}
|
||||
{"type":"stream","data":"正在执行...","from":"cli-def","ts":"..."}
|
||||
{"type":"done","data":"","from":"cli-def","ts":"..."}
|
||||
```
|
||||
|
||||
### 消息类型
|
||||
|
||||
| 类型 | 方向 | 说明 |
|
||||
|------|------|------|
|
||||
| `ping`/`pong` | 双向 | 健康检查 |
|
||||
| `attach_request`/`accept`/`reject` | M→S/S→M | 连接控制 |
|
||||
| `detach` | M→S | 断开连接 |
|
||||
| `prompt` | M→S | 主向从发送 prompt |
|
||||
| `prompt_ack` | S→M | 从确认接收 |
|
||||
| `stream` | S→M | 从流式回传 AI 输出 |
|
||||
| `tool_start`/`tool_result` | S→M | 工具执行通知 |
|
||||
| `done` | S→M | 本轮完成 |
|
||||
| `error` | 双向 | 错误通知 |
|
||||
| `permission_request`/`response`/`cancel` | 双向 | 权限审批转发 |
|
||||
|
||||
### 传输层
|
||||
|
||||
```
|
||||
本机 LAN
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ PipeServer │ │ PipeServer │
|
||||
│ UDS sock │ │ UDS sock │
|
||||
│ TCP :rand │◄───TCP───►│ TCP :rand │
|
||||
├──────────────┤ ├──────────────┤
|
||||
│ LanBeacon │◄──UDP────►│ LanBeacon │
|
||||
│ 224.0.71.67 │ mcast │ 224.0.71.67 │
|
||||
└──────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
- **UDS**:本机实例间通讯,通过文件系统路径寻址(`~/.claude/pipes/cli-xxx.sock`)
|
||||
- **TCP**:LAN 实例间通讯,动态端口,通过 beacon 发现
|
||||
- **UDP Multicast**:peer 发现,3 秒广播一次 announce 包
|
||||
|
||||
### 角色模型
|
||||
|
||||
| 角色 | 说明 |
|
||||
|------|------|
|
||||
| `main` | 首个启动的实例,管理 registry |
|
||||
| `sub` | 后续启动的同机实例(或被 attach 的 LAN 实例) |
|
||||
| `master` | attach 了至少一个 slave 的实例 |
|
||||
| `slave` | 被 master attach 控制的实例 |
|
||||
|
||||
角色转换:
|
||||
- 首个启动 → `main`
|
||||
- 同机后续启动 → `sub`(自动被 main attach → `slave`)
|
||||
- LAN 发现 → 两边都是 `main`,heartbeat 自动互相 attach
|
||||
- 被 attach → 变为 `slave`(可通过 `/detach` 恢复)
|
||||
|
||||
### 发现机制
|
||||
|
||||
**本机**:通过 `~/.claude/pipes/registry.json` 文件(带文件锁),`machineId` 绑定主机身份。
|
||||
|
||||
**LAN**:通过 UDP multicast beacon:
|
||||
1. 每 3 秒广播 `{ proto, pipeName, machineId, ip, tcpPort, role }`
|
||||
2. 收到其他实例的 announce → 记入 peers Map
|
||||
3. 15 秒未收到 → 标记 peer lost
|
||||
4. Heartbeat 合并 local registry + beacon peers → 统一 attach 目标列表
|
||||
|
||||
### Heartbeat 循环(5 秒间隔)
|
||||
|
||||
```
|
||||
main/master 角色:
|
||||
1. cleanupStaleEntries() — 清理 registry 中死掉的条目
|
||||
2. getAliveSubs() — 获取存活的本地 subs
|
||||
3. refreshDiscoveredPipes() — 刷新 discoveredPipes(包含 LAN peers)
|
||||
4. 合并 LAN peers 到 state
|
||||
5. 构建统一 attach 目标列表 — 本地 subs + LAN peers
|
||||
6. 遍历未连接的目标 → 自动 attach
|
||||
7. 清理断开的 slave 连接 — 同时检查 local registry 和 beacon
|
||||
|
||||
sub 角色:
|
||||
1. 检测 main 是否存活
|
||||
2. main 死亡 → 同机则接管 main 角色,跨机则独立
|
||||
```
|
||||
|
||||
## 关键文件
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/utils/pipeTransport.ts` | PipeServer(双模 UDS+TCP)、PipeClient、类型定义 |
|
||||
| `src/utils/lanBeacon.ts` | UDP multicast beacon、singleton 管理 |
|
||||
| `src/utils/pipeRegistry.ts` | Registry CRUD、角色判定、machineId、LAN merge |
|
||||
| `src/utils/peerAddress.ts` | 地址解析(uds:/bridge:/tcp: scheme) |
|
||||
| `src/screens/REPL.tsx` | Bootstrap、heartbeat、cleanup、prompt 路由 |
|
||||
| `src/hooks/useMasterMonitor.ts` | Slave client registry、消息订阅 |
|
||||
| `src/hooks/useSlaveNotifications.ts` | Slave 端通知处理 |
|
||||
| `src/commands/pipes/pipes.ts` | /pipes 命令 |
|
||||
| `src/commands/attach/attach.ts` | /attach 命令 |
|
||||
| `src/commands/send/send.ts` | /send 命令 |
|
||||
| `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 发送结果/请求
|
||||
@@ -8,7 +8,6 @@
|
||||
| Feature | 引用 | 状态 | 类别 | 简要说明 |
|
||||
|---------|------|------|------|---------|
|
||||
| CHICAGO_MCP | 16 | N/A | 内部基础设施 | Anthropic 内部 MCP 基础设施,非外部可用 |
|
||||
| UDS_INBOX | 17 | Stub | 消息通信 | Unix 域套接字对等消息,进程间消息传递 |
|
||||
| MONITOR_TOOL | 13 | Stub | 工具 | 文件/进程监控工具,检测变更并通知 |
|
||||
| BG_SESSIONS | 11 | Stub | 会话管理 | 后台会话管理,支持多会话并行 |
|
||||
| SHOT_STATS | 10 | 无实现 | 统计 | 逐 prompt 统计信息收集 |
|
||||
@@ -68,7 +67,7 @@ BUILDING_CLAUDE_APPS, ANTI_DISTILLATION_CC, AGENT_TRIGGERS, ABLATION_BASELINE
|
||||
这些 feature 被列为 Tier 3 的原因:
|
||||
|
||||
1. **内部基础设施**(CHICAGO_MCP, LODESTONE):Anthropic 内部使用,外部无法运行
|
||||
2. **纯 Stub 且引用低**(UDS_INBOX, MONITOR_TOOL, BG_SESSIONS):需要大量工作才能实现
|
||||
2. **纯 Stub 且引用低**(MONITOR_TOOL, BG_SESSIONS):需要大量工作才能实现
|
||||
3. **实验性功能**(SHOT_STATS, EXTRACT_MEMORIES):尚在概念阶段
|
||||
4. **辅助功能**(STREAMLINED_OUTPUT, HOOK_PROMPTS):影响范围小
|
||||
5. **CCR 系列**:依赖远程控制基础设施,需要 BRIDGE_MODE 先完善
|
||||
|
||||
114
docs/features/uds-inbox.md
Normal file
114
docs/features/uds-inbox.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# UDS_INBOX / pipes
|
||||
|
||||
## 概述
|
||||
|
||||
`UDS_INBOX` 现在不是一个“空壳 flag”,而是一套已经落地的本机 IPC 能力。但它同时承载了两层不同目标,必须拆开理解:
|
||||
|
||||
1. **UDS peer messaging**
|
||||
- 面向任意 Claude Code 进程。
|
||||
- 使用 `src/utils/udsMessaging.ts` 和 `src/utils/udsClient.ts`。
|
||||
- 对外入口是 `/peers` 和 `SendMessageTool` 的 `uds:<socket-path>` 地址。
|
||||
2. **pipes control plane**
|
||||
- 面向交互式 REPL 会话之间的主从协作。
|
||||
- 使用 `src/utils/pipeTransport.ts`、`src/utils/pipeRegistry.ts` 和 `src/screens/REPL.tsx` 中的内联 bootstrap。
|
||||
- 对外入口是 `/pipes`、`/attach`、`/detach`、`/send`、`/pipe-status`、`/history`、`/claim-main`。
|
||||
|
||||
这两层都依赖本机 socket,但职责不同。`/peers` 解决“找到其他会话并发消息”,`/pipes` 解决“把一个 REPL 变成另一个 REPL 的受控 worker”。
|
||||
|
||||
## 为什么要有单独的 `pipes`
|
||||
|
||||
单独的 `pipes` 层有三个实际理由:
|
||||
|
||||
1. **命名与角色模型不同**
|
||||
- UDS peer 层按 `messagingSocketPath` 寻址。
|
||||
- pipes 层按 `cli-xxxxxxxx` 会话名、`main/sub/master/slave` 角色和 `machineId` 注册表工作。
|
||||
2. **交互语义不同**
|
||||
- peer 层是通用消息投递。
|
||||
- pipes 层需要 attach、detach、历史收集、选择性广播、状态栏和 REPL 快捷键。
|
||||
3. **UI 集成不同**
|
||||
- peer 层主要服务工具调用。
|
||||
- pipes 层直接影响 REPL 提交路径和 PromptInput 页脚。
|
||||
|
||||
如果把两者硬合并,`SendMessageTool` 的通用寻址和 REPL 的主从控制会互相污染,命令语义也会变得混乱。
|
||||
|
||||
## 当前通信模型
|
||||
|
||||
### 1. UDS peer messaging
|
||||
|
||||
- 服务端:`src/utils/udsMessaging.ts`
|
||||
- 客户端:`src/utils/udsClient.ts`
|
||||
- 发现方式:读取 `~/.claude/sessions/*.json`
|
||||
- 地址方式:`uds:<socket-path>`
|
||||
- 传输方式:**本机 Unix socket / Windows named pipe**
|
||||
|
||||
这层是真正的“通用收件箱”。
|
||||
|
||||
### 2. pipes control plane
|
||||
|
||||
- 服务端/客户端:`src/utils/pipeTransport.ts`
|
||||
- 注册表:`src/utils/pipeRegistry.ts`
|
||||
- 生效入口:`src/screens/REPL.tsx`
|
||||
- 发现方式:扫描 `~/.claude/pipes/` + `registry.json`
|
||||
- 会话名:`cli-${sessionId.slice(0, 8)}`
|
||||
- 传输方式:**本机 Unix socket / Windows named pipe**
|
||||
|
||||
这层是真正的“主从 REPL 协调平面”。
|
||||
|
||||
## 关于“局域网通信”的事实
|
||||
|
||||
当前实现**不是**真正的局域网传输。
|
||||
|
||||
代码里虽然保存了这些字段:
|
||||
|
||||
- `localIp`
|
||||
- `hostname`
|
||||
- `machineId`
|
||||
- `mac`
|
||||
|
||||
但这些字段当前只用于:
|
||||
|
||||
1. 注册表展示
|
||||
2. main/sub 身份判定
|
||||
3. `claim-main` 的机器级归属切换
|
||||
4. 状态输出与排障信息
|
||||
|
||||
它们**没有**被用于创建 TCP/WebSocket 连接。真正的传输仍然是 `getPipePath(name)` 返回的本机 socket 路径。
|
||||
|
||||
所以目前更准确的描述应该是:
|
||||
|
||||
- `pipes` 支持 **本机多实例协作**
|
||||
- `registry` 带有 **机器身份元数据**
|
||||
- 但 **尚未实现跨机器局域网 transport**
|
||||
|
||||
如果未来要做真局域网版本,至少还需要:
|
||||
|
||||
1. TCP/WebSocket transport
|
||||
2. 认证与会话授权
|
||||
3. 发现与地址交换
|
||||
4. 超时、重连和安全边界
|
||||
|
||||
## 当前 REPL 行为
|
||||
|
||||
当前线上行为由 `src/screens/REPL.tsx` 的内联实现负责:
|
||||
|
||||
1. 启动时创建当前 REPL 的 pipe server
|
||||
2. 通过 `pipeRegistry` 判定 `main` / `sub`
|
||||
3. 处理 `attach_request` / `detach` / `prompt`
|
||||
4. 主实例心跳探测并维护 `slaves`
|
||||
5. `/pipes` 打开状态栏并维护选择器
|
||||
6. 提交普通消息时,仅向**已连接**的 selected pipes 广播
|
||||
|
||||
最近的收敛点:
|
||||
|
||||
- 过去遗留了一套未接线的 hook 方案
|
||||
- 当前已明确以 `REPL.tsx` 内联 bootstrap 为唯一生效实现
|
||||
- 选中但未连接的 pipe 不再导致本地处理被错误跳过
|
||||
|
||||
## 文档与代码对齐约定
|
||||
|
||||
后续关于 `UDS_INBOX` / `pipes` 的说明应遵守以下表述:
|
||||
|
||||
1. 默认称为“本机 IPC / 本机多实例协作”
|
||||
2. 不把 `localIp` / `hostname` 元数据表述成已完成的 LAN transport
|
||||
3. 明确区分 `/peers` 和 `/pipes` 的两层职责
|
||||
4. 以 `src/screens/REPL.tsx`、`src/utils/pipeTransport.ts`、`src/utils/pipeRegistry.ts` 为事实来源
|
||||
Reference in New Issue
Block a user