cc-haha/docs/desktop/02-architecture.md
程序员阿江(Relakkes) 7ce39d099c docs(desktop): add comprehensive desktop app documentation with illustrations
添加桌面端完整文档体系,包含快速上手、架构设计、功能详解三篇渐进式文档,
涵盖 Tauri 三层架构、WebSocket 通信协议、适配器桥接、状态管理等核心内容,
配 8 张深色主题技术配图。
2026-04-10 09:39:08 +08:00

27 KiB
Raw Blame History

Claude Code 桌面端 — 架构设计

从 Tauri 窗口到 CLI 子进程,深入理解桌面端的三层通信架构。

技术栈 · 三层架构 · WebSocket 协议 · HTTP API · 状态管理 · 会话生命周期 · 协议转换 · 适配器架构

技术栈全景


一、技术栈全景

Claude Code 桌面端的技术选型强调轻量、高性能、跨平台

前端层

技术 版本 职责
React 18.3.1 UI 框架,函数式组件 + Hooks
Zustand 5.0.3 轻量级状态管理,零样板代码
Vite 6.0.7 构建工具HMR 热更新
Tailwind CSS 4.0 原子化样式框架
Shiki 4.0.2 VS Code 级别代码语法高亮
react-diff-viewer 4.2.0 Diff 变更展示
marked 15.0.7 Markdown 解析渲染

桌面层

技术 版本 职责
Tauri 2.x 跨平台桌面框架Rust
tauri-plugin-shell 2.x Sidecar 进程管理
tauri-plugin-dialog 2.x 原生文件选择对话框
tauri-plugin-process 2.x 进程生命周期管理

服务端层

技术 职责
Bun JavaScript/TypeScript 运行时 + 构建工具
Bun.serve HTTP/WebSocket 服务器
Zod 请求参数校验

字体系统

桌面端自托管所有字体,无需 CDN 依赖:

  • Inter (400-600) — 正文
  • Manrope (400-800) — 标题
  • JetBrains Mono — 代码
  • Material Symbols Outlined — 图标

二、三层架构

三层架构图

桌面端采用三层架构设计,各层职责明确:

┌─────────────────────────────────┐
│         Tauri 主进程 (Rust)       │
│  ┌───────────────────────────┐  │
│  │   WebView (React App)     │  │  ← 用户看到的界面
│  │   Port 1420 (开发) / 内置  │  │
│  └───────────┬───────────────┘  │
│              │ HTTP + WebSocket  │
│  ┌───────────▼───────────────┐  │
│  │   Sidecar: claude-server  │  │  ← Bun 编译的服务端二进制
│  │   Port: 动态分配           │  │
│  └───────────┬───────────────┘  │
│              │ 子进程 spawn      │
│  ┌───────────▼───────────────┐  │
│  │   CLI 子进程 (claude-cli)  │  │  ← 实际执行 AI 对话和工具调用
│  │   stdin/stdout 通信        │  │
│  └───────────────────────────┘  │
└─────────────────────────────────┘

第一层Tauri 主进程

职责:窗口管理、进程编排、原生 API 桥接

关键文件:desktop/src-tauri/src/lib.rs

// 核心功能
fn reserve_local_port() -> u16    // 获取空闲端口
fn wait_for_server(port) -> bool  // 等待服务就绪(超时 10 秒)
fn start_server_sidecar(port)     // 启动 Server 后台进程
fn stop_server_sidecar()          // 停止 Server 后台进程

// Tauri 命令(暴露给前端调用)
#[tauri::command]
fn get_server_url() -> String     // 返回 http://127.0.0.1:{port}

启动流程:

  1. Tauri 主进程启动
  2. 调用 reserve_local_port() 获取空闲端口
  3. 启动 claude-server sidecar 进程,传入端口号
  4. 调用 wait_for_server() 轮询等待服务就绪
  5. WebView 加载 React 应用
  6. 前端调用 get_server_url 获取服务地址

第二层Server Sidecar

职责HTTP API + WebSocket 网关 + 会话管理 + 代理转换

关键文件:src/server/index.ts

// 服务器启动
Bun.serve({
  port: config.port,
  fetch(req, server) {
    // HTTP 路由 + WebSocket 升级
    if (url.pathname.startsWith('/ws/')) {
      server.upgrade(req, { data: { sessionId } })
      return
    }
    return router.handle(req)
  },
  websocket: {
    open(ws) { /* 连接建立 */ },
    message(ws, msg) { /* 消息处理 */ },
    close(ws) { /* 连接关闭 */ }
  }
})

Server 提供两类服务:

  • HTTP REST API — 会话管理、配置读写、模型操作
  • WebSocket — 实时消息通道(对话流、权限请求、状态推送)

第三层CLI 子进程

职责AI 对话核心逻辑、工具执行、Agent 编排

Server 为每个 Session 创建一个 CLI 子进程:

// 创建 CLI 子进程
const child = spawn('claude-cli', ['--session', sessionId], {
  cwd: workDir,
  env: { ...process.env, ANTHROPIC_AUTH_TOKEN: token }
})

// 通过 stdin/stdout 通信
child.stdin.write(JSON.stringify({ type: 'user_message', content }))
child.stdout.on('data', (chunk) => {
  // 解析 stream_event转发给 WebSocket 客户端
})

Sidecar 构建

使用 Bun 将 TypeScript 编译为独立可执行文件:

// desktop/scripts/build-sidecars.ts
// 支持 8 种目标平台:
const targets = [
  'bun-darwin-arm64',      // M1/M2 Mac
  'bun-darwin-x64',        // Intel Mac
  'bun-windows-x64',       // Windows x64
  'bun-windows-arm64',     // Windows ARM
  'bun-linux-x64',         // Linux x64
  'bun-linux-arm64',       // Linux ARM
  'bun-linux-x64-musl',    // Alpine Linux
  'bun-linux-arm64-musl',  // Alpine ARM
]

编译产物放置在 desktop/src-tauri/binaries/Tauri 打包时自动包含。


三、WebSocket 通信协议

WebSocket 通信协议

WebSocket 是桌面端与服务端实时通信的核心通道。

连接建立

ws://127.0.0.1:{port}/ws/{sessionId}

前端通过 wsManager(单例)管理所有 WebSocket 连接:

// desktop/src/api/websocket.ts
class WebSocketManager {
  private connections = new Map<string, WebSocket>()
  
  connect(sessionId: string): void {
    const ws = new WebSocket(`${baseUrl}/ws/${sessionId}`)
    // 自动重连(指数退避)
    // 消息队列(连接未就绪时缓冲)
    // 心跳 ping30 秒间隔)
  }
}

客户端 → 服务端消息

type 字段 说明
user_message content, attachments? 用户发送消息
permission_response requestId, allowed, rule? 权限审批响应
set_permission_mode mode 切换权限模式
stop_generation 停止当前生成
ping 心跳保活

服务端 → 客户端消息

type 关键字段 说明
connected sessionId 连接成功确认
status state, verb?, elapsed? 状态变更thinking/generating
content_start blockType 内容块开始text/tool_use
content_delta text?, toolInput? 增量内容(流式文本)
thinking text Extended Thinking 内容
tool_use_complete toolName, input 工具调用准备就绪
tool_result output, isError 工具执行结果
permission_request toolName, input, requestId 权限请求
message_complete usage: TokenUsage 消息完成(含 Token 统计)
error message, code 错误通知
session_title_updated title 会话标题更新
team_update team 团队状态变更
team_created / team_deleted teamName 团队创建/删除
task_update task 任务状态变更
pong 心跳响应

完整消息流示例

一条用户消息从发送到收到完整回复的流程:

用户输入 "写一个排序函数"
    ↓
ChatInput → chatStore.sendMessage()
    ↓
wsManager.send({ type: 'user_message', content: '...' })
    ↓
Server 收到 → 转发到 CLI 子进程
    ↓
CLI 开始处理,流式返回事件:

← { type: 'status', state: 'thinking' }
   → UI 显示"思考中..."动画

← { type: 'thinking', text: '让我分析...' }
   → ThinkingBlock 展示推理过程

← { type: 'content_start', blockType: 'text' }
   → 准备接收文本内容

← { type: 'content_delta', text: 'def sort(' }
← { type: 'content_delta', text: 'arr):\n' }
← { type: 'content_delta', text: '  return sorted(arr)' }
   → AssistantMessage 流式渲染,闪烁光标

← { type: 'content_start', blockType: 'tool_use' }
   → 准备接收工具调用

← { type: 'tool_use_complete', toolName: 'Write', input: {...} }
   → ToolCallBlock 展示文件写入操作

← { type: 'permission_request', toolName: 'Write', requestId: '...' }
   → PermissionDialog 弹出,等待用户审批

用户点击"允许"
    ↓
→ { type: 'permission_response', requestId: '...', allowed: true }

← { type: 'tool_result', output: '文件已写入' }
   → ToolResultBlock 展示结果

← { type: 'message_complete', usage: { input: 1200, output: 350 } }
   → 更新 Token 统计,清除流式状态

连接管理

心跳保活:每 30 秒发送 ping,服务端回复 pong

自动重连:指数退避策略

重连间隔 = min(1000ms × 2^(attempt-1), 30000ms)
最大重连次数 = 10

消息缓冲WebSocket 未就绪时,消息暂存队列,连接恢复后自动发送。


四、HTTP API 端点

Server 提供完整的 RESTful API供前端和适配器调用。

会话管理

方法 端点 说明
GET /api/sessions 获取会话列表
POST /api/sessions 创建新会话
GET /api/sessions/:id 获取会话详情
PATCH /api/sessions/:id 重命名会话
DELETE /api/sessions/:id 删除会话
GET /api/sessions/:id/messages 获取历史消息
GET /api/sessions/:id/git-info 获取 Git 信息
GET /api/sessions/:id/slash-commands 获取可用斜杠命令
GET /api/sessions/recent-projects 最近项目列表

模型与提供商

方法 端点 说明
GET /api/models 可用模型列表
GET /api/models/current 当前使用的模型
POST /api/models/:id/set-current 切换模型
GET/POST /api/models/effort 获取/设置 Effort 级别
GET /api/providers 提供商列表
POST /api/providers 创建提供商
PUT /api/providers/:id 更新提供商配置
DELETE /api/providers/:id 删除提供商
POST /api/providers/:id/activate 激活提供商
POST /api/providers/:id/test 测试连接
GET /api/providers/presets 获取预设列表

定时任务

方法 端点 说明
GET /api/scheduled-tasks 任务列表
POST /api/scheduled-tasks 创建任务
PUT /api/scheduled-tasks/:id 更新任务
DELETE /api/scheduled-tasks/:id 删除任务
POST /api/scheduled-tasks/:id/run 手动运行
GET /api/scheduled-tasks/runs 所有运行记录
GET /api/scheduled-tasks/:id/runs 单任务运行记录

Agent 团队

方法 端点 说明
GET /api/teams 团队列表
GET /api/teams/:name 团队详情
GET /api/teams/:name/members/:agentId/transcript 成员转录
DELETE /api/teams/:name 删除团队

其他

方法 端点 说明
GET /health 健康检查
GET/POST /api/settings/permission-mode 权限模式
GET /api/agents Agent 定义列表
GET /api/skills 技能列表
GET /api/skills/detail 技能详情
GET/PUT /api/adapters 适配器配置

五、状态管理

会话生命周期

桌面端使用 Zustand 进行状态管理,按领域拆分为多个独立 Store。

Store 架构

stores/
├── chatStore.ts          # 聊天状态(消息、流式、权限)  20KB
├── sessionStore.ts       # 会话列表、创建、删除
├── tabStore.ts           # 标签页管理、顺序、持久化    5KB
├── settingsStore.ts      # 全局设置(模型、权限、语言)
├── providerStore.ts      # LLM 提供商配置
├── uiStore.ts            # UI 状态主题、Toast、模态框
├── taskStore.ts          # 定时任务管理
├── teamStore.ts          # Agent 团队状态
├── agentStore.ts         # Agent 定义管理
├── skillStore.ts         # 技能库
├── adapterStore.ts       # 适配器配置
└── cliTaskStore.ts       # CLI 任务状态

chatStore 核心设计

chatStore 是最复杂的 Store每个 Session 维护独立状态

type PerSessionState = {
  messages: UIMessage[]                     // 消息列表
  chatState: 'idle' | 'thinking' | ...     // 对话状态
  connectionState: ConnectionState         // WebSocket 连接状态
  streamingText: string                    // 流式文本缓冲
  streamingToolInput: string               // 工具输入流
  activeToolUseId: string | null           // 当前工具调用 ID
  activeToolName: string | null            // 当前工具名称
  activeThinkingId: string | null          // 当前思考块 ID
  pendingPermission: PermissionRequest | null  // 待决权限
  tokenUsage: TokenUsage                   // Token 使用统计
  elapsedSeconds: number                   // 经过时间
  statusVerb: string                       // 随机状态动词
  slashCommands: SlashCommand[]            // 可用命令列表
}

关键方法:

方法 职责
connectToSession(id) 建立 WebSocket 连接
disconnectSession(id) 断开连接
sendMessage(id, content, attachments) 发送用户消息
respondToPermission(id, requestId, allowed, rule) 权限审批
stopGeneration(id) 停止生成
loadHistory(id) 加载历史消息
handleServerMessage(id, msg) 处理服务端消息

数据流向

用户操作
  ↓
Component (调用 Store 方法)
  ↓
Store (更新状态 + 调用 API/WebSocket)
  ↓
API 层 (HTTP 请求 / WebSocket 发送)
  ↓
Server 响应
  ↓
Store (更新状态)
  ↓
Component (自动重渲染)

持久化策略

数据 存储位置 说明
打开的标签页 localStorage 页面刷新/重启后恢复
会话数据 Server JSONL 文件 ~/.claude/sessions/
设置 Server API 通过 HTTP 读写
适配器配置 ~/.claude/adapters.json JSON 文件
会话映射 ~/.claude/adapter-sessions.json 适配器 Chat→Session 映射

六、会话生命周期

一个会话从创建到恢复的完整流程:

创建

用户点击 + 新建会话
  ↓
sessionStore.createSession({ workDir })
  ↓
POST /api/sessions { workDir: '/path/to/project' }
  ↓
Server 创建 Session 记录 → 返回 { sessionId }
  ↓
tabStore.openTab(sessionId)
  ↓
chatStore.connectToSession(sessionId)
  ↓
WebSocket 连接 → 收到 { type: 'connected' }

交互

用户发送消息
  ↓
wsManager.send({ type: 'user_message', content })
  ↓
Server 转发到 CLI 子进程
  ↓
CLI 处理 → 流式返回 stream_event
  ↓
Server 转换为 ServerMessage → 推送到 WebSocket
  ↓
chatStore.handleServerMessage() → UI 更新

断开

用户关闭标签页 / 切换到其他标签
  ↓
chatStore.disconnectSession(sessionId)
  ↓
WebSocket 关闭
  ↓
CLI 子进程可能继续运行(后台任务)

恢复

应用启动 / 切回标签页
  ↓
tabStore.restoreTabs()  // 从 localStorage 读取
  ↓
验证会话是否仍存在 (GET /api/sessions)
  ↓
chatStore.connectToSession(sessionId)
  ↓
chatStore.loadHistory(sessionId)  // GET /api/sessions/:id/messages
  ↓
WebSocket 重连 → 恢复实时通信

会话持久化

Server 使用 JSONLJSON Lines 格式持久化会话数据:

~/.claude/sessions/
├── {sessionId}/
│   ├── messages.jsonl    ← 流式追加,每行一条消息/事件
│   ├── metadata.json     ← 会话元数据workDir、标题、模型等
│   └── ...

JSONL 格式的优势:

  • 流式追加:新消息直接 append无需重写整个文件
  • 崩溃恢复:即使中途崩溃,已写入的行不会丢失
  • 大会话支持:按行读取,无需一次性加载整个 JSON

七、代理层协议转换

Server 内置了多协议代理层Proxy让不同 AI 提供商的 API 统一接入。

支持的 API 格式

格式 说明 典型提供商
anthropic Anthropic Messages API Anthropic 官方、MiniMax
openai_chat OpenAI Chat Completions OpenAI、DeepSeek
openai_responses OpenAI Responses API OpenAI 新接口

协议转换流程

前端发送消息
  ↓
Server 接收
  ↓
判断当前 Provider 的 API 格式
  ↓
┌─────────────────────────────────┐
│  anthropic  │ openai_chat/resp  │
│  直接透传    │ 转换消息格式       │
│             │ system → 系统消息   │
│             │ tool_use → 函数调用 │
│             │ tool_result → 函数结果│
└──────┬──────┴──────┬────────────┘
       │             │
       ↓             ↓
   Anthropic API   OpenAI API
       │             │
       ↓             ↓
   直接返回       反向转换响应格式
       │             │
       └──────┬──────┘
              ↓
         统一的 ServerMessage 格式
              ↓
         WebSocket 推送给前端

模型映射

每个 Provider 可配置四个模型槽位:

槽位 用途
main 主对话模型
haiku 快速/轻量任务
sonnet 中等复杂度
opus 最高能力

例如 OpenRouter 配置:

{
  "apiFormat": "anthropic",
  "baseUrl": "https://openrouter.ai/api/v1",
  "modelMapping": {
    "main": "anthropic/claude-sonnet-4-20250514",
    "haiku": "anthropic/claude-haiku-4-5-20251001",
    "opus": "anthropic/claude-opus-4-20250514"
  }
}

八、适配器桥接架构

适配器桥接架构

适配器系统让任何 IM 平台都可以接入 Claude Code无需修改核心代码。

整体架构

┌──────────────┐    ┌──────────────┐
│   Telegram   │    │    飞书       │    ← IM 平台
│   grammy     │    │   Lark SDK   │
└──────┬───────┘    └──────┬───────┘
       │                   │
       ▼                   ▼
┌──────────────────────────────────┐
│       Adapter 进程               │    ← 平台适配层
│  ┌─────────────────────────────┐ │
│  │       common/ 共享模块       │ │
│  │  config   │  ws-bridge      │ │
│  │  pairing  │  message-buffer │ │
│  │  dedup    │  chat-queue     │ │
│  │  session  │  http-client    │ │
│  └─────────────────────────────┘ │
└──────────────┬───────────────────┘
               │ HTTP + WebSocket
               ▼
┌──────────────────────────────────┐
│       Desktop Server             │    ← 服务端
│  POST /api/sessions              │
│  WS   /ws/{sessionId}           │
└──────────────┬───────────────────┘
               │
               ▼
┌──────────────────────────────────┐
│       CLI 子进程                  │    ← AI 对话引擎
└──────────────────────────────────┘

四层架构

层级 职责 关键文件
配置层 桌面端 UI 填写凭证 desktop/src/pages/AdapterSettings.tsx
存储层 配置读写和脱敏 src/server/services/adapterService.ts
适配层 IM SDK 对接、消息路由 adapters/telegram/index.ts, adapters/feishu/index.ts
会话层 WebSocket 桥接到 Server adapters/common/ws-bridge.ts

共享模块

所有适配器共用 adapters/common/ 模块:

模块 职责
config.ts 配置加载(环境变量 > JSON 文件 > 默认值)
ws-bridge.ts WebSocket 连接管理(心跳、重连、消息路由)
pairing.ts 用户配对6 位安全码、速率限制)
session-store.ts Chat→Session 映射持久化
message-buffer.ts 流式消息缓冲500ms/200 字符阈值刷新)
message-dedup.ts 消息去重(防重连重复)
chat-queue.ts 同一 Chat 消息串行队列
http-client.ts HTTP 客户端(创建会话、获取项目列表)

接入新 IM 平台

得益于模块化设计,接入新平台只需三步:

1. 创建适配器文件

// adapters/discord/index.ts
import { WsBridge } from '../common/ws-bridge.js'
import { MessageBuffer } from '../common/message-buffer.js'
import { loadConfig } from '../common/config.js'
import { isPaired, tryPair } from '../common/pairing.js'

2. 实现消息接收

bot.on('message', async (msg) => {
  // 去重 → 授权检查 → 会话管理 → WebSocket 桥接
  if (!dedup.tryRecord(msg.id)) return
  if (!isPaired('discord', msg.author.id, config)) {
    tryPair(msg.content, { userId: msg.author.id, ... }, 'discord')
    return
  }
  bridge.sendUserMessage(chatId, msg.content)
})

3. 处理服务端事件

bridge.onServerMessage(chatId, async (msg) => {
  switch (msg.type) {
    case 'content_delta': /* 流式文本 */ break
    case 'permission_request': /* 权限按钮 */ break
    case 'message_complete': /* 完成 */ break
  }
})

消息缓冲机制

为防止 IM 平台的消息频率限制,适配器使用 MessageBuffer 批量刷新:

content_delta: "def "    ─┐
content_delta: "sort("   │→ 缓冲累积
content_delta: "arr):"   │
                         ↓ 500ms 或 200 字符
                    IM 平台: "def sort(arr): ▍"  (编辑消息)
                         
content_delta: "\n  ..."  ─┐
content_delta: "return"   │→ 继续缓冲
                          ↓
                    IM 平台: "def sort(arr):\n  return ▍"

message_complete          ↓
                    IM 平台: 最终完整文本(分片发送)

分片限制:

  • Telegram: 4000 字符/消息
  • 飞书: 30000 字符/消息

九、项目目录结构

desktop/
├── src/                           # React 前端
│   ├── App.tsx                   # 根组件
│   ├── main.tsx                  # 入口
│   ├── components/
│   │   ├── layout/               # 布局AppShell, Sidebar, TabBar
│   │   ├── chat/                 # 聊天MessageList, ChatInput, ToolCallBlock
│   │   ├── shared/               # 通用Button, Modal, Toast, Spinner
│   │   ├── controls/             # 控件ModelSelector, PermissionModeSelector
│   │   ├── markdown/             # MarkdownRenderer
│   │   ├── skills/               # 技能浏览
│   │   ├── tasks/                # 任务管理
│   │   └── teams/                # 团队视图
│   ├── pages/                    # 页面ActiveSession, Settings, ScheduledTasks
│   ├── stores/                   # Zustand 状态
│   ├── api/                      # HTTP + WebSocket 客户端
│   ├── types/                    # TypeScript 类型定义
│   ├── hooks/                    # 自定义 Hooks
│   ├── i18n/                     # 国际化
│   ├── theme/                    # 全局样式
│   ├── config/                   # 提供商预设
│   └── lib/                      # 工具库
├── src-tauri/                     # Tauri Rust 后端
│   ├── src/lib.rs                # 核心端口管理、Sidecar 启动
│   ├── Cargo.toml                # Rust 依赖
│   └── tauri.conf.json           # Tauri 配置
├── sidecars/                      # Sidecar 启动器
│   ├── server-launcher.ts        # Server 进程启动
│   └── cli-launcher.ts           # CLI 进程启动
├── scripts/
│   └── build-sidecars.ts         # 跨平台编译脚本
├── vite.config.ts                # Vite 构建配置
├── package.json                  # 依赖和脚本
└── tsconfig.json                 # TypeScript 配置

src/server/                        # 服务端(独立于 desktop/
├── index.ts                      # 入口Bun.serve
├── router.ts                     # HTTP 路由
├── ws/                           # WebSocket 处理
├── api/                          # REST API 路由
├── services/                     # 业务服务
├── proxy/                        # 代理层(协议转换)
├── middleware/                   # 中间件
├── config/                       # 服务端配置
└── types/                        # 类型定义

adapters/                          # 外部 IM 适配器
├── common/                       # 共享模块9 个文件)
├── telegram/index.ts             # Telegram Bot
├── feishu/index.ts               # 飞书 Bot
└── package.json                  # 独立依赖

十、快速参考

关键端口

端口 用途
1420 Vite 开发服务器
动态分配 Server Sidecarreserve_local_port

关键文件路径

文件 说明
~/.claude/adapters.json 适配器配置
~/.claude/adapter-sessions.json 适配器会话映射
~/.claude/sessions/ 会话持久化数据

开发命令

# 前端开发
cd desktop && pnpm dev

# 编译 Sidecar
cd desktop && bun run build:sidecars

# Tauri 开发模式(前端 + 桌面框架)
cd desktop && pnpm tauri dev

# 构建发布包
cd desktop && pnpm tauri build

# 运行测试
cd desktop && pnpm test